4、泛型-通配符

无边界通配符:<?>

无边界通配符的主要作用就是让泛型能够接受未知类型的数据。

    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类的方法大多跟泛型无关。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值