个人博客 转载请注明原创出处。java
之因此会想来写泛型相关的内容,是由于看到这样的一段代码:程序员
当时个人心里是这样的:数组
因此就赶忙去复习了下,记录下来。基础不扎实,源码看不懂啊。安全
Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制容许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操做的数据类型被指定为一个参数,在Java集合框架里使用的很是普遍。bash
定义的重点是提供了编译时类型安全检测机制。好比有这样的一个泛型类:markdown
public class Generics <T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } 复制代码
而后写这样一个类:框架
public class Generics { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } 复制代码
它们一样都能存储全部值,可是泛型类有编译时类型安全检测机制:ide
一个类定义了一个或多个类型变量,那么就是泛型类。语法是在类名后用尖括号括起来,类型变量写在里面用逗号分开。而后就能够在方法的返回类型、参数和域、局部变量中使用类型变量了,可是不能在有static
修饰符修饰的方法或域中使用。例子:oop
类定义参考上文例子
使用形式
Generics<String> generics = new Generics<String>();
后面尖括号内的内容在Jdk7之后能够省略
Generics<String> generics = new Generics<>();
复制代码
一个方法定义了一个或多个类型变量,那么就是泛型方法。语法是在方法修饰符后面、返回类型前面用尖括号括起来,类型变量写在里面用逗号分开。泛型方法能够定义在普通类和泛型类中,泛型方法能够被static
修饰符修饰。 例子:this
private <U> void out(U u) { System.out.println(u); } 调用形式, Test.<String>out("test"); 大部分状况下<String>均可以省略,编译器能够推断出来类型 Test.out("test"); 复制代码
有时候咱们会有但愿限定类型变量的状况,好比限定指定的类型变量须要实现List
接口,这样咱们就能够在代码对类型变量调用List
接口里的方法,而不用担忧会没有这个方法。
public class Generics <T extends List> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } public void add(Object u) { value.add(u); } public static void main(String[] args) { Generics<List> generics = new Generics<>(); generics.setValue(new ArrayList<>()); generics.add("ss"); System.out.println(generics.getValue()); } } 复制代码
限定的语法是在类型变量的后面加extends
关键字,而后加限定的类型,多个限定的类型要用&
分隔。类型变量和限定的类型能够是类也能够是接口,由于Java中类只能继承一个类,因此限定的类型是类的话必定要在限定列表的第一个。
类型擦除是为了兼容而搞出来的,大意就是在虚拟机里是没有泛型类型,泛型只存在于编译期间。泛型类型变量会在编译后被擦除,用第一个限定类型替换(没有限定类型的用Object
替换)。上文中的Generics <T>
泛型类被擦除后会产生对应的一个原始类型:
public class Generics { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } 复制代码
之因此咱们能设置和返回正确的类型是由于编译器自动插入了类型转换的指令。
public static void main(String[] args) { Generics<String> generics = new Generics<>(); generics.setValue("ss"); System.out.println(generics.getValue()); } javac Generics.java javap -c Generics 编译后的代码 public static void main(java.lang.String[]); Code: 0: new #3 // class generics/Generics 3: dup 4: invokespecial #4 // Method "<init>":()V 7: astore_1 8: aload_1 9: ldc #5 // String ss 11: invokevirtual #6 // Method setValue:(Ljava/lang/Object;)V 14: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 17: aload_1 18: invokevirtual #8 // Method getValue:()Ljava/lang/Object; 21: checkcast #9 // class java/lang/String 24: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return 复制代码
咱们能够看到在21行插入了一条类型转换的指令。
类型擦除还带来了另外一个问题,若是咱们有一个类继承了泛型类并重写了父类的方法:
public class SubGenerics extends Generics<String> { @Override public void setValue(String value) { System.out.println(value); } public static void main(String[] args) { Generics<String> generics = new SubGenerics(); generics.setValue("ss"); } } 复制代码
由于类型擦除因此SubGenerics
实际上有两个setValue
方法,SubGenerics
本身的setValue(String value)
方法和从Generics
继承来的setValue(Object value)
方法。例子中的generics
引用的是SubGenerics
对象,因此咱们但愿调用的是SubGenerics.setValue
。为了保证正确的多态性,编译器在SubGenerics
类中生成了一个桥方法
:
public void setValue(Object value) { setValue((String) value); } 复制代码
咱们能够编译验证下:
Compiled from "SubGenerics.java" public class generics.SubGenerics extends generics.Generics<java.lang.String> { public generics.SubGenerics(); Code: 0: aload_0 1: invokespecial #1 // Method generics/Generics."<init>":()V 4: return public void setValue(java.lang.String); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_1 4: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: return public static void main(java.lang.String[]); Code: 0: new #4 // class generics/SubGenerics 3: dup 4: invokespecial #5 // Method "<init>":()V 7: astore_1 8: aload_1 9: ldc #6 // String ss 11: invokevirtual #7 // Method generics/Generics.setValue:(Ljava/lang/Object;)V 14: return public void setValue(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #8 // class java/lang/String 5: invokevirtual #9 // Method setValue:(Ljava/lang/String;)V 8: return } 复制代码
引用《Java核心技术 卷一》
总之,须要记住有关 Java 泛型转换的事实: 1.虚拟机中没有泛型,只有普通的类和方法。 2.全部的类型参数都用它们的限定类型替换。 3.桥方法被合成来保持多态。 4.为保持类型安全性,必要时插人强制类型转换。
int,double
等。应该使用它们的包装类Integer,Double
。if (generics instanceof Generics<String>) // Error
Generics<String>[] generics = new Generics<String>[10]; // Error
new T(...) new T[...] 或 T.class
通配符类型和上文中的类型变量的限定有些相似,区别是通配符类型是运用在声明的时候而类型变量的限定是在定义的时候。好比通配符类型Generics<? extends List>
表明任何泛型Generics
类型的类型变量是List
和List
的子类。
Generics<? extends List> generics = new Generics<ArrayList>();
不过这样声明以后Generics
的方法也发生了变化,变成了
这样就致使了不能调用setValue
方法
而getValue
方法是正常的
通配符限定还能够限定超类,好比通配符类型Generics<? super ArrayList>
表明任何泛型Generics
类型的类型变量是ArrayList
和ArrayList
的超类。
Generics<? super ArrayList> generics = new Generics<List>();
一样的,Generics
的方法也发生了变化,变成了
调用getValue
方法只能赋值给Object
变量
调用setValue
方法只能传入ArrayList
和ArrayList
的子类,超类List,Object
等都不行
虽然由于类型擦除,在虚拟机里是没有泛型的。不过被擦除的类仍是保留了一些关于泛型的信息,可使用反射相关的Api
来获取。
相似地,看一下泛型方法
public static <T extends Comparable<? super T>> T min(T[] a)
这是擦除后
public static Comparable min(Coniparable[] a)
可使用反射 API 来肯定:
T
的类型参数。周一就建好的草稿,到了星期天才写好,仍是删掉了一些小节状况下,怕是拖延症晚期了......不过也是由于泛型的内容够多,虽然平常业务里不多本身去写泛型相关的代码,可是在阅读类库源码时要是不懂泛型就步履维艰了,特别是集合相关的。此次的大部份内容都是《Java核心技术 卷一》里的,这但是本关于Java
基础的好书。不过仍是老规矩,光读可不行,仍是要用本身的语言记录下来。众所周知,人类的本质是复读机,把好书里的内容重复一遍,就等于我也有责任了!