刚学java那会对泛型总是觉得有点云里雾里,工作一段时间后,再回顾泛型算是能搞明白了。
1.泛型的种类
<E> 类型泛型 -为某个具体类型E
<? extends E> - 为E及其子类型
<? super E> - 为E及其父类型
<?> - 为任何类型
2.简单的使用说明
针对最常用的集合来讲。假设有一颗继承树 Object <- Food <- Apple & Cookie
method1(List<Food> list)
method2(List<? extends Food> list)
method3(List<? super Foods> list)
method4(List<?> list)
2.1
method1(List<Food> list)只能接收List<Food>,不能接受List<Apple>,也不能接受List<Object>,list参数在方法体中可以get/set Food类型元素。
这很好理解,在编写method1的代码时,使用的list中都是Food类型元素,list.add(aCookie)是合法的,如果能传入一个List<Apple>,那么代码将可以把Cookie放入这个Apple List。如果能传入一个List<Object>, 那么对list中的元素调用Food的方法肯定是错误的。
2.2
method2(List<? extends Food> list)能接受List<Food>、List<Apple>,不能接受List<Object>,list参数在方法体中能get Food类型元素,不能set任何元素,除了null。
list不能set这是因为method2能接收Food及其子类集合,并且在代码块中,list被当作List<Food>使用(这是因为在写代码的时候并不知道传入的到底是List<Apple>还是List<Cookie>)。如果传入的参数是List<Apple>,那么向其中存放Cookie显然是错误的。
2.3
method3(List<? super Food> list)能接收List<Food>、List<Object>,不能接受List<Apple>,list参数在方法体中能set Food及其子类和null,不能set 父类元素,get只能返回Object类型。
在方法调用之前是不知道会传入List<Food>还是List<Object>,如果传入的是List<Food>,那么向其中存放Apple、Cookie、Food显然都是可以的,但存放Object却是不行的。同样,如果传入的是List<Object>,那么就只能取出Object元素。为了保证运行期间的安全,泛型在编译期做了很多限制,取两种情况下安全性的交集,以保证运行的绝对安全。
2.4
method4(List<?> list)能接受所有泛型集合,但不能set任何非null元素,只能get Object类型对象。
这个也很好理解,在编写方法的时候(编译期)并不知道传入的集合泛型,自然不能存放元素。例如:
List<Food> foods = new ArrayList<>();
method4(foods);
Food food = foods.get(0);
如果method4把一个Object元素存入foods,那么在get时就会出现强转异常。
2.5
可以这样理解,原生态类型(List)是为了兼容旧的代码作出的保留,在新的代码中,原生态类型应该被禁用。不允许使用原生态类型,而参数化类型(List<Food>)限制又很多,就需要通配符?来应对不同的使用情况。例如计算List的size,ListUtil.size(list),要能接收所有list对象,这时候就使用List<?>。
之所以要使用泛型,更多的是保证代码的健壮性。即保证编写的方法在任何情况下不会出现非预期的异常,以及将更多的异常发现在早期(编译期)。例如你是用<? extends E>设计一个方法时,能保证调用的人传入的参数是符合预期的,而实现方法的人也知道不能在这个list中存放元素。如果用原生态类型,那就需要复杂的校验,多余的注解来告诉调用和编写代码的人,并且这多数情况下还是低效和没用的。
这有点像Kotlin的NullPointException处理,增加了代码的编写规则,但让异常能暴露在早期。
3.泛型和数组
【泛型不能new数组】记住这条就好,除了<?>,new ArrayList<?>[]可以,但是这种不能正常set、get所以没什么意义。
数组是协变得,而泛型不是协变得。意味着Object[]是Food[]的父类,而List<Object>则不是List<Food>的父类。数组存在这种情况:
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<E>.class ×
List<?>.class ×
List<String>.class ×
T.class ×
4.2
参数化的类型不能用于强转,而形式参数类型可以用于强转,但是会有warning。
(List<String>) myList ×
(List<T>) myList √
(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<T> {
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<String> {
}
}
注意这里在Person类实现的时候就指定了Base<String>,所以能获得。而ArrayList那种,在运行时新建的时候才指定的类型参数不能这样获得new ArrayList<String>()。因为泛型在运行时已经擦除了。这里获得的泛型,是因为Person这个类的文件中有extends Base<String>的信息,所以才能通过Person的Class对象获得。
4.4
Class#asSubclass(List.class)
将某个class对象的泛型类型转换成另一种泛型类型的<? extends T>形式。如果转换失败则报cast异常。
List<String> list = new ArrayList<>();
Class listclass = list.getClass();
Class<? extends ArrayList> arrlistclass = listclass.asSubclass(ArrayList.class);
5.泛型和类
见4.3
6.泛型和方法
6.1 泛型声明位于方法的修饰符和返回值类型之间
public static <E> void method1(List<E> list);
6.2 通配符类型不能作为方法声明泛型
public static <E> E method1(List<E> list); //正确
public static <E> void method2(List<E> list); //正确
public static <?> void method3(List<?> list); //错误
6.3 类型推导与显示指定
方法的类型参数很多时候都通过类型推导直接获得,但是在类型推导不好用的时候,也可以通过显示指定的方式指定方法的泛型。
public static <E> E method1();
String result = method1(); //这里调用method1()的时候,并没有指定method1()的<E>,而是通过前面的引用推导出来<E>为<String>。
特殊情况:
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2);
Set<Number> numbers = union(new HashSet<Integer>(), new HashSet<Long>()); //报错
Set<Number> numbers = <Number>union(~);//正确
6.4 PECS设计准则
【如果参数化类型表示一个T的生产者,就使用<? extend T>; 如果参数化类型表示一个T的消费者,就使用<? super T>; 如果既是生产者又是消费者,就使用<T>】。
例如List:
method(List<? extend T> supplier, Funcation<T> func);
method(List<? super T> consumer, Funcation<T> func);
生产者要提供T,List<? super T> 只能获得Object类型元素;
消费者要接收T,List<? extend T> 只能存放null。
这里仅是List,PECS适用于任何消费者/生产者的泛型设计。
网友评论