We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
我的博客 转载请注明原创出处。
之所以会想来写泛型相关的内容,是因为看到这样的一段代码:
当时我的内心是这样的:
所以就赶紧去复习了下,记录下来。基础不扎实,源码看不懂啊。
Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,在Java集合框架里使用的非常广泛。
定义的重点是提供了编译时类型安全检测机制。比如有这样的一个泛型类:
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; } }
它们同样都能存储所有值,但是泛型类有编译时类型安全检测机制:
一个类定义了一个或多个类型变量,那么就是泛型类。语法是在类名后用尖括号括起来,类型变量写在里面用逗号分开。然后就可以在方法的返回类型、参数和域、局部变量中使用类型变量了,但是不能在有static修饰符修饰的方法或域中使用。例子:
static
类定义参考上文例子 使用形式 Generics<String> generics = new Generics<String>(); 后面尖括号内的内容在Jdk7以后可以省略 Generics<String> generics = new Generics<>();
一个方法定义了一个或多个类型变量,那么就是泛型方法。语法是在方法修饰符后面、返回类型前面用尖括号括起来,类型变量写在里面用逗号分开。泛型方法可以定义在普通类和泛型类中,泛型方法可以被static修饰符修饰。 例子:
private <U> void out(U u) { System.out.println(u); } 调用形式, Test.<String>out("test"); 大部分情况下<String>都可以省略,编译器可以推断出来类型 Test.out("test");
有时候我们会有希望限定类型变量的情况,比如限定指定的类型变量需要实现List接口,这样我们就可以在代码对类型变量调用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中类只能继承一个类,所以限定的类型是类的话一定要在限定列表的第一个。
extends
&
类型擦除是为了兼容而搞出来的,大意就是在虚拟机里是没有泛型类型,泛型只存在于编译期间。泛型类型变量会在编译后被擦除,用第一个限定类型替换(没有限定类型的用Object替换)。上文中的Generics <T>泛型类被擦除后会产生对应的一个原始类型:
Object
Generics <T>
之所以我们能设置和返回正确的类型是因为编译器自动插入了类型转换的指令。
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类中生成了一个桥方法:
SubGenerics
setValue
setValue(String value)
Generics
setValue(Object value)
generics
SubGenerics.setValue
桥方法
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<? extends List> generics = new Generics<ArrayList>();
不过这样声明之后Generics的方法也发生了变化,变成了
这样就导致了不能调用setValue方法
而getValue方法是正常的
getValue
通配符限定还可以限定超类,比如通配符类型Generics<? super ArrayList>代表任何泛型Generics类型的类型变量是ArrayList和ArrayList的超类。
Generics<? super ArrayList>
ArrayList
Generics<? super ArrayList> generics = new Generics<List>();
同样的,Generics的方法也发生了变化,变成了
调用getValue方法只能赋值给Object变量
调用setValue方法只能传入ArrayList和ArrayList的子类,超类List,Object等都不行
List,Object
虽然因为类型擦除,在虚拟机里是没有泛型的。不过被擦除的类还是保留了一些关于泛型的信息,可以使用反射相关的Api来获取。
Api
类似地,看一下泛型方法
public static <T extends Comparable<? super T>> T min(T[] a)
这是擦除后
public static Comparable min(Coniparable[] a)
可以使用反射 API 来确定:
T
周一就建好的草稿,到了星期天才写好,还是删掉了一些小节情况下,怕是拖延症晚期了......不过也是因为泛型的内容够多,虽然日常业务里很少自己去写泛型相关的代码,但是在阅读类库源码时要是不懂泛型就寸步难行了,特别是集合相关的。这次的大部分内容都是《Java核心技术 卷一》里的,这可是本关于Java基础的好书。不过还是老规矩,光读可不行,还是要用自己的语言记录下来。众所周知,人类的本质是复读机,把好书里的内容重复一遍,就等于我也有责任了!
Java
The text was updated successfully, but these errors were encountered:
No branches or pull requests
序
之所以会想来写泛型相关的内容,是因为看到这样的一段代码:
当时我的内心是这样的:
所以就赶紧去复习了下,记录下来。基础不扎实,源码看不懂啊。
泛型介绍
Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,在Java集合框架里使用的非常广泛。
定义的重点是提供了编译时类型安全检测机制。比如有这样的一个泛型类:
然后写这样一个类:
它们同样都能存储所有值,但是泛型类有编译时类型安全检测机制:
泛型类
一个类定义了一个或多个类型变量,那么就是泛型类。语法是在类名后用尖括号括起来,类型变量写在里面用逗号分开。然后就可以在方法的返回类型、参数和域、局部变量中使用类型变量了,但是不能在有
static
修饰符修饰的方法或域中使用。例子:泛型方法
一个方法定义了一个或多个类型变量,那么就是泛型方法。语法是在方法修饰符后面、返回类型前面用尖括号括起来,类型变量写在里面用逗号分开。泛型方法可以定义在普通类和泛型类中,泛型方法可以被
static
修饰符修饰。例子:
类型变量的限定
有时候我们会有希望限定类型变量的情况,比如限定指定的类型变量需要实现
List
接口,这样我们就可以在代码对类型变量调用List
接口里的方法,而不用担心会没有这个方法。限定的语法是在类型变量的后面加
extends
关键字,然后加限定的类型,多个限定的类型要用&
分隔。类型变量和限定的类型可以是类也可以是接口,因为Java中类只能继承一个类,所以限定的类型是类的话一定要在限定列表的第一个。类型擦除
类型擦除是为了兼容而搞出来的,大意就是在虚拟机里是没有泛型类型,泛型只存在于编译期间。泛型类型变量会在编译后被擦除,用第一个限定类型替换(没有限定类型的用
Object
替换)。上文中的Generics <T>
泛型类被擦除后会产生对应的一个原始类型:之所以我们能设置和返回正确的类型是因为编译器自动插入了类型转换的指令。
我们可以看到在21行插入了一条类型转换的指令。
类型擦除还带来了另一个问题,如果我们有一个类继承了泛型类并重写了父类的方法:
因为类型擦除所以
SubGenerics
实际上有两个setValue
方法,SubGenerics
自己的setValue(String value)
方法和从Generics
继承来的setValue(Object value)
方法。例子中的generics
引用的是SubGenerics
对象,所以我们希望调用的是SubGenerics.setValue
。为了保证正确的多态性,编译器在SubGenerics
类中生成了一个桥方法
:我们可以编译验证下:
约束与局限性
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
基础的好书。不过还是老规矩,光读可不行,还是要用自己的语言记录下来。众所周知,人类的本质是复读机,把好书里的内容重复一遍,就等于我也有责任了!The text was updated successfully, but these errors were encountered: