解析java泛型(二)
上篇我们简单的介绍了java中泛型的最基本的内容,知道了什么是泛型以及泛型对我们的程序编写有什么好处,最后以类型限定收尾。本篇将从类型限定开始阐述java泛型中很重要的概念:通配符
一、何为通配符
首先我们看一段代码:
/*我们声明了一个泛型方法*/
public class Pair<T> {
public static <T extends Double> void show(myArray<T> a){
}
}
上文声明了一个泛型方法,但是实在是太过啰嗦,于是我们可以使用通配符来简化这种泛型方法的声明形式。有时我们也称通配符为更加简洁的类型变量的限定。接下来我们看看使用通配符如何声明一个泛型方法。
public class Pair<T> {
public static void show(myArray<? extends Double> a){
}
}
对比两段代码,我们可以看出来,类型限定也就是<T extends Double>主要用于类型参数的定义,声明在访问修饰符后面函数返回值之前;而通配符也就是<? extends Double>主要用于实例化类型参数,声明在类型变量之前,简化了泛型方法的声明。所以,凡是可以由通配符实现的都可以使用类型限定来实现,因为通配符是简化了的类型限定。
二、通配符的只读性
以上是这个通配符的一个特性,通配符的只读性是其第二个重要特性,我们知道:Child类继承于Base类是不能推出Pair<Child>继承于Pair<Base>的,所以:
public static void print(Pair<Base>){
}
Pair<Child>是不能作为参数来使用上述方法了,因为Pair<Child>和Pair<Base>是完全不同的两种类型。但是我们可以使用通配符完成两者的联系。
public static void print(Pair<? extends Base>){
}
这样,Base及其子类都是可以调用此方法的。说明了,Pair<Base>和Pair<Child>都是Pair<? extends Base>的子类型。下面我们看看为什么通配符要具有只读性,不允许修改属性值。
/*定义泛型类*/
public class Pair<T> {
private T a;
public Pair(T a){
this.a = a;
}
public void setA(T a){
this.a = a;
}
public T getA(){
return this.a;
}
}
/*main方法*/
public class Test2 {
public static void main(String[] args){
Pair<Integer> p = new Pair<>(0);
p.setA(10);
System.out.println(p.getA());
}
}
输出结果:10
没有使用通配符之前,一切安好;下面使用通配符:
/*定义泛型类*/
public class Pair<T> {
private T a;
public Pair(T a){
this.a = a;
}
public void setA(T a){
this.a = a;
}
public T getA(){
return this.a;
}
}
/*main方法*/
public class Test2 {
public static void main(String[] args){
Pair<? extends Number> p = new Pair<Integer>(0);
Pair<Double> d = new Pair<>(1.5);
p.setA(d);//编译错误
System.out.println(p.getA());
}
}
下面我们一起看看java中为什么不允许这种机制的存在,我们不妨现假设java中不存在这种机制,也就是可以对通配符类型进行修改设置其值,语句:Pair<? extends Number> p = new Pair<Integer>(0);,p中装的是Pair<? extends Number>的子类型,d实际上也是Pair<? extends Number>的子类型,所以可以调用set函数,将Double数值赋值给实际类型Integer,必然出错,所以,java中为了避免这种问题,直接不允许更改通配符修饰的类型。
三、超类通配符
超类通配符从某种角度上来说,和上述的普通的通配符作用上是相反的。其主要的形式为:<? super E>,是否和上述的<? extends E>形式上相似?接下来我们一起看看这种通配符。一段代码:
Class Base implements Comparable<Base>{
private int x;
public Base(int x){
this.x = x;
}
//实现compareTo方法
public int compareTo(Base b){
.......
}
}
Class Child extends Base{
public Child(int x){
super(x);
}
}
public static void main(String[] args){
//定义一个容器类
DynamicArray<Child> childs = new DynamicArray<Child>();
//添加元素到容器中
childs.add(new child(10));
childs.add(new child(20));
Child maxA = max(childs);
}
此段程序编译时出错,原因在于,max方法推断传入的参数类型为Child类,调用comparaeTo方法时,发现类型不匹配,拒绝执行,父类Base实现了接口Comparable<Base>,而max方法需要Comparable<Child>方法,由于子类child并没有重新实现此接口而虽然Child继承与Base,但是Comparable<Base>与Comparable<Child>却毫无关系。
解决办法,使用我们的通配符
/*将max方法修改如下*/
public static <T extends Comarable<? super T>> T max(DynamicArray<T> a){
//这样child和Base都可以被匹配到
}
超类通配符主要用于灵活的写入和比较而<? extends E>主要用于读写,不能写入或者修改。
**四、泛型的实现的细节**
首先我们需要讨论一下由类型擦除导致的一些冲突,我们来看一个代码块:
public class Base implements Comparable<Base>{
}
public class Child extends Base implements Comparable<Child>{
}
/*这是不允许的*/
虽然在编译器看来他们分别继承的是不同的接口类型,但是由于类型擦除导致父类和子类继承了同一个接口,也就是说子类两次继承了同一个接口类型,这在java中是不允许的。
最后我们看看泛型中的数组问题,如果我告诉你泛型是不能创建数组的,你能想到原因么?下面我们就从为什么泛型中不能创建数组来看看泛型的一些细节问题。
首先我们先假设泛型是可以创建数组的。
Integer[] ins = new Integer[10];
Number[] nums = ins;
Object[] obj = ins;
/*在一般数组中这是允许的,因为编译器会为我们进行检查*/
Pair<Integer>[] p = new Pair<Integer>[10];
Object[] obj = p;
obj[0] = "dfa";
/*编译器不会报错,但是在jvm运行时肯定抛异常*/
由于以上所示原因,java泛型是不允许定义数组的。
最后,由于本篇文章是博主自学时查阅书籍和博客学习的笔记和总结,如有错误,还望大家指出!
解析java泛型(二)的更多相关文章
- 解析java泛型(一)
对于我们java中的泛型,可能很多人知道怎么使用并且使用的还不错,但是我认为想要恰到好处的使用泛型,还是需要深入的了解一下它的各种概念和内部原理.本文将尽可能的囊括java泛型中的重要的概念.主要内容 ...
- Java 泛型 二
一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...
- Java泛型二:通配符的使用
原文地址http://blog.csdn.net/lonelyroamer/article/details/7927212 通配符有三种: 1.无限定通配符 形式<?> 2.上边界限定 ...
- Java学习笔记(二一)——Java 泛型
[前面的话] 最近脸好干,掉皮,需要买点化妆品了. Java泛型好好学习一下. [定义] 一.泛型的定义主要有以下两种: 在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个 ...
- 转:二十一、详细解析Java中抽象类和接口的区别
转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...
- 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore
前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...
- 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁
上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和基本的方法,显示了怎样 ...
- Java泛型解析(03):虚拟机运行泛型代码
Java泛型解析(03):虚拟机运行泛型代码 Java虚拟机是不存在泛型类型对象的,全部的对象都属于普通类,甚至在泛型实现的早起版本号中,可以将使用泛型的程序编译为在1.0虚拟机上可以执行的 ...
- 解析令人费解的java泛型
对于我们java中的泛型,可能很多人知道怎么使用并且使用的还不错,但是我认为想要恰到好处的使用泛型,还是需要深入的了解一下它的各种概念和内部原理.本文将尽可能的囊括java泛型中的重要的概念.主要内容 ...
随机推荐
- wordpress-标签
来源1:http://www.graphicrating.com/2009/01/18/my-wordpress-cheat-sheet/ 来源2:http://www.cnblogs.com/asq ...
- 微信小程序之----问题
1.如何定义全局数据 在app.js的App({})中定义的数据或函数都是全局的,在页面中可以通过var app = getApp(); app.function/key的方式调用,不过我们没有必要 ...
- 好的 vim编辑博客
http://www.cnblogs.com/ma6174/archive/2011/12/10/2283393.html 如果你不满足于使用现成的颜色主题的话,那我们来看一下如何修改环境配色.首先要 ...
- bzoj2555
开始时间:19:40 完成时间:21:00 传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2555 题目大意:(1):在当前字符串的后面插入一 ...
- call的初步理解
首先说下call的本质是一个函数 模Function.prototype.call = function(context){ // this表示某函数,函数里面的this先被替换成context,然后 ...
- php 禁用eval( )函数
php的eval函数并不是系统组件函数,因此我们在php.ini中使用disable_functions是无法禁止它的. 但是eval()对于php安全来说具有很大的杀伤力,因此一般不用的情况下为了防 ...
- spark中数据的读取与保存
1.文本文件 (1)读取文本文件 JavaRDD<String> input =sc.textFile(dir) (2)保存文本文件 result.saveAsTextFile(dir)) ...
- ssh框架整合log4j
这个是摘录的别人博客的地址,请点击下面的链接...... http://www.cnblogs.com/rushoooooo/archive/2011/08/29/2157361.html
- java 调用webservice的各种方法总结,wsimport方法总结
http://www.blogjava.net/zjhiphop/archive/2009/04/29/webservice.html wsimport生成webservice客户端: wsimpor ...
- jquery属性的相关js实现方法
有些公司手机网站开发不用第三方的jquery或者zeptio,直接用原生的javascript.原生javascript功能是蛮强大的,只不过部分属性不支持IE8以下浏览器.下面对jquery相关方法 ...