[java泛型] 通配符wildcards

来源:互联网 发布:聊天交友软件排行 编辑:程序博客网 时间:2024/05/13 07:28

前言

最近被反射获取泛型搞的有点晕,所以总结一下泛型中的通配符

测试类

我们先定义几个测试类
这里写图片描述

/** * 动物类 */public class Animal {    private String name;    public Animal() {}    public Animal(String name) {        this.name = name;    }    public void eat() {        System.out.println(getName() + "can eat");    }    public String getName() { return name;}    public void setName(String name) {        this.name = name;    }}/** * 猫 继承 动物 */class Cat extends Animal {    public Cat(String name) {        super(name);    }    public void jump() {        System.out.println(getName() + "can jump");    }}/** * 鸟 继承 动物 */class Bird extends Animal {    public Bird(String name) {        super(name);    }    public void fly() { System.out.println(getName() + "can fly.");}}/** * 喜鹊 继承 鸟 */class Magpie extends Bird {    public Magpie(String name) {        super(name);    }    public void sing() { System.out.println(getName() + "can sing.");}}

多态 new List<Animal>().add();

class AnimalTest {    public static void main(String[] args) {        List<Animal> list = new ArrayList<>();        list.add(new Animal());        list.add(new Bird("bage"));        list.add(new Cat("dengyi"));        //在为java的多态机制        List<Animal> list2 = new ArrayList<>();        Animal animal = new Animal();        Animal bage = new Bird("bage");        Animal cat = new Cat("cat");        list2.add(animal);        list2.add(bage);        list2.add(cat);        act(list); //可以传入list参数        act(list2); //可以传入list2参数        List<Bird> birdList = new ArrayList<>();        birdList.add(new Bird("dengyi"));        birdList.add(new Magpie("jianglei"));        act(birdList);   //无法通过编译 因为List<Bird> 不是 List<Animal>的子类型    }    public static void act(List<Animal> animalList) {        for (Animal animal : animalList) {            animal.eat();        }    }}

通配符的上界

即然知道List<Bird>并不是List<Animal>的子类型,那就需要寻找其他解决的办法,使act()方法更加通用(好可以接受List<Bird>也可以接受List<Animal>)。在java里解决办法就是使用通配符?,具体到act()方法就是将方法改为act(List<? extends Animal>),当中”?”就是通配符,而“? extends Animal”则表示通配符”?”的上界为Animal,换句话说是,”? extends Animal”可以代表Animal或其子类,但不代表Animal的父类(如Object),因为通配符的上界是Animal。如下,为改进这后的代码:

class AnimalTest {    public static void main(String[] args) {        List<Animal> list = new ArrayList<>();        list.add(new Animal());        list.add(new Bird("bage"));        list.add(new Cat("dengyi"));        act(list);        List<Bird> birdList = new ArrayList<>();        birdList.add(new Bird("dengyi"));        birdList.add(new Magpie("jianglei"));        act(birdList);    }    public static void act(List<? extends Animal> animalList) {        for (Animal animal : animalList) {            animal.eat();        }    }}

这里写图片描述

也就是说:

public void cast() {    List<Bird> birdList = new ArrayList<>();    List<? extends Bird> birds = birdList;    List<? extends Animal> animalList = birds;    List<? extends Object> objectList = animalList;    List<?> list = objectList;    List l = list;}

学到这里,可能会遇到一些疑惑的地方,或者说是理解不透的地方,先观察如下两段代码,判断一下其是否可行?

public void testAdd(List<? extends Animal> list){    list.add(new Animal("animal"));    list.add(new Bird("bird"));    list.add(new Cat("cat"));}

先分析如下:因为“? extends Animal”可代表Animal或其子类(Bird, Cat),那上面的操作应该是可行的。事实上是“不行”,即无法通过编译。为什么呢??

在解释之前,再来重新强调一下已经知道的规则:在List<Animal>里只能添加Animal类对象及其子类对象(如Cat和Bird对象),在List<Bird>里只能添加Bird类和其子类对象(如Magpie),要不能添加Animal对象(不是Bird的子类),类似的在List<Cat>和List<Magpie>里只能添加Cat和Bird对象(或其子类对象,不过这没有列出)。现在再回头看一下上述代码,我们知道List<Animal>List<Cat>等都是List<? extends Animal>的子类型。先假设传入的参数为List<Animal>,则第一段代码的三个“add“操作都是可行的;可如果是List<Bird>呢?则只有第二个“add”可以执行;再假设传入的是List<Tiger>,则三个”add”操作都不能执行。

再在反过来说,给testAdd传入不同的参数,三个”add”操作都可能引发类型不兼容问题,而传入的参数是未知的,所以java了保护其类型一致,禁止向List<? extends Animal>添加任意对象,不过去可以添加null,即list.add(null)是可以的。有了上面谈到的基础,我们可以总结一下不能添加的原因了

因为List<? extends Animal> 的类型 “? extends Animal”无法确定,可以是Animal, Bird或者Cat等,所以为了保护其类型的一致性,也是不能往list添加任意对象的,不过却可以添加null

另外提醒大家注意的一点是,在List<? extends Animal>可以是Animal类对象或Bird对象等(只是某一类对象),反过来说,在List<? extends Animal> list里的都是Animal对象,即Bird也是Animal对象,Cat也是Animal对象(用java的语言来说就是子类可以指向父类,父类却不能指向子类),那么在Animal里的所有方法都是可以调用的,如下:

for(Animal animal : list){    animal.eat();}

通配符的下界

即然有了通配符的上界,自然有通配符的下界。可以如此定义通配符的下界List<? super Bird>,其中”Bird”就是通配符的下界。注意:泛型不能同时声明上界和下界
规律:

