/**
* @version 1.0
* @author 子月初七
*/
复制代码
泛型是从Java1.5开始引进的,所谓的泛型能够理解成参数化类型,即类型是以参数的方式传入泛型类或者泛型方法。 泛型这个术语的意思是:“适用于许多许多的类型”。java
泛型可使编写的代码被不少不一样的类型对象所重用。编程
使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,而后再进行强制类型转换的代码具备更好的安全性和可读性。泛型对于集合类尤为有用,例如,ArrayList就是一个无处不在的集合。api
泛型类能够当作普通类的工厂。数组
package com.corejava.genericprogramming;
public class ArrayList{
private Object[] elementData;
...
public Object get(int i ){...}
public void add(Object o){...}
}
复制代码
这种方法有两个问题。安全
ArrayList files = new ArrayList();
String filename = (String) files.get(0);
复制代码
//ArrayList类有一个类型参数用来指示元素的种类
ArrayList<String> files = new ArrayList<String>();
//JavaSE 7后,构造函数能够省略泛型类型。
ArrayList<String> files = new ArrayList<>();
复制代码
当调用get的时候,不须要进行强制类型转换。 编译器就知道返回值类型为String,而不是Object。bash
String filename = files.get(0);
复制代码
具备一个或多个类型变量的类称之为泛型类。app
下面是一个泛型类。ide
package com.corejava.genericprogramming;
public class Pair<T> {
private T first;
private T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
复制代码
由上段代码可见,类型变量位于类名以后的尖括号<>中。函数
能够引进多个类型变量。ui
public class Pair<T, U> {...}
复制代码
在Java库中,E表示集合的元素类型,K和V表示关键字和值,T或者U或者S等表示任意类型。
泛型方法能够定义在普通类中,也能够定义在泛型类中。
注意,类型变量放在修饰符后面,返回类型前面。
package com.corejava.genericprogramming;
public class ArrayAlg {
//若是没有<T>声明,不能称之为泛型方法
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
public static void main(String[] args) {
//System.out.println(ArrayAlg.<String>getMiddle("Matthew","Xu","CoreJava"));
//通常状况下能够省略<String>类型参数。
//编译器有足够的信息推断出所调用的方法。
System.out.println(ArrayAlg.getMiddle("Matthew","Xu","CoreJava"));
}
}
复制代码
以下定义一个泛型接口。
package com.corejava.genericprogramming;
public interface IGeneric<T> {
public T test();
}
复制代码
泛型接口未传入泛型实参时
package com.corejava.genericprogramming;
public class TestGeneric<T> implements IGeneric<T>{
@Override
public T test() {
return null;
}
}
复制代码
泛型接口传入泛型实参时
package com.corejava.genericprogramming;
public class TestGeneric implements IGeneric<String>{
@Override
public String test() {
return null;
}
}
复制代码
有的时候,类或方法须要对类型变量加以约束。 下面是一个计算最小元素的例子。
package com.corejava.genericprogramming;
public class ArrayAlg {
public static <T> T getMin(T[] a) {
if( a == null || a.length == 0)
return null;
T min = a[0];
for (T t : a) {
if(min.compareTo(t) > 0)
min = t;
}
return min;
}
}
复制代码
这里有个问题,T是任意类型,可是不能保证T含有compareTo方法,在编写代码过程当中会直接报错。
解决方案即是给T加上限定,使其实现Comparable接口。
public static <T extends Comparable> T getMin(T[] a){...}
复制代码
一个类型变量或者通配符能够有多个限定。
不管限定是接口仍是类,只用extends来链接。
选择关键字extends的缘由是更接近子类的概念。
限定类型用&分隔,而逗号用来分隔类型变量。
类型变量的限定类型中能够有多个接口,但最多一个类。
若是须要用一个类做为限定,它必须是限定列表中的第一个。
T extends Comparable & Serializable
复制代码
请注意,虚拟机没有泛型类型对象——全部对象属于普通类。
泛型类型的原始类型是删除类型参数后的泛型类型名。
擦除类型变量,而且替换成限定类型。
若是没有限定类型,使用Object。
例如,Pair的原始类型以下所示。
package com.corejava.genericprogramming;
public class Pair {
private Object first;
private Object second;
public Pair(Object first, Object second){
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public void setFirst(Object first) {
this.first = first;
}
public Object getSecond() {
return second;
}
public void setSecond(Object second) {
this.second = second;
}
}
复制代码
若是泛型类型含有限定的类型变量,那么原始类型可使用第一个限定的类型变量替换。若是没有限定就用Object替换。
代码示例:
package com.corejava.genericprogramming;
import java.io.Serializable;
public class Interval <T extends Comparable & Serializable> implements Serializable{
private T lower;
private T upper;
public Interval(T first, T second) {
if(first.compareTo(second) <= 0) {
lower = first;
upper = second;
}else {
lower = second;
upper = first;
}
}
}
复制代码
原始类型:
package com.corejava.genericprogramming;
import java.io.Serializable;
public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
public Interval(Comparable first, Comparable second) {
if(first.compareTo(second) <= 0) {
lower = first;
upper = second;
}else {
lower = second;
upper = first;
}
}
}
复制代码
以以前的Pair类为例,只有Pair<Double>,没有Pair<double>。
缘由:
擦除以后,Pair类含有Object类的域,而Object不能存储double值。
笔者在这里其实有个疑惑,就算擦除以后,为何Object不能存放double?
double是能够自动装箱成Double,而Double做为一个对象类型是能够被Object存储的。
请看下列两段代码。
package com.corejava.genericprogramming;
public class Generic<T> {
private T first;
private T second;
public Generic(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
复制代码
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
Generic<String> genericStr = new Generic<>("first", "second");
//error:Cannot perform instanceof check against parameterized type Generic<String>.
//Use the form Generic<?> instead since further generic type information will be erased at runtime
//System.out.println(genericStr instanceof Generic<String>);
//error:Cannot perform instanceof check against parameterized type Generic<String>.
//Use the form Generic<?> instead since further generic type information will be erased at runtime
//System.out.println(genericStr instanceof Generic<T>);
//输出:true
System.out.println(genericStr instanceof Generic);
//输出:class com.corejava.genericprogramming.Generic
System.out.println(genericStr.getClass());
}
}
复制代码
因而可知,若是想查询一个对象是否属于某个泛型类型时,使用instanceof会获得一个编译器错误。
若是使用getClass方法则会返回一个原始类型。
不能实例化参数化类型的数组
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
//实际这行代码会报错,报错信息:Cannot create a generic array of Generic<String>
Generic<String>[] array = new Generic<String>[10];
//类型擦除后,array的类型即为Generic[]。
//能够把它转换成Object[]类型。
Object[] objarray = array;
//数组会记住它的元素类型,当传入其余类型元素,本应会报错。
//可是对于泛型来讲,擦除使检查机制无效。
//出于这个缘由,参数化类型的数组不容许被建立。
objarray[0] = "hello";
}
}
复制代码
须要说明的是,声明类型为Generic[]的变量还是合法的,可是不能用new Generic[10]进行初始化。
若是须要收集参数类型化对象,只能使用ArrayList。
不能使用new T(...)或者new T[...]或T.class这样的表达式中的类型变量。 下面的构造器是非法的。
public Generic(){
first = new T();
second = new T();
}
复制代码
public static <T extends Comparable> T[] minmax(T[] a) {
//error:
T[] mm = new T[2];
}
复制代码
不能在静态域或方法中引用类型变量。 请看代码:
package com.corejava.genericprogramming;
public class Generic<T> {
private T first;
private T second;
public Generic(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
public void test(T t) {
System.out.println("hello");
}
//若是不声明T,会给出下列报错信息。
//error: Cannot make a static reference to the non-static type T
public static <T> void name(T t) {
System.out.println("hello");
}
}
复制代码
既不能抛出也不能捕获泛型类对象。
实际上,甚至泛型类拓展Throwable都是不合法的。
//The generic class Generic<T> may not subclass java.lang.Throwable
public class Generic<T> extends Exception{...}
复制代码
不管S与T有什么联系,一般,Pair<E>与Pair<T>没有什么联系。 请看下列代码。
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
Manager ceo = new Manager();
Manager cfo = new Manager();
Employee employee = new Employee();
Generic<Manager> managerGroup = new Generic<>(ceo, cfo);
//error:Type mismatch: cannot convert from Generic<Manager> to Generic<Employee>
Generic<Employee> employeeGroup = managerGroup;
employeeGroup.setFirst(cfo);
}
}
复制代码
注意泛型和数组之间的区别。
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
Manager ceo = new Manager();
Manager cfo = new Manager();
Employee lowerEmployee = new Employee();
Manager[] managerGroup = new Manager[] {ceo, cfo};
//操做合法
Employee[] employeeGroup = managerGroup;
//error: Type mismatch: cannot convert from Employee to Manager
managerGroup[0] = lowerEmployee;
}
}
复制代码
通配符类型中,容许类型参数变化。
例如,通配符类型Pair< ? extends Employee>表示任何泛型Pair类型,它的类型参数是Employee的子类。
假设要编写一个方法。
package com.corejava.genericprogramming;
public class GenericTest {
public static void printGroup(Generic<Employee> g) {
System.out.println(g.getFirst().getName() + g.getSecond().getName());
}
}
复制代码
可是这个方法不能将Generic传入。
public static void main(String[] args) {
Manager m1 = new Manager();
Manager m2 = new Manager();
m1.setName("mat");
m2.setName("xu");
Generic<Manager> gm = new Generic<Manager>(m1, m2);
//error:The method printGroup(Generic<Employee>) in the type GenericTest is not applicable for the arguments (Generic<Manager>)
printGroup(gm);
}
复制代码
解决的办法即是使用通配符类型。
public static void printGroup(Generic<? extends Employee> g) {...}
复制代码
但笔者在这里有个疑惑,为何不能这么写。
public static void printGroup(Generic<T extends Employee> g) {...}
复制代码
Eclipse给出的报错信息是:
Incorrect number of arguments for type Generic<T>; it cannot be parameterized with arguments <T, Employee>
复制代码
也就是说其实<? extends Employee>算一个参数,而算两个参数。 对应的泛型类Generic只包含一个类型参数,因此后者不能经过。
本文参考了大量文章,将来会加入《Java编程思想》和《Effective Java》的内容和观点。目前主要内容仍是来自《Java核心技术》,经过这篇博文的撰写,原先比较陌生的Java泛型如今也比较熟悉了。可是仍然未涉及其复杂之处。若是须要转载,请注明出处!若是有什么疑问或者看法,请分享你的观点!谢谢你们!
参考连接