刚学java那会对泛型总是觉得有点云里雾里,工作一段时间后,再回顾泛型算是能搞明白了。
1.泛型的种类
extends E> - 为E及其子类型
super E> - 为E及其父类型
> - 为任何类型
2.简单的使用说明
针对最常用的集合来讲。假设有一颗继承树 Object <- Food <- Apple & Cookie
method1(List
method2(List extends Food> list)
method3(List super Foods> list)
method4(List> list)
2.1
method1(List
List foods = new ArrayList<>();
method4(foods);
Food food = foods.get(0);
如果method4把一个Object元素存入foods,那么在get时就会出现强转异常。
2.5
可以这样理解,原生态类型(List)是为了兼容旧的代码作出的保留,在新的代码中,原生态类型应该被禁用。不允许使用原生态类型,而参数化类型(List
之所以要使用泛型,更多的是保证代码的健壮性。即保证编写的方法在任何情况下不会出现非预期的异常,以及将更多的异常发现在早期(编译期)。例如你是用 extends E>设计一个方法时,能保证调用的人传入的参数是符合预期的,而实现方法的人也知道不能在这个list中存放元素。如果用原生态类型,那就需要复杂的校验,多余的注解来告诉调用和编写代码的人,并且这多数情况下还是低效和没用的。
这有点像Kotlin的NullPointException处理,增加了代码的编写规则,但让异常能暴露在早期。
3.泛型和数组
【泛型不能new数组】记住这条就好,除了>,new ArrayList>[]可以,但是这种不能正常set、get所以没什么意义。
数组是协变得,而泛型不是协变得。意味着Object[]是Food[]的父类,而List
E[] es = new E[1];
Object[] os = es;
os[0] = "error";
E e = es[0];
泛型设计用来避免强转异常,出现这种操作显然是违背设计初衷的,所以就直接禁止了,以保证泛型的安全性。但是泛型数组变量是可以声明的:
E[] es = (E[]) (new Object[2])
但这会有warning,最好还是用集合代替数组。有一种隐藏的情况是使用可变参数方法,因为可变参数底层也是创建一个数组来接收参数,所以不能在可变参数方法中使用泛型。
4.泛型和Class
4.1
泛型不能使用.class,不能使用instanceof(>例外,等同于原生类型,所以也没什么必要)。
String[].class √
List.class √
List
List>.class ×
List
T.class ×
4.2
参数化的类型不能用于强转,而形式参数类型可以用于强转,但是会有warning。
(List
(List
(T) myObj √
4.3
通过反射获得子类指定的泛型。
public class HelloS {
public static void main(String[] args) {
HelloS.Person person = new HelloS().getPerson();
person.sayType();
}
public Person getPerson() {
Person person = new Person();
return person;
}
class Base {
public void sayType() {
Class c = this.getClass(); //这里是Person.class
Type t = c.getGenericSuperclass();
if (t instanceof ParameterizedType) {
Type[] p = ((ParameterizedType) t).getActualTypeArguments();
Class typeClass = (Class) p[0]; //因为只有一个泛型,所以是0
System.out.println(typeClass.getName());
}
}
}
class Person extends Base {
}
}
注意这里在Person类实现的时候就指定了Base
4.4
Class#asSubclass(List.class)
将某个class对象的泛型类型转换成另一种泛型类型的 extends T>形式。如果转换失败则报cast异常。
List list = new ArrayList<>();
Class listclass = list.getClass();
Class extends ArrayList> arrlistclass = listclass.asSubclass(ArrayList.class);
5.泛型和类
见4.3
6.泛型和方法
6.1 泛型声明位于方法的修饰符和返回值类型之间
public static void method1(List list);
6.2 通配符类型不能作为方法声明泛型
public static E method1(List list); //正确
public static void method2(List list); //正确
public static > void method3(List> list); //错误
6.3 类型推导与显示指定
方法的类型参数很多时候都通过类型推导直接获得,但是在类型推导不好用的时候,也可以通过显示指定的方式指定方法的泛型。
public static E method1();
String result = method1(); //这里调用method1()的时候,并没有指定method1()的,而是通过前面的引用推导出来为。
特殊情况:
public static Set union(Set extends E> s1, Set extends E> s2);
Set numbers = union(new HashSet(), new HashSet()); //报错
Set numbers = union(~);//正确
6.4 PECS设计准则
【如果参数化类型表示一个T的生产者,就使用 extend T>; 如果参数化类型表示一个T的消费者,就使用 super T>; 如果既是生产者又是消费者,就使用
例如List:
method(List extend T> supplier, Funcation func);
method(List super T> consumer, Funcation func);
生产者要提供T,List super T> 只能获得Object类型元素;
消费者要接收T,List extend T> 只能存放null。
这里仅是List,PECS适用于任何消费者/生产者的泛型设计。