最近终于开始总结自己理解的东西,终于有时间写写自己对一些知识的认识。文笔不好,还望各位包含。
本预先资料来源于Oracle官方文档Java™ 教程-Java Tutorials
官方文档:https://docs.oracle.com/javase/tutorial/java/generics/index.html
中文翻译:https://pingfangx.github.io/java-tutorials/java/generics/types.html
下面我以问题的方式,阐明对泛型深入浅出的认识:
1.泛型是什么? 泛型是JDK5引入的一种参数化类型 特性。JDK7及以上,泛型的菱形可以推断参数化类型:把类型当参数一样传递。数据类型只能是引用类型。
举个栗子:Plate中的T是类型参数;Plate的Banana是实际类型参数;
Plate整个称为泛型类型;Plate整个称为参数化的类型;
2.为什么使用泛型,使用泛型的好处?
1.代码更健壮(只要在编译器没有警告,运行期就不会出现ClassCastException);
2.代码更简洁(不用强转)
3.代码更灵活,可复用。
3.泛型包含哪些? 泛型只有三种情况:泛型接口,泛型类,泛型方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public interface Plate <T > { public void set (T t) ; public T get () ; } public class AiPlate <T > { private T t; public AiPlate (T t) { this .t = t; } public void set (T t) { this .t = t; } public T get () { return t; } public <U> void addFruit (U u) { } }
4.泛型限定有哪些? ?无限定 ; extends 限定上界;super 限定下界 后面会以一个通熟易懂的口诀演示记住他
1 2 3 4 5 6 7 8 9 10 11 Class A {} Class B {} Interface C{} Interface D{} public class ClassTest <T extends A & C & D > {} public class ClassTest <T extends A & B & C & D > //error , 因为java 只支持单继承
在这个体系中,上界通配符 “Plate<? extends Fruit>” 覆盖下图中蓝色的区域。
Plate是Plate的基类,但是不是Plate的基类,对应上面例子,Plate覆盖的下图中红色的区域
5. JAVA泛型的原理?什么是泛型擦除机制? Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种伪泛型机制 ,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。
泛型的擦除机制:如果有父类被擦除成父类,否则被擦除成Object 。如果有继承,擦除之后会生成桥接方法 ,来解决类型擦除后保留泛型类型的多态性;桥方法在调用父类方法前,会将object进行强转成父类。
从 .java文件编译到.class文件会产生泛型擦除的残留,保留了定义的格式,方便分析字节码。类的常量池保留了泛型信息,可以通过反射API拿出type类型信息。
6. Java编译器具体是如何擦除泛型的?
step 1. 检查泛型类型,获取目标类型
step 2. 擦除类型变量,并替换为限定类型
如果泛型类型的类型变量没有限定(),则用Object作为原始类型
如果有限定(),则用XClass作为原始类型
如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
step 3. 在必要时插入类型转换以保持类型安全
step 4. 生成桥方法以在扩展时保持多态性
7.使用了泛型,后留下很多“后遗症” 7.1 泛型类型变量不能使用基本数据类型 比如没有ArrayList,只有ArrayList.当类型擦除后,ArrayList的原始类中的类型变量(T)替换成Object,但Object类型不能存放int值
7.2 不能使用instanceof 运算符 因为擦除后,ArrayList只剩下原始类型,泛型信息String不存在了,所有没法使用instanceof。
1 2 3 4 5 ArrayList<String> strings = new ArrayList<>(); if (strings instanceof ArrayList<?>){} if (strings instanceof ArrayList<String>)
7.3 泛型在静态方法和静态类中的问题 因为泛型类中的泛型参数的实例化在定义泛型类型对象(比如ArrayList)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,编译器不知道如何确定这个泛型参数是什么。
7.4 泛型类型中的方法可能会冲突 因为擦除后两个equals方法变成一样的了。擦除之后T变成Object,而Object中的equals是默认被实现了的。所以重复了。
1 2 3 4 5 6 7 public boolean equals (T t) { return super equals (t) } public boolean equals (Object obj) { return super equals (obj) }
7.5 没法创建泛型实例 因为类型不确定,没法直接new对象。
1 2 3 public static <E> void append (List<E> list) { E elem = new E(); }
但是下面这种方式是可以的,通过反射来new对象;
1 2 3 4 public static <E> void append (List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); list.add(elem); }
7.6 没有泛型数组 答:因为数组可以协变,擦除后就没法满足数组协变的原则。
T[] arr = new T[10] //不可以 不知道T代表那个类
另外:Apple extends Fruit
Apple[] 的父类是Fruit[] 这个叫数组的协变。
其中数组协变中:父类可以持有子类,子类持有父类可能会报转化异常。
List 和 List在擦除之后运行期不知道是什么类型了,都是不满足协变原则了;数组是可以协变的,但是list是不会协变的。
另外假如:A extends B
Plate = Plate是不允许的,因为在泛型中,不管AB是什么关系,Plate 和Plate没有任何关系。
8. 通配符和泛型的关系? 通配符可以让泛型转化更灵活。
9. 说出以下类型的区别? 1 2 3 4 5 6 Plate 普通的盘子类,编译时不会做类型检查 Plate<Object> 参数化的plate类,他在编译之后,字节码的类中的方法是Object Plate<?> 非限定通配符下的泛型类,泛型?表示类型未知,等价于Plate<? extends Object> Plate<T> 泛型Plate类,T是未知类型,编译后会被擦除成Object对象 Plate<? extends T> 限定上界的泛型类 Plate<? super T> 限定下界的泛型类
10.泛型边界的熟记口诀:
上界只可往上读,往上赋值;方法只能传递自己或者子类
下界只可向下写,向下赋值;方法只能传递自己或者父类
验证口诀的正确性: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import java.util.ArrayList;import java.util.List;public class TestBoundary { public static void main (String[] args) { List<Food> foods = new ArrayList<>(); List<Apple> apples = new ArrayList<>(); List<Fruit> fruits = new ArrayList<>(); List<? extends Fruit> fruits1 = apples; Fruit fruit = fruits1.get(0 ); Food food = fruits1.get(0 ); Object object = fruits1.get(0 ); eat1(fruits); eat1(apples); List<Food> foodsLower = new ArrayList<>(); List<Apple> applesLower = new ArrayList<>(); List<Fruit> fruitsLower = new ArrayList<>(); List<? super Fruit> fruitList = foodsLower; fruitList.add(new Apple(1 )); eat2(fruitsLower); eat2(foodsLower); } public static <T> void eat1 (List<? extends Fruit> fruits) { } public static <T> void eat2 (List<? super Fruit> fruits) { } }
11.最后说了半天规则,用实例演示以下泛型有什么用处 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import java.util.ArrayList;import java.util.Collections;import java.util.List;public class Test { public static void copy1 (List<Apple> dest,List<Apple> src) { Collections.copy(dest,src); } public static <T> void copy2 (List<T> dest,List<T> src) { Collections.copy(dest,src); } public static <T> void copy3 (List<? super T> dest,List<T> src) { Collections.copy(dest,src); } public static <T> void copy4 (List<? super T> dest,List<? extends T> src) { Collections.copy(dest,src); } public static void main (String[] args) { List<Apple> apples = new ArrayList<>(); apples.add(new Apple(1 )); List<Apple> apples1 = new ArrayList<>(); apples1.add(new Apple(2 )); List<Banana> bananas = new ArrayList<>(); bananas.add(new Banana(1 )); List<Banana> bananas1 = new ArrayList<>(); bananas1.add(new Banana(2 )); List<Fruit> fruits = new ArrayList<>(); fruits.add(new Apple(10 )) ; copy1(apples,apples1); copy2(bananas,bananas1); Test.<Apple>copy3(fruits,apples); Test.<Fruit>copy4(fruits,apples); } }
测试代码: https://github.com/oujie123/UnderstandingOfGeneric