ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

为何需要使用泛型的通配符? --《JAVA编程思想》 56

2021-09-26 23:03:30  阅读:218  来源: 互联网

标签:JAVA Apple -- 56 Fruit fruits 类型 new public


我们先来看一个导出类型的数组赋予基类型数组的例子:

public class Fruit {}
public class Apple extends Fruit {}
public class RedApple extends Apple {}
public class CovariantArrays {

    public static void main(String[] args) {
        Fruit[] fruits = new Apple[10];
        fruits[0] = new Apple();
        System.out.println("成功存入 Apple");
        fruits[1] = new RedApple();
        System.out.println("成功存入 RedApple");
        fruits[2] = new Fruit();
        System.out.println("成功存入 Fruit");
    }

}
成功存入 Apple
成功存入 RedApple
Exception in thread "main" java.lang.ArrayStoreException: mtn.baymax.charpter15.Fruit
	at mtn.baymax.charpter15.CovariantArrays.main(CovariantArrays.java:16)

上述代码虽然能通过编译,但在运行时会抛出 ArrayStoreException 表示我们将错误的类型存储至数组中。

fruits 数组的实际引用仍然是 Apple 类型的数组,编译器虽然允许你将任何 Fruit 类型及其导出类放入数组中,但数组存储的元素类型是不可变的,只能存放 Apple 类型或者及其子类,故 fruits 中不能存储 Fruit 类型。

实际上,数组的向上转型在这是不合适的。但可怕的是,即使存储类型不对,也能通过编译,我们只能在运行期间发现这个错误。

但有时候我们希望一个存储 Fruit 类型的容器来接收各类存储 Fruit 及 Fruit 导出类的容器,这时该怎么办呢?

我们首先想到用 List 来作为容器,List 会对存入的元素做类型检查,加强代码的安全性。

不过持有 Apple 类型的 List 仍然不等于持有 Fruit 类型的 List 。这是因为 Fruit 类型的 List 可以存储 Fruit 及其任何导出类,但 Apple 类型的 List 只能存储 Apple 类型及其子类,这两种容器本质是不同的(想象一下,你能在 Apple 类型的 List 中放入 Orange 类型嘛?)。故下述代码是无法通过编译的。

public class GenericsAndCovariance {

    public void printFruit(ArrayList<Fruit> fruits) {
        for (Fruit fruit : fruits) {
            System.out.println(fruit);
        }
    }

    public static void main(String[] args) {
        ArrayList<Apple> apples = new ArrayList<>();
        GenericsAndCovariance gc = new GenericsAndCovariance();
        //类型错误,无法编译
        //gc.printFruit(apples);
    }

}

想要 fruits 能接收持有任何 Fruit 导出类型的 List ,使用泛型的通配符便可以解决。

public class GenericsAndCovariance {

    public void printFruit(ArrayList<? extends Fruit> fruits) {
        for (Fruit fruit : fruits) {
            System.out.println(fruit);
        }
    }

    public static void main(String[] args) {
        ArrayList<Apple> apples = new ArrayList<>();
        apples.add(new Apple());
        apples.add(new Apple());
        apples.add(new Apple());
        GenericsAndCovariance gc = new GenericsAndCovariance();
        gc.printFruit(apples);
        ArrayList<Fruit> fruits = new ArrayList<>();
        fruits.add(new Fruit());
        fruits.add(new Fruit());
        fruits.add(new Fruit());
        gc.printFruit(fruits);
    }

}

mtn.baymax.charpter15.Apple@1b6d3586
mtn.baymax.charpter15.Apple@4554617c
mtn.baymax.charpter15.Apple@74a14482
mtn.baymax.charpter15.Fruit@1540e19d
mtn.baymax.charpter15.Fruit@677327b6
mtn.baymax.charpter15.Fruit@14ae5a5

ArrayList<? extends Fruit> fruits 表示持有任何继承自 Fruit 类型的 List,故 fruits 可以接收持有 Frult 和 Apple 类型的 List。

虽然我们可以正常读取 fruits 中存储的元素,但我们不能往 fruits 中存放任何元素,哪怕 Object 也不行,这又是为什么呢?

    public void printFruit(ArrayList<? extends Fruit> fruits) {
        for (Fruit fruit : fruits) {
            System.out.println(fruit);
        }
        //下面三种类型均无法通过编译
        //fruits.add(new Fruit());
        //fruits.add(new Apple());
        //fruits.add(new Object());
    }

当任何持有 Fruit 导出类的 List 被赋值给 fruits 时,都会执行向上转型(如同第一个例子中数组向上转型),不过这里向上转型的类型并不是某个具体类型,我们只知道它继承自 Fruit,可能是 Fruit 、Apple,还有可能是 Orange、Banana 。在无法确定 List 容器中具体类型的情况下,编译器禁止我们往容器中添加任何数据,但因知道存储的类型都继承自 Fruit,故在读取的时候,可以安全的向上转型为 Fruit。

本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。

若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!

标签:JAVA,Apple,--,56,Fruit,fruits,类型,new,public
来源: https://blog.csdn.net/BaymaxCS/article/details/120497906

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有