Java 泛型
1. 概述
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。
也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
2. 目的
类型安全性:一旦使用类型参数后,在该方法或框架中就不存在其他的数据类型,同时也避免了类型转化的需求;
3. 泛型擦除
泛型是提供给javac编译器看的,编译器阻止源程序的非法输入,限定集合中的输入对象的类型
编译器在编译期间去掉带参数类型说明的集合,字节码文件中不存在泛型的类型信息. 跳过编译期后皆可往某个泛型集合加入其它类型的对象
3.1 .java文件
package com.pinnet.test; import java.util.ArrayList; public class Generic<T> { T name; public static void say() { ArrayList<Integer> ric = new ArrayList<>(); ric.add(12); } public T hi() { return name; } }
3.2 .class文件反汇编(看不太懂啊)
D:\eclipse\workspace\NorthAPI\bin\com\pinnet\test>javap -verbose Generic.class Classfile /D:/eclipse/workspace/NorthAPI/bin/com/pinnet/test/Generic.class Last modified 2018-8-15; size 920 bytes MD5 checksum 0c37f13b82e7c11498e5a1193ad70010 Compiled from "Generic.java" public class com.pinnet.test.Generic<T extends java.lang.Object> extends java.lang.Object minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Class #2 // com/pinnet/test/Generic #2 = Utf8 com/pinnet/test/Generic #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 name #6 = Utf8 Ljava/lang/Object; #7 = Utf8 Signature #8 = Utf8 TT; #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Methodref #3.#13 // java/lang/Object."<init>":()V #13 = NameAndType #9:#10 // "<init>":()V #14 = Utf8 LineNumberTable #15 = Utf8 LocalVariableTable #16 = Utf8 this #17 = Utf8 Lcom/pinnet/test/Generic; #18 = Utf8 LocalVariableTypeTable #19 = Utf8 Lcom/pinnet/test/Generic<TT;>; #20 = Utf8 say #21 = Class #22 // java/util/ArrayList #22 = Utf8 java/util/ArrayList #23 = Methodref #21.#13 // java/util/ArrayList."<init>":()V #24 = Methodref #25.#27 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer; #25 = Class #26 // java/lang/Integer #26 = Utf8 java/lang/Integer #27 = NameAndType #28:#29 // valueOf:(I)Ljava/lang/Integer; #28 = Utf8 valueOf #29 = Utf8 (I)Ljava/lang/Integer; #30 = Methodref #21.#31 // java/util/ArrayList.add:(Ljava/lang/Object;)Z #31 = NameAndType #32:#33 // add:(Ljava/lang/Object;)Z #32 = Utf8 add #33 = Utf8 (Ljava/lang/Object;)Z #34 = Utf8 ric #35 = Utf8 Ljava/util/ArrayList; #36 = Utf8 Ljava/util/ArrayList<Ljava/lang/Integer;>; #37 = Utf8 hi #38 = Utf8 ()Ljava/lang/Object; #39 = Utf8 ()TT; #40 = Fieldref #1.#41 // com/pinnet/test/Generic.name:Ljava/lang/Object; #41 = NameAndType #5:#6 // name:Ljava/lang/Object; #42 = Utf8 SourceFile #43 = Utf8 Generic.java #44 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object; { T name; descriptor: Ljava/lang/Object; flags: Signature: #8 // TT; public com.pinnet.test.Generic(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #12 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 5: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/pinnet/test/Generic; LocalVariableTypeTable: Start Length Slot Name Signature 0 5 0 this Lcom/pinnet/test/Generic<TT;>; public static void say(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=0 0: new #21 // class java/util/ArrayList 3: dup 4: invokespecial #23 // Method java/util/ArrayList."<init>":()V 7: astore_0 8: aload_0 9: bipush 12 11: invokestatic #24 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 14: invokevirtual #30 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 17: pop 18: return LineNumberTable: line 10: 0 line 11: 8 line 12: 18 LocalVariableTable: Start Length Slot Name Signature 8 11 0 ric Ljava/util/ArrayList; LocalVariableTypeTable: Start Length Slot Name Signature 8 11 0 ric Ljava/util/ArrayList<Ljava/lang/Integer;>; public T hi(); descriptor: ()Ljava/lang/Object; flags: ACC_PUBLIC Signature: #39 // ()TT; Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #40 // Field name:Ljava/lang/Object;划重点 4: areturn LineNumberTable: line 15: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/pinnet/test/Generic; LocalVariableTypeTable: Start Length Slot Name Signature 0 5 0 this Lcom/pinnet/test/Generic<TT;>; } SourceFile: "Generic.java" Signature: #44 // <T:Ljava/lang/Object;>Ljava/lang/Object; D:\eclipse\workspace\NorthAPI\bin\com\pinnet\test>
从反编译工具中可以看出,至少反编译工具是可以从class文件中找到你之前使用的泛型的,证明文件中是存在标记来记录这个泛型的,然后从javap中我们可以看出,虽然泛型不在代码中了,但是他还是记录在了注释中,这样就可以解释通为什么有的反编译工具可以反编译出泛型了。
泛型在编译期会被擦除的结论是没有问题的,在jvm中不存在泛型的概念。但是反编译工具通过注释中的记录找到了之前使用过的泛型类型,并在反编译时将其添加回来,所以我们所看到的反编译的文件中泛型存在,好像与泛型擦除这一概念冲突了。然而事实证明并没有,只是反编译工具太智能了而已。
上述结论节选自 : 关于java泛型擦除反编译后泛型会出现问题
4. 对于参数化泛型类型,其 getClass() 方法的返回值和原始类型的完全一样
ArrayList<String> a = new ArrayList<>(); ArrayList<Integer> b = new ArrayList<>(); System.out.println(a.getClass()==b.getClass()); System.out.println(a.getClass()==ArrayList.class); System.out.println(b.getClass()==ArrayList.class);
执行结果
5. 通配符
5.1 限定通配符?的上边界
ArrayList<? extends Integer> list;
集合中只能存放 Integer 及其子类实例
5.2 限定通配符?的下边界
ArrayList<? super Integer> list;
集合中只能存放 Integer 及其父类实例
6. 兼容性
原始类型与参数化类型互相引用
类型检查就是编译时完成的。new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。
而真正涉及类型检查的是它的引用,因为我们是使用它引用 list1 来调用它的方法,比如说调用add()方法。所以 list1 引用能完成泛型类型的检查。
为了保证代码的兼容性,下面的代码编译器(javac)允许,类型安全有你自己保证
// 原始类型引用指向参数化类型对象 可以添加任意类型的对象
// 静态类型为Object 实际类型为String List list = new ArrayList<String>(); // 编译通过 list.add(123); // 编译通过 list.add("123"); // 结果为 true 说明将元素自动转为 Object 类型 System.out.println(list.get(0) instanceof Object); // 参数化类型引用指向原始类型对象 只能添加指定类型对象
// 静态类型为String 实际类型为Object List <String>list2 = new ArrayList<>(); // 编译不通过 // list2.add(123); // 编译通过 list2.add("123"); // 结果为 true 说明将元素自动转为 String 类型 System.out.println(list2.get(0) instanceof String);
7. 泛型接口
- 不能用于全局常量
- 只能用于公共的抽象方法
- 实现类实现泛型接口时必须指定泛型方法
interface Human<T> { void create(T t); } class Man implements Human<Integer> { @Override public void create(Integer t) { } }
8. 泛型类
- 声明时使用泛型
- 运行时确定类型
- 泛型声明时不能使用在静态变量和静态方法上(编译期间就要确定)
class Info<T>{ T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public static void main(String[] args) { // 静态类型 实际类型 Info<String> info = new Info<String>(); info.getObj(); } }
9. 泛型方法
- 在访问权限修饰符和返回值中间加 <T> 声明方法为泛型方法
- 运行时确定参数类型
public <T> void doSomething(T obj) { System.out.println(obj); }