这篇文章将为大家详细讲解有关Java中数组协变和范型不变性的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
为宿迁等地区用户提供了全套网页设计制作服务,及宿迁网站建设行业解决方案。主营业务为网站建设、网站设计、宿迁网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
一、协变、不变、逆变
假设,我为一家餐馆写了这样一段代码
class Soup{ public void add(T t) {} } class Vegetable { } class Carrot extends Vegetable { }
有一个范型类Soup
那么问题来了,Soup
第一反应,Soup
Soupsoup = new Soup (); soup.add(new Tomato());
第一句没问题,Soup
但是,两句放在一起却有了问题。soup的实际类型是Soup
那么,Soup
(1)如果Soup
(2)如果Soup
(3)如果Soup
理解了协变、不变和逆变的概念,再看Java的实现。Java的一般泛型是不变的,也就是说Soup
二、数组协变
Java中,数组是基本类型,不是泛型,不存在Array
与泛型的不变性不同,Java的数组是协变的。也就是说,Carrot[]是Vegetable[]的子类。而上一节中的例子已经表明,协变有时会引发问题。比如下面这段代码
Vegetable[] vegetables = new Carrot[10]; vegetables[0] = new Tomato(); // 运行期错误
因为数组是协变的,编译器允许把Carrot[10]赋值给Vegetable[]类型的变量,所以这段代码可以顺利通过编译。只有在运行期,JVM真的试图往一堆胡萝卜中插入一个西红柿的时候,才发现大事不好。所以,上面的代码在运行期会抛出一个java.lang.ArrayStoreException类型的异常。
数组协变性,是Java的著名历史包袱之一。使用数组时,千万要小心!
如果把例子中的数组替换为List,情况就不同了。就像这样
ArrayListvegetables = new ArrayList (); // 编译期错误 vegetables.add(new Tomato());
ArrayList是一个泛型类,它是不变的。所以,ArrayList
两段代码虽然都会报错,但通常情况下,编译期错误总比运行期错误好处理一些。
三、当泛型也想要协变、逆变
泛型是不变的,但某些场景里我们还是希望它能协变起来。比如,有一个天天喝蔬菜汤减肥的小姐姐
class Girl { public void drink(Soupsoup) {} }
我们希望drink方法可以接受各种不同的蔬菜汤,包括Soup
要实现这一点,应该采用一种类似于协变性的写法
public void drink(Soup extends Vegetable> soup) {}
意思是,参数soup的类型是泛型类Soup
但是,这种方法有一个限制。编译器只知道泛型参数是Vegetable的子类,却不知道它具体是什么。所以,所有非null的泛型类型参数均被视为不安全的。说起来很拗口,其实很简单。直接上代码
public void drink(Soup extends Vegetable> soup) { soup.add(new Tomato()); // 错误 soup.add(null); // 正确 }
方法内的第一句会在编译期报错。因为编译器只知道add方法的参数是Vegetable的子类,却不知道它具体是Carrot、Tomato、或者其他的什么类型。这时,传递一个具体类型的实例一律被视为不安全的。即使soup真的是Soup
但是方法内的第二句是正确的。因为参数是null,它可以是任何合法的类型。编译器认为它是安全的。
同样,也有一种类似于逆变的方法
public void drink(Soup super Vegetable> soup) {}
这时,Soup
这种情况就不存在上面的限制了,下面的代码毫无问题
public void drink(Soup super Vegetable> soup) { soup.add(new Tomato()); }
Tomato是Vegetable的子类,自然也是Vegetable父类的子类。所以,编译期就可以确定类型是安全的。
关于“Java中数组协变和范型不变性的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。