php企业网站模板下载,html网站运行时间代码,定制网站建设官网,长春哪家网站做的好参考链接#xff1a; Java中的协变返回类型
在《JAVA核心思想》这本书里#xff0c;关于泛型的章节意外的很多#xff0c;小小的泛型里其实有很多可以学习的内容#xff0c;我总结下最近看书的成果。
一. 泛型的好处和应用
最基础的用到泛型的地方无非是在容器里 使用…参考链接 Java中的协变返回类型
在《JAVA核心思想》这本书里关于泛型的章节意外的很多小小的泛型里其实有很多可以学习的内容我总结下最近看书的成果。
一. 泛型的好处和应用
最基础的用到泛型的地方无非是在容器里 使用泛型用以保证容器内数据的类型统一所以我们先总结下泛型使用的好处
可以统一集合类型容器的存储类型防止在运行期出现类型装换异常增加编译时类型的检查解决重复代码的编写能够复用算法。可以起到重载的作用
第二个作用很好理解泛型 的 ‘泛’ 即代表着 ‘泛化’不仅仅是保证容器的安全性更重要的是减少类型的限制让我们编写更加通用的代码。我们举个例子 在javaweb中我们经常向前台返回JSON对象信息不同的业务可以会组装不同的bean为了节省提高代码的复用性我们可以这么写一个类
public class ReturnObjectA, B { public final A a; public final B b; public ReturnObject(A a, B b) { this.a a; this.b b; } public A A t(A a) { return a; } Override public String toString() { return ReturnObject{ a a , b b }; }
}
这个类没有具体的类型意思也很简单就是不限定变量的类型根据你业务的不同你可以传不同的类型进去通过构造器更方便的是java泛型支持继承你可以随意拓展你的业务字段也就不再需要为了一种业务专门创建一个bean类了。
// 业务字段拓展
public class ReturnObjectExtenderA, B, C extends ReturnObjectA, B { public final C c; public ReturnObjectExtender(A a, B b, C c) { super(a, b); this.c c; } Override public String toString() { return ReturnObjectExtender{ c c , a a , b b }; }
}
二. 重要泛型的擦除
JAVA的泛型都是通过擦除来实现的这句话的意思是 当你的程序真正跑起来的时候任何具体的类型其实都已经被擦除了所以在下面的例子中输出的结果是trueaClass和aClass1都是一样的class生成的对象。
Class aClass new ArrayListInteger().getClass();
Class aClass1 new ArrayListString().getClass(); System.out.println(aClass aClass1); 擦除的负面效应直接体现在如果你写下面这段代码T类型并不能认出你传给它的是String类型T直接会被Object替代
public class WildCardTestT { public void f(T t) { //这一段编译报错 t.isEmpty(); } public static void main(String[] args) { WildCardTestString stringWildCardTest new WildCardTest(); stringWildCardTest.f(); }
}
要让String调用它的isEmpty()方法需要给泛型一个边界代码只需要重新改一下T extends String表明T可以是String类或是String的子类如果传入的没有问题那就可以调用isEmpty()方法。
public class WildCardTestT extends String { public void f(T t) { t.isEmpty(); } public static void main(String[] args) { WildCardTestString stringWildCardTest new WildCardTest(); stringWildCardTest.f(); }
}
擦除是历史遗留问题
java的泛型不是从jdk1.0就出现的为了跟以往没有泛型代码的源代码兼容例如List被擦除为List而普通的类型变量在未指定边界的时候被擦除为Object从而实现泛型的功能并且向后兼容。
三. 泛型的通配符(逆变与协变)
这是两个赋值一个是数组ArrayList因为是Collection的子类所以数组向上转型是可以的另一个是带泛型的ArrayList带ArrayList的泛型并不能赋值给带Collection的泛型。
Collection[] collections new ArrayList[]{};
//泛型会报错
ArrayListCollection collections1 new ArrayListArrayList();
逆变协变与不变
为什么会导致这样的差异呢这里又引出了一个概念– 逆变协变与不变。逆变与协变用来描述类型转换后的继承关系。
f(⋅)是逆变contravariant的当A≤B时有f(B)≤f(A)成立
f(⋅)是协变covariant的当A≤B时有成立f(A)≤f(B)成立
f(⋅)是不变invariant的当A≤B时上述两个式子均不成立即f(A)与f(B)相互之间没有继承关系。
根据上面两个赋值语句做解释A 是ArrayList B是Collection所以B A,这个没有问题然后 A[] 数组当作f(A)B[] 数组当作f(B),并且A[]可以赋值给B[]数组说明 f(B) f(A),符合协变原则所以数组是协变的。 而泛型也套用这个规则发现 泛型其实是不变的。
Java中泛型是不变的可有时需要实现逆变与协变怎么办呢这时通配符?派上了用场
// ? extends实现了泛型的协变它意味着某种Collection或Collection子类的类型规定了该容器持有类型的上限它是具体的类型只是这个类型是什么没有人关心 比如
List? extends Collection list new ArrayListArrayList(); // ? super实现了泛型的逆变它意味着某种ArrayList或ArrayList父类的类型规定了该容器持有类型的下限比如
List? super ArrayList list new ArrayListCollection();
逆变协变的应用
在协变中还是先以数组做举例协变中对持有对象的存入有严格限制
Collection[] collections new ArrayList[2]; collections[0] new ArrayList();
//这一步编译没问题。但是运行时发现数组里已经定好了只能存ArrayList类型所以会抛ArrayStoreException
collections[1] new LinkedList();
所以在泛型的协变中例如ArrayList的add方法是不能调用了在编译期间直接报错。
这是ArrayList 的add,get方法定义当使用协变时E e 会被直接替换成 ? extends E
public boolean add(E e);
public E get(int index);
具体事例
ArrayList? extends Set sets new HashSet(); //因为 ? extends Set 编译器不知道sets引用指向什么对象有可能是 HashSet可能是TreeSet这种不确定性导致sets不能使用add方法。
//sets.add(new HashSet()); //能插入null值
sets.add(null); //因为这个泛型参数的上限是Set为了安全性所以只返回set类型
Set set sets.get(0);
在逆变中因为规定了泛型的下界所以get set 方法的使用限制又有所不同
ArrayList? super List list new ArrayListCollection(); //对于 add方法只能放List或List的子类因为list容器泛型参数都是List的父类 不会出现问题
list.add(new ArrayList());
//不能add HashSet
//list.add(new HashSet());
//不能add Object
//list.add(new Object()); //因为这个泛型参数的下限是List 所以无法确定这是个什么类型只能返回Object
Object object list.get(0);
无界通配符
除了extends和super还有一种 List\?这种通配符代表着任何事物但它与List不同的是:
List\Object List ‘持有任何Object类型的原生List’List\?表示–’具有某种特定类型的非原生List只是我们不知道那种类型是什么’。
具体用代码看出区别
ArrayListCollection collections2 new ArrayList(); ArrayList? objects new ArrayList();
//?代表持有某种特定类型 所以也可以是Collection这种赋值时合法的
objects collections2; //?代表持有某种特定类型d但是不确定具体哪种所以只能返回Object
Object o objects.get(0); //?代表持有某种特定类型但是什么类型编译器并不知道所以为了安全起见不会让你用add方法
//objects.add(new Object()); //可以add null
objects.add(null); 总结来说
要从泛型类取数据时用extends要往泛型类写数据时用super既要取又要写就不用通配符即extends与super都不用。
四. 总结
这篇文章总结的是书里比较重要的知识点跳过了简单的应用写了那么多感觉总结还是很有必要的你第一遍看书也许概念会有些懵懂但是再记录总结下你会解开第一遍看书时有点么棱两可的知识点。