  • List<? super Animal> 是 <? super Bird> 的子类型
  • List<Animal>是List< super Animal>的子类型

现在再来看如下代码,判断其是否符合逻辑

public void testAdd(List<? super Bird> list){    list.add(new Bird("bird"));    list.add(new Magpie("magpie"));}

分析:因为”? super Bird”代表了Bird或其父类,而Magpie是Bird的子类,所以上述代码不可通过编译。而事实上是”行“,为什么呢???
在解疑之前,再来强调一个知识点,子类可以指向父类,即Bird也是Animal对象。现在考虑传入到testAdd()的所有可能的参数,可以是List<Bird>,List<Animal>或者List<Object>等等,发现这些参数的类型都是Bird或其父类,那我们可以这样看,把bird,magpie看成Bird对象,也可以将bird, magpie看成Animal对象,类似的可看成Object对象,最后发现这些添加到List< super Bird>里的对象都是同一类对象,因此testAdd方法是符合逻辑的,可以通过编译的。
再来看看如下代码:

List<? super Bird> list = new ArrayList();list.add(new Bird("bird"));list.add(new Magpie("magpie"));list.add(new Animal("animal"));

第二行和第三行的解释和上文一样,至于最后一行list.add(new Animal("animal"));是无法通过编译的,为什么呢??为了保护类型的一致性,因为”? super Bird”可以是Animal,也可以是Object或其他Bird的父类,因无法确定其类型,也就不能往List<? super Bird>添加Bird的任意父类的对象

既然无法确定其父类对象,那该如何遍历List<? super Bird>因为Object是所有类的父类,所以可以用Object来遍历。如下

for(Object object : list){....}

无界定通配符和原始类型的区别

List<?> list = new ArrayList<>();list.add(new Object()); //compile error:因为无法保证类型的一致性,所以不能addObject o = list.get(0);  List list2 = new ArrayList<>();list2.add(new Object()); //可以添加Object o2 = list2.get(0); 

泛型通配符可以用在哪里

一般用在方法的参数限定上

public void needNumber(List<? extends Number> list);

public static <T extends Comparable<? super T>> T min(T[] a)...
原创粉丝点击