无边界通配符:<?>
无边界通配符的主要作用就是让泛型能够接受未知类型的数据。
public static void main(String[] args) {
List<String> list1=new ArrayList<>();
list1.add("aa");
list1.add("bb");
printList(list1);
System.out.println();
List<Integer> list2=new ArrayList<>();
list2.add(11);
list2.add(22);
printList(list2);
}
public static void printList(List<?> list){
for(Object o:list){
System.out.println(o);
}
}
这种使用List<?>的方式就是父类引用指向子类对象。注意这里的printList方法不能写成public static void printList<List<Object> list>的形式,虽然Object是所有类的父类,但是List<Object>跟其他泛型的List(如List<Integer>,List<String>)不存在继承关系,因此会报错。
不能对List<?>使用add方法,仅有一个列外,就是add(null)。原因是我们不确定该List的类型,不知道add什么类型的数据才对,只有null是所有引用数据类型都具有的元素。
public static void printList(List<?> list){
list.add(null);
list.add("cc");//error
}
List<?>也不能使用get方法,只有Object类型列外。原因是我们不知道传入的List是什么泛型,所以无法接受得到的get,但是Object是所有数据类型的父类。
有人说不是有类型强制转换吗?是有的,但是我们不知道传入的是什么类型,比如我们将其强转为String,编译是通过的,但是如果传入个Integer类型的,运行就会出错。有人说那么保证传入String类型的数据不就好了吗,那样是没有问题的,但是那还用<?>干嘛呢,直接用List<String>就可以了。
public static void printList(List<?> list){
String str=list.get(0);//error
Object obj = list.get(0);
}
固定上边界通配符:<? extends E>
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。 要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界。 注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类.。
public static void main(String[] args) {
List<Double> list1=new ArrayList<>();
list1.add(1.1);
list1.add(2.2);
printList(list1);
System.out.println();
List<Integer> list2=new ArrayList<>();
list2.add(11);
list2.add(22);
printList(list2);
}
public static double printList(List<? extends Number> list){
double s=0.0;
for(Number o:list){// 注意这里得到的n是其上边界类型的, 也就是Number, 需要将其转换为double
s+=o.doubleValue();
}
return s;
}
List<? extends E>不能使用add方法,除了null。
泛型<? extends E>指的是E及其子类, 这里传入的可能是Integer, 也可能是Double, 我们在写这个方法时不能确定传入的什么类型的数据,如果List<Integer>当入参调用下面方法,方法中的list.add(1.1)存的是double,类型不正确。
public static void addTest2(List<? extends Number> list){
list.add(1.1);//error
list.add(null);
}
get的时候是可以得到一个Number。 也就是上边界类型的数据的,因为不管存入什么数据类型都是Number的子类型,得到这些就是一个父类引用指向子类对象.
public static void addTest2(List<? extends Number> list){
Number number = list.get(0);
}
固定下边界通配符:<? super E>
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据。要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界。注意:你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
public static void main(String[] args) {
List<Object> list1 = new ArrayList<>();
addNumber(list1);
System.out.println();
List<Integer> list2 = new ArrayList<>();
addNumber(list2);
}
public static void addNumber(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
List<? super E>是能够调用add方法。
我们在addNumber所add的元素就是Integer类型的,而传入的list不管是什么,都一定是Integer或其父类泛型的List, 这时add一个Integer元素是没有任何疑问的。
public static void addNumber(List<? super Integer> list) {
list.add(1.1);//error
list.add(1);
}
不能使用get方法。
因为我们所传入的类都是Integer的类或其父类,所传入的数据类型可能是Integer到Object之间的任何类型, 这是无法预料的, 也就无法接收. 唯一能确定的就是Object, 因为所有类型都是其子类型。
public static void addNumber(List<? super Integer> list) {
Integer in=list.get(0);//error
Object object = list.get(0);
}
List<? super E>实现接口时需要注意
TreeSet(Comparator<? super E> comparator)
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Student extends Person {
public Student(String name, int age) {
super(name, age);
}
}
public class ComparatorTest implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
}
public class GenericTest {
public static void main(String[] args) {
TreeSet<Person> ts1 = new TreeSet<>(new ComparatorTest());
ts1.add(new Person("Tom", 20));
ts1.add(new Person("Jack", 25));
ts1.add(new Person("John", 22));
System.out.println(ts1);
TreeSet<Student> ts2 = new TreeSet<>(new ComparatorTest());
ts2.add(new Student("Susan", 23));
ts2.add(new Student("Rose", 27));
ts2.add(new Student("Jane", 19));
System.out.println(ts2);
}
}
为什么Comparator<Person>这里用的是父类Person,而不是子类Student。初学时很容易困惑, ? super E不应该E是子类才对么?其实,实现接口时我们所设定的类型参数不是E, 而是?; E是在创建TreeSet时设定的。
TreeSet<Person> ts1 = new TreeSet<>(new comparatorTest());
TreeSet<Student> ts2 = new TreeSet(new comparatorTest());
这里实例化的comparatorTest的泛型就是<Student super Student>和<Person super Student>。
如果改成class comparatorTest implements Comparator<Student>,那么上面的结果就成了: <Student super Person>和<Person super Person>, <Student super Person>显然是错误的。
总结
使用原装,有人将其称为PECS(即"Producer Extends, Consumer Super", 翻译为"生产者使用extends, 消费者使用super"。
- producer就是要你读取出数据以供随后使用,这时使用extends,可以将该对象当做一个只读对象。
- consumer就是要你将已有的数据写入对象,这时使用super,可以将该对象当做一个只能写入的对象。
- producer的数据能够使用Object类中的方法访问时,使用无边界通配符。
- 需要一个既能读又能写的对象时,就不用要通配符。
参数类型(T) 和 通配符(?)区别
- "T"是定义类或方法时声明的东西;
"?"是调用时传入的东西; - 有两种情况可以传入“?”
1、使用过程中仅用到Object的方法,跟T的具体类型无关,像equals()等等,因为任何一个泛型肯定是Object的子类;
2、使用过程中不依赖于泛型。最典型的是Class<?>,因为Class类的方法大多跟泛型无关。