泛型基本原理
略,只需注意 Java 泛型是编译擦除机制,只在编译时有效,运行时无效。
泛型标记的区分
完全个人总结,征求来源认证
泛型说到本质,是模板替换,需遵循模板替换的基本原则,需要涉及到下述三种标记。
泛型参数(模板变量)
标记替换什么,即替换内容的含义是什么。这是额外的独立定义,在 Javadoc 会有单独位置,只有类和方法旁边才能定义泛型参数。
举例:
public interface List<E></font> { … } // List 后面的<E>
<T> T[] toArray(T[] a) {} // 最前面的<T>
注意点: 这是个参数声明,因此请勿使用“ ?”。
泛型(模板变量的引用)
标记在哪里替换。这是定义要替换的位置,因此它必定是替换掉之前的位置。类当中的类型声明,例如方法的形参类型,方法返回类型,可被替换为泛型。变量的类型声明,不属于类,故不参与泛型,而是参与后文所述的定型。
举例:
public interface List<E> { boolean add(E e); } // add 方法的参数类型 E
<T> T[] toArray(T[] a) {} // 参数类型 T , 还有 返回类型 T[]
注意点:
Class<T> { List<T> getList(List<T> paramList); },两个List<T>中这个 T , 在 Class 类中是泛型 ,但同时,也是对List<E>的 泛型参数 E 的定型。
定型(模板变量的值)
标记替换成什么。_模板变量的值可能无需刻意定义,只要能根据上下文推断出来即可_
举例:
List<String> = new ArrayList<String>() // 显式定义
Optional.of("123").orElseGet(()->"sss"); // 上下文自动推断
List<String> = new ArrayList<>(); // 显式定义+上下文自动推断
List<? extends B> bList= new ArrayList<A>; // 向上造型通配符
List<? super A> bList = new AyyarList<B>; // 向下造型通配符
关于造型通配符,见后文
extends/super 特别说明
__ <? extends SomeObject>,跟 <T extend SomeObject>,是两码事: __
-
<T extends SomeObject>是泛型参数,它将 T 的可选范围,从 Object 缩小 为 SomeObject 。- 泛型参数,跟常规参数(变量)一样,可以限定为类及其子类,不可以限定为类及其父类,故不存在
<T super SomeObject>
- 泛型参数,跟常规参数(变量)一样,可以限定为类及其子类,不可以限定为类及其父类,故不存在
-
<? extends/super SomeObject>是通配符定型,它以 SomeObject 的向上 /向下造型通配,作为定型。- 如果是类的方法的参数类型声明,更多遇到的是
<? extends/super T>。
- 如果是类的方法的参数类型声明,更多遇到的是
关于造型通配符,见后文
造型通配符
特别说明: Java 泛型是 1.5 加入,并且加入之后再没有动过,这里的笔记也是 Java 6 年代总结的,那时候 C# 泛型可能还没出或者出来了但没流行。所以这里没有 PECS ,没有斜变逆变,这俩理论可以作为外部参考,但是它们不太可能是 Java 泛型的基础原理。
由于类型参数与类型声明无关,因此类型参数的继承关系不能区分泛型实例的继承关系。例如List<Integer>与List<Object>之间没有继承关系,他们都是 List 接口的实例并且由于加入了类型参数限定二者之间也不能做强制类型转换。而自然情况以及面向对象的多态型又是需要该区分的即List<Integer>是List<Object>的子类,此时需要使用“? extends”、“? super”通配符,但是注意这只是一种变通手段,并且是不完善的。毕竟泛型是 编译时 的特征,而多态是 运行时 的特征。
提示: 下文均假设 A extends B 。
向上造型通配符
** 可以使用其“方法返回类型限定”的方法(例如 get 方法),不能使用其“方法参数类型限定”的方法(例如 add 、set 方法)。 **
举例:
List< ? extends B> bList= new AyyarList<A>;
B b = bList.get(0); // 正确
bList.add(new A()); // 编译错误
分析:
- 对于“方法返回类型限定”的方法,多态性要求方法返回父类 (即 B )。实际使用中,满足
<? extends B>可自动 向上造型 到 B ,符合要求。允许实际使用此方式。 - 对于“方法参数类型限定”的方法,多态性要求方法参数类型是具体的子类型(即 A ,由变量的实际类型,即上面语句的等号右边决定)。实际使用中,
<? extends B>完全无法限定具体类型是 A ,故若使用“方法参数类型限定”的方法,则强制编译不通过。不允许实际使用此方式。
向下造型通配符
可以使用其“方法参数类型限定”的方法(例如 add 、set 方法),不过参数类型应当是类型参数或其子类型(而不是父类);仍然可以使用其“方法返回类型限定”的方法(例如 get 方法),但只能得到 Object 类型的返回类型。
举例:
List<? super A> bList = new AyyarList<B>;
bList.add(new A()); // 正确
bList.add(new B()); // 编译警告,但不是编译错误,
Object obj = bList.get(0); // 可用,但是没有实际意义
分析:
- 注意看定义,是
List bList = new ArrayList<B>,仍然是多态,但不是常规多态,故没有向上造型通配符用得多。 - 对于“方法参数类型限定”的方法,
<? super A>额外要求参数类型是 A (即虽然是 bList ,但额外要求参数是它的 向下造型 A )。实际使用中,如果传入参数类型是 A ,符合要求。如果是 B ,则有可能符合要求(实例正好是 A 类型),也有可能是不符合要求,但又没错(毕竟实际类型,即上面语句中等号的右边,是List<B>),故此时会给编译警告。允许实际使用此方式,但应当先向下造型再给参数。 - 对于“方法返回类型限定”的方法,多态性要求方法返回父类,而
<? super A>的父类只能推断成最高超类 Object 。允许但不推荐使用此方式。
造型通配使用原则
- 如果你想从一个数据类型里获取数据,使用
? extends通配符 - 如果你想把对象写入一个数据结构里,使用
? super通配符 - **如果你既想存,又想取,那就别用通配符。 **