在看Java核心技术卷I中泛型通配符相关的知识时产生了很多疑问,通过了解后简单的做下笔记方便回顾。
本文重点记录了一下这几个问题:
- 为什么要用泛型通配符?
? extends T
为什么只能调用使用了泛型取出对象的成员方法,不能调用使用泛型设置对象的成员方法?? super T
为什么可以调用使用泛型设置对象的成员方法,调用使用了泛型取出对象的成员方法会返回Object对象?- 使用了泛型的成员方法和泛型方法有什么区别?
package com.ww.generic.csdn;
import java.util.List;
/**
* 职工类
*
* @author: Sun
* @create: 2020-03-04 16:55
* @version: v1.0
*/
public class Employee {
}
/**
* 管理者类
*/
class Manager extends Employee {
}
/**
* 经理类
*/
class Executive extends Manager {
}
class TestGeneric<T> {
T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
// 使用了泛型的成员方法
public void printParam(TestGeneric<T> p) {
System.out.println(p);
}
// 使用了泛型通配符的成员方法
public void printParamUseWildcard(TestGeneric<? extends T> p) {
System.out.println(p);
}
// 泛型方法
public <E> E genericPrintParam(TestGeneric<E> param) {
return param.getValue();
}
public static void main(String[] args) {
// ----------------为什么要使用泛型通配符?---------------- //
/**
* 首先看下面这个例子,因为TestGeneric类中声明的泛型类型为Employee,所以在调用其printParam()方法时,只能传递new TestGeneric<Employee>()类型的对象,
* 无法传递new TestGeneric<Manager>()这个看起来是new TestGeneric<Employee>()类的子类对象。
*/
TestGeneric<Employee> employeeTestGeneric = new TestGeneric<>();
// 调用没有使用通配符的方法
employeeTestGeneric.printParam(new TestGeneric<Employee>());
// employeeTestGeneric.printParam(new TestGeneric<Manager>()); compile error
// 调用使用了通配符的方法
employeeTestGeneric.printParamUseWildcard(new TestGeneric<Employee>());
employeeTestGeneric.printParamUseWildcard(new TestGeneric<Manager>());
// employeeTestGeneric.printParamUseWildcard(new TestGeneric<Object>()); compile error
// ----------------为什么使用? extends T只能get获取对象,不能set对象了?---------------- //
/**
* `? extends T`
* 描述一个这样的对象/容器对象,它/它内部的对象起码是个T。
* 因此我们可以get(),无论容器中是T的哪个子类或者T本身,我们都可以当作T来操作.
* 这也就是生产者Extends,它可以对外提供对象。
* 但你不知道T实际是什么,是哪个子类。所以你的任何set操作,或者容器的add操作都不被java允许。否则就可能出现一个装香蕉的水果篮子里出现了苹果的情况:
* TestGeneric<? extends Fruit> t3 = new TestGeneric<Banana>();
* t3.setValue(new Apple())
*/
// TestGeneric<? extends Manager> t1 = new TestGeneric<Object>(); compile error
// TestGeneric<? extends Manager> t2 = new TestGeneric<Employee>(); compile error
TestGeneric<? extends Manager> t3 = new TestGeneric<Manager>();
TestGeneric<? extends Manager> t4 = new TestGeneric<Executive>();
// `? extends T` 可以向外提供对象
Manager t3Value = t3.getValue();
Manager t4Value = t4.getValue();
// compile error t4跟t3一样
// t3.setValue(new Employee());
// t3.setValue(new Manager());
// t3.setValue(new Executive());
// t3.setValue(new Object());
// ----------------为什么使用? super T可以使用set方法,但使用get方法时只会得到Object对象而不是具体类型的对象?---------------- //
/**
* `? super T`
* 或许你以为按照对称性,? super T描述的应该是一个什么都可以放的对象/容器,只要是T的父类。包括Object。以下语句也确实不会报错.
*/
// TestGeneric<? super Manager> t5 = new TestGeneric<Executive>(); compile error
TestGeneric<? super Manager> t6 = new TestGeneric<Manager>();
TestGeneric<? super Manager> t7 = new TestGeneric<Employee>();
TestGeneric<? super Manager> t8 = new TestGeneric<Object>();
/**
* 但是你会失望的发现.在执行set操作/容器的add操作的时候。t7、t8跟t6一样
* 你只能set/add Manager和Manager的子类。
* 这是因为:
* `? super T` 确实描述了一个T的父类对象/父类容器,但不知道是哪个父类,所以你只能set T/T的子类. 因为所有T的子类都可以转化为任何一个T的父类。
* set/add 操作是绝对安全的。这也就是消费者super,可以接受外部传递过来的对象。
*/
t6.setValue(new Manager());
t6.setValue(new Executive());
// t6.setValue(new Object()); // compile error
// t6.setValue(new Employee()); // compile error
/**
* 但由于你不清楚实际是那个父类,所以你只能get到一个Object。
* 这是合法的,但是没有什么意义。Object对象除了一个引用,无法进行什么操作。
*/
Object o = t6.getValue();
// ----------------使用了泛型的成员方法和泛型方法有什么区别?---------------- //
/**
*
* 1、使用了泛型的成员方法如果不使用通配符:`? extends T`,在调用该成员方法时只能传入T类对象,例如:TestGeneric<Manager>不是TestGeneric<Employee>的子类
* 而泛型方法是在使用的时候才将泛型类型作为参数传递到调用方法的泛型形参中去的,所以没有这样的问题。
*
* 2、类中没有声明泛型时,方法中自然不能使用泛型。这时如果还想限制方法中传递的参数的类型,就只能使用泛型方法了。
*/
employeeTestGeneric.genericPrintParam(new TestGeneric<Employee>());
employeeTestGeneric.genericPrintParam(new TestGeneric<Manager>());
employeeTestGeneric.genericPrintParam(new TestGeneric<Object>());
}
}
PECS原则
Producer Extends Consumer Super。带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
- 频繁往外读取内容的,适合用上界Extends。
- 经常往里插入的,适合用下界Super。