使用泛型可以在编译时而不是在运行时检测出错误,在试图使用一个不相容的对象时,编译器就会检测出这个错误。提高了可靠性和可读性。
泛型类型必须是引用类型,不能用基本类型来替代泛型类型。
一、泛型类和接口
class MyStack<E>{
public MyStack() {
//构造方法应该这样定义
}
}
创建时
MyStack<String> stack1 = new MyStack<String>();
可以定义一个类或接口个作为泛型或者接口的子类型
String类实现Comparable接口
public class String implements Comparable<String>
二、泛型方法
public class Test {
public static void main(String[] args){
String[] str1 = {"a", "b", "c"};
Integer[] integer1 = {1, 2, 3};
Test.<String>print(str1);
Test.<Integer>print(integer1);
}
public static <E> void print(E[] list){
for (int i = 0; i < list.length; i++) {
System.out.print(list[i] + " ");
}
System.out.println();
}
}
方法调用中可以省略掉类型参数,写成
Test.print(str1);
可以定义泛型类型为<T extends MyType>
这样的泛型类型称为受限的
三、如何理解<T extends Comparable<T>>
和<T extends Comparable<? super T>>
首先理解一下<T extends Comparable<T>>
类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T。只有这样,T 的实例之间才能相互比较。例如,在实际调用时若使用的具体类是 Food,那么 Food 必须implements Comparable<Food>
再说<T extends Comparable<? super T>>
类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T 或 T 的任一父类。T 的实例之间,T 的实例和它的父类的实例之间,可以相互比较大小。例如,在实际调用时若使用的具体类是 Fruit (假设 Fruit 有一个父类 Food),Fruit 可以从Food 那里继承 Comparable<food>
,或者自己 implements Comparable<Fruit>
四、?非受限通配
和? extends Object是相同的, 表示任何一种对象类型
public class Test {
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("hello");
strList.add("hi");
print(strList);
}
public static <E> void print(List<?> list){
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
}
}
不能使用List<Object>
代替List<?>
因为List<String>
并不是List<Object>
的实例
五、上界<? extends Class>
和下界<? super Class>
get-put principle:
Use an extends wildcard when you only get values out of a structure.
Use a super wildcard when you only put values into a structure.
And don't use a wildcard when you both want to get and put from/to a structure.
Exceptions are:
You cannot put anything into a type declared with an extends wildcard except for the value null, which belongs to every reference type.
You cannot get anything out from a type declared with an super wildcard except for a value of type Object, which is a super type of every reference
PECS原则:
如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
如果既要存又要取,那么就不要使用任何通配符。
原问题连接:http://stackoverflow.com/questions/1292109/explanation-of-the-get-put-principle
大概意思为
class Fruit{}
class Apple extends Fruit{}
class Banana extends Fruit{}
public class Test {
public static void main(String[] args){
List<? extends Fruit> list = new ArrayList<>();
//list.add(new Apple());
//list.add(new Banana());
list.add(null);
list.contains(new Apple());
list.indexOf(new Apple());
}
}
不能向其中添加Apple或Banana的实例
你知道那是fruit的集合,但是不知道是那种特定的。可以取出,例如list.contains()和list.indexOf(),你知道取出的将会是fruit。但是不能添加进去,编译器并不能了解这里到底需要哪种 Fruit 的子类型
但是可以add(null),因为 null 代表任何类型。
class Fruit{}
class Banana extends Fruit{}
class YellowBanana extends Banana{}
public class Test {
public static void main(String[] args){
List<? super Banana> list = new ArrayList<>();
list.add(new Banana());
list.add(new YellowBanana());
//list.add(new Fruit());
//Banana banana = list.get(0);
Object banana = list.get(0);
}
}
现在是List<? super Banana>
,其中都是具有Banana的父类的列表,可以添加进Banana的实例或者Banana的子类YellowBanana。但是不能add(new Fruit()), 因为?代表Banana的父类,但是编译器不知道你要添加哪种Banana的父类,因此不能安全地添加。
当取出的时候,你不知道将会得到什么(可能不会是个Banana),所以不能,可以确定它是一个Object。因为编译器不能确定列表中的是Banana的哪个子类,所以只能返回 Object。
举个使用例子
public class Test {
public static void main(String[] args){
List<String> list1 = new ArrayList<>();
List<Object> list2 = new ArrayList<>();
list1.add("hello");
list1.add("world");
list2.add(1);
add(list1, list2);
print(list2);
}
public static <T> void add(List<T> list1, List<? super T> list2){
for (int i = 0; i < list1.size(); i++) {
list2.add(list1.get(i));
}
}
public static void print(List<Object> list){
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
输出:
1
hello
world
这里<? super T>
表示类型T或T的父类型。Object是String的父类型
泛型类型和通配类型之间的关系(无视水印orz)
六、擦除
泛型是使用一种称为类型擦除的方法实现的,编译器使用泛型类型信息来编译代码,但是随后消除它,因此泛型信息在运行时是不可用的,这种方法使泛型代码向后兼容使用原始类型的遗留代码
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2);
结果为:true
这是java泛型擦除造成的,ArrayList<Integer>()
和new ArrayList<String>()
,在编译器中都会转换成原始类型ArrayList。泛型存在于编译时,一旦编译器确认泛型类型是安全使用的,就会将它替换成原始类型
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
类型擦除的原则
1、所有参数化容器类都被擦除成非参数化,如List<E>
擦除成Lsit
2、参数化的数组被擦除成非参数化的数组,如List<E>[]
擦除成List[]
3、参数类型E,没有上限的情况下,擦除成Object
4、所有约束参数,如<? extends E>
擦除成E
5、多个约束的情况下,擦除成第一个,如<T extends Object & E>
擦除成Object
七、擦除产生的问题
1、不管实际的具体类型是什么,泛型类是被它的所有实例所共享的。在编译时ArrayList<Integer>
和ArrayList<String>
是两种不同的类型,但是运行时只有一个ArrayList会被加载。
所以要求所有静态成员不能够是泛型
2、重载冲突
public void method(T t){
\\do something
}
public void method(Object t){
\\do something
}
报错:Method method(Object) has the same erasure method(Object) as another method in type Test1
是因为参数T被擦除成了Object,导致这两个方法的特征签名变得一模一样
3、接口冲突
class Test2 implements Comparable<String>, Comparable<Integer>{
//错误的
}
这里Comparable<String>, Comparable<Integer>
都是泛型接口,被擦除成Comparable。
如果要实现多个泛型接口,只能实现具有不同擦除效果的接口
八、擦除对泛型的限制
由于泛型类型在运行时被擦除, 对于如何使用泛型类型有一些限制
1、if(obj instanceof T);
2、不能使用new E()
例如
运行时泛型类型是不可用的
3、不能使用new E[]
4、在静态的环境下不允许类的参数是泛型
5、异常类不能是泛型
假如定义一个泛型的异常类
class MyException<T> extends Exception{
}
捕获异常的时候
因为运行时类型信息是不出现的
解除擦除带来的问题
1.解决instanceof
先理解一下什么是Class<T>
使用泛型, Class.newInstance() 方法具有一个更加特定的返回类型:
class Class<T> {
T newInstance();
}
T指的是由此 Class 对象建模的类的类型。例如,Integer.class 的类型是 Class<Integer>
。如果将被建模的类未知,则使用 Class<?>
。
创建实例:调用方法 Class.forName() 或者使用类常量X.class。Class.forName() 被定义为返回 Class。另一方面,类常量 X.class 被定义为具有类型 Class,所以 String.class 是Class 类型的。
现在来说明如何利用Class<T>
解决instanceof的问题
Class<T> type;
if ( type.isInstance(obj) ) {
//...
}
原问题连接:http://stackoverflow.com/questions/5734720/java-generics-obj-instanceof-t
举个例子:
class A{}
class B extends A{}
public class Test<T> {
private Class<T> type;
public Test(Class<T> type){
this.type = type;
}
public boolean compareTo(Object obj){
return type.isInstance(obj);
}
public static void main(String[] args) {
Test<A> test = new Test<A>(A.class);
System.out.println(test.compareTo(new A()));
System.out.println(test.compareTo(new B()));
}
}
结果:
true
true
二、解决泛型数组
最简单的方式是通过Array.newInstance(Class<t>type,int size)
的方式来创建数组。
public class Test<T> {
private Class<T> type;
public Test(Class<T> type) {
this.type = type;
}
@SuppressWarnings("unchecked")
T[] createArray(int size) {
return (T[]) Array.newInstance(type, size);
}
public static void main(String[] args) {
Test<String> test = new Test<String>(String.class);
String[] list = test.createArray(10);
}
}