泛型使用与泛型擦除
Java 泛型泛型(generics)是Java 1.5 中引入的特性。泛型的引入使得代码的灵活性和复用性得以增强,对于容器类的作用更为明显。泛型可以加在类、接口、方法之上。如下所示:public class Generic1<T> {T t;List<T> list;//表示返回值为K,参数类型为Kpubli
Java 泛型
泛型(generics)是Java 1.5 中引入的特性。泛型的引入使得代码的灵活性和复用性得以增强,对于容器类的作用更为明显。
泛型可以加在类、接口、方法之上。如下所示:
public class Generic1<T> {
T t;
List<T> list;
//表示返回值为K,参数类型为K
public <K> K test(K e) {
return e;
}
}
泛型类型参数以<>定义,括号内可以定义多个泛型,如<K,V>。
泛型的类型参数只能是对象类型(包括自定义类),不能是简单类型。定义了泛型后,就可以在原来使用具体类型的地方以泛型代替。注意泛型添加的位置,如果是类上的泛型,添加在类名之后;如果是方法上的泛型,添加在修饰符之后,返回值之前。
定义了泛型之后,我们就可以使用了。
public class Generic1<T> {
T t;
public <K> K test(K e) {
return e;
}
public static void main(String[] args) {
Generic1<String> g = new Generic1<>();
System.out.println(g.test(2));
System.out.println(g.test("2"));
}
}
可以看到,我们只需要在使用的时候指定具体的类型即可。我们可以给 test 方法传递任意类型的参数,在没有泛型前,我们只能用方法重载实现。
类型上界
在上面的例子中,我们可以给类传递任何泛型参数。
如果我们有这样一个需求,传递的参数要是某个类的子类。
比如现在有一个类,表示将传进来的水果制成果汁,那传进来的类只能是某种水果,而不能是其它东西。extends 可以实现这样的效果。
extends 关键字指定泛型类型的上界,表示该类型必须是继承某个类,或者实现某个接口,也可以是这个类或接口本身。
示例如下:
public class Generic1<T extends List<String>> {
T t;
List<T> list;
public <K extends Number> K test(K e) {
return e;
}
public static void main(String[] args) {
Generic1<ArrayList<String>> g = new Generic1<>();
System.out.println(g.test((byte) 2));
System.out.println(g.test(2));
System.out.println(2L);
System.out.println(g.test(2.0f));
System.out.println(g.test(2.0));
//无法编译,提示参数类型错误
//System.out.println(g.test("hello"));
}
}
在这个例子中, Generic1 类上的泛型参数只能接受 List 或 List 的子类,传递给 test( ) 方法的只能是 Number 类型的数据。
当没有指定泛型继承的类型或接口时,默认为 extends Object,此时任何类型都可以作为参数传入。
注意:对于 ? extends 的通配符限定泛型,我们无法向里面添加元素(只可以添加null),只能读取其中的元素。
类型下界
super 指定泛型类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。
示例如下:
class Fruit {}
class Apple extends Fruit {}
class Banna extends Fruit {}
class FujiApple extends Apple {}
public class Generic2 {
public static void test(List<? super FujiApple> list) {
list.add(new FujiApple());
//list.add(new Apple());编译错误
}
}
我们可以向 list 中通过 add( ) 方法添加 FujiApple 类,但却不能添加 Apple( ) 类。事实上我们只能添加 FujiApple 及其子类,而不能添加它的任意超类。
正确的用法应该是这样的:
public class Generic2 {
public static void test(List<? super FujiApple> list) {
list.add(new FujiApple());
//list.add(new Apple());编译错误
System.out.println(list);
}
public static void main(String[] args) {
List<? super FujiApple> list = new ArrayList<Apple>();
List<? super FujiApple> list1 = new ArrayList<Fruit>();
//编译错误
//List<? super FujiApple> list2 = new ArrayList<Banana>();
test(list);
test(list1);
}
}
没错,就是多态,super 提供了多态支持。
注意:对于 ?super 的通配符限定泛型,我们可以读取其中的元素,但读取出来的元素会变为 Object 类型。
通配符(Wildcards)
?叫做通配符,表示任意类型,上面的例子中已经出现了。它与类型参数T的不同点如下:(Java 泛型通配符和类型限定)
- T 只有extends一种限定方式,<T extends List>是合法的,<T super List>是不合法的
- ?有extends与super两种限定方式,即<? extends List> 与<? super List>都是合法的
- T 用于泛型类和泛型方法的定义。?用于泛型方法的调用和形参,即下面的用法是不合法的:
public class Generic1<? extends List<String> {
public <? extends List> void test(String t) {
}
}
- T 可用于多重限定,如 <T extends A & B>,通配符 ?不能进行多重限定
PECS法则
生产者(Producer)使用 extends,消费者(Consumer)使用 super。
如果需要读取 T 类型的元素,需要声明成 <? extends T>,例如 List<? extends Apple>,此时不能往列表中添加元素。
如果需要添加 T 类型的元素,需要声明成 <?super T>,例如 List<? super Apple>,此时可以向其中添加 Apple 及其子类。从其中取元素的时候,要注意取出元素的类型是 Object。
如果需要同时添加和使用,不使用泛型通配符。
泛型与数组
不能创建泛型数组,下面的语句是无法编译通过的
ArrayList<String>[] genericArray = new ArrayList<String>[10];
数组是协变的,即如果A ≤ B,则 f(A) ≤ f(B),举个例子:
Number[] i = new Integer[10];
因为Integer是Number的子类,所以我们可以将Integer类型数组赋给Number类型数组的引用变量。我们自然会想到,泛型是否也可以这样?如下所示:
ArrayList<Number> list = new ArrayList<Integer>();
事实上,上面这句是无法编译通过的。很显然,泛型不是协变的,泛型具有无关性。正确的使用方法如下:
ArrayList<? extends Number> list = new ArrayList<Integer>();
静态成员与静态方法
无法通过类上的泛型定义类的泛型静态成员变量和静态方法。
例如,下面的写法是错误的
public class Generic3<T extends List> {
private static T t;
public static T void test(String t) {}
}
类的静态变量与静态方法是该类所有示例共享的,如果有两个实例具体化了不同的参数类型,那此时静态变量和静态方法的泛型到底是哪一个呢?因此才有这个限制。但你可以在静态方法上加泛型,如下:
public static <T> void f(T t) { }
泛型擦除(Type Erasure)
Java中的泛型擦除是指在编译后的字节码文件中类型信息被擦除,变为原生类型(raw type),因此在运行期,ArrayList<Integer> 与 ArrayList<String> 就是同一个类。
实际上 Java 泛型的擦除并不是对所有使用泛型的地方都会擦除的,部分地方会保留泛型信息。泛型技术相当于 Java 语言的一颗语法糖,这种实现泛型的方法称为伪泛型(参考深入理解Java虚拟机第二版)。
在泛型类被类型擦除的时候,如果类型参数部分没有指定上限,如 <T> 会被转译成普通的 Object 类型,如果指定了上限,则类型参数被替换成类型上限。
例如,下面的例子在编译期无法通过:
public class Generic4 {
public void test(ArrayList<Integer> list) {
}
public void test(ArrayList<String> list) {
}
}
ArrayList<Integer> 与 ArrayList<String> 编译后都被擦除了,变成了原生类型 ArrayList。
(注:深入理解Java虚拟机(第二版)所说加返回值后,javac 可以编译通过,经测试,在Java 1.8 下无法编译通过)
我们可以借助Java的Type接口获取泛型(Java中的Type详解)。
看下面一个例子:
class FF<K, V> {}
public class Generic4<K extends Integer, V extends String> extends FF<String, Integer> {
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
Generic4<Integer, String> instance = new Generic4<>();
System.out.println(Arrays.toString(instance.getClass().getTypeParameters()));
System.out.println(instance.getClass().getGenericSuperclass());
System.out.println(Arrays.toString(Generic4.class.getTypeParameters()));
System.out.println(Generic4.class.getGenericSuperclass().getTypeName());
System.out.println("-----------------------------------------------");
Map<Integer, String> map = new HashMap<Integer, String>();
Map<Integer, String> map1 = new HashMap<Integer, String>() {
};
ParameterizedType paraType1 = (ParameterizedType) map.getClass().getGenericSuperclass();
Type[] type1 = paraType1.getActualTypeArguments();
System.out.println(Arrays.toString(type1));
ParameterizedType paraType2 = (ParameterizedType) map1.getClass().getGenericSuperclass();
Type[] type2 = paraType2.getActualTypeArguments();
System.out.println(Arrays.toString(type2));
System.out.println("-----------------------------------------------");
FF<String, Integer> ff = new FF<>();
System.out.println(ff.getClass().getGenericSuperclass());
}
}
输出结果:
[K, V]
com.test.FF<java.lang.String, java.lang.Integer>
[K, V]
com.test.FF<java.lang.String, java.lang.Integer>
-----------------------------------------------
[K, V]
[class java.lang.Integer, class java.lang.String]
-----------------------------------------------
class java.lang.Object
泛型的几点结论:
- 如果通过 new 创建了类或者直接通过类似 FF.class 的形式(我们无法使用 FF<String,Integer>.class 这样的形式),我们并不能因此获得实际的类型变量,通过反射只能得到占位符的形式。这么做是要避免在创建泛型实例时而创建新的类,从而避免运行时的过度消耗
- 如果继承了类A或实现了接口B,并且具体化了A或B中的泛型,那么可以获得A或B中的实际的类型变量
- 对于成员变量,我们只能得到与类上的泛型声明相同的结果
- 对于方法声明中的泛型,如果没有指定上限,通过反射返回的是 Object 类型,否则返回的是我们指定的上限
- 对于方法参数中和方法内部的泛型(方法内部的泛型可以借助匿名内部类间接获取泛型),我们无法获得它的实际的变量类型,只能得到占位符的形式
泛型与序列化
当序列化一个泛型类,然后反序列化时,会丧失原有的类型信息。示例如下:
class Serial<T> implements Serializable {
ArrayList<T> list = new ArrayList<>();
public void f(T t) {
list.add(t);
}
}
public class Generic5 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, SecurityException {
Serial<String> serial = new Serial<>();
serial.f("hello");
//序列化对象
FileOutputStream out = new FileOutputStream("e:/ToSerial.txt");
ObjectOutputStream objectToOut = new ObjectOutputStream(out);
objectToOut.writeObject(serial);
//反序列化对象
ObjectInputStream objectToRead = new ObjectInputStream(new FileInputStream("e:/ToSerial.txt"));
Serial<Float> restore = (Serial) objectToRead.readObject();
restore.f(2.0f);
System.out.println(restore.list);
objectToOut.close();
objectToRead.close();
}
}
输出结果:
[hello, 2.0]
从结果可以看到,反序列化后我们可以将浮点数加入到原本是 String 类型的 list 中,说明反序列化后原有的类型限制消失了。
更多推荐
所有评论(0)