不诗意的女程序媛不是好厨师~ 转载请注明出处,From李诗雨---blog.csdn.net/cjm24848365…】java
今天要讲的就是有一点点难度的东西了,理解一个东西最好的方式就是: 本身找到类似的情景做类比→画图帮助理解→本身敲代码理解→反思回味。 毕竟只有通过本身大脑思考的东西,才能为己所用。程序员
笔记我仍是整理的很细,也很好理解。那么今天,让我来继续学习剩下的三点内容吧~♥安全
? 和关键字extends或者super在一块儿其实就是泛型的高级应用:通配符。函数
对于它们的使用送你们八字咒语: “上界不存下界不取”学习
这个咒语什么意思,别急让我来慢慢讲:this
它表示传递给方法的参数,必须是X的子类(包括X自己), 是类型的上界。spa
因此上界 就是 针对 ?extends X 来讲的。.net
咱们来让代码本身说它是什么意思~ 有这样的几个类:3d
public class Animal {
//...
}
复制代码
public class Pet extends Animal {
//...
}
复制代码
public class Dog extends Pet {
//...
}
复制代码
又有这样一个方法:code
public static void print(Generic<? extends Animal> a){
System.out.println(a.getData().getName());
}
复制代码
咱们把这个方法和 ? extends X 对应一下,那此时的Animal类就至关因而X,因为它表示类型的上界,因此,咱们能够传Animal及其子类。
再画个图来帮助咱们理解一下 :
如今,?extends X 的含义你们应该已经理解,而且知道怎么传递类型参数了吧。
知道了魔咒 “上界不存” 中的 “上界” 的意思了,那 “不存” 又是什么意思呢? 继续往下看: 如今有一个Generic的泛型类,它提供了get和set类型参数变量的方法:
public class Generic<T> {
//...
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
复制代码
我想经过set方法往里面放数据,可是,set方法却不容许被调用!编译不经过!
既然 ? extends X 表示类型的上界,类型参数是X的子类,
因此:? extends X 主要用于安全地访问数据,能够访问X及其子类型,可是不能写入非null的数据。
这就是 “不存” 的意思了,如今,“上界不存” 的咒语你理解了吗?
它表示传递给方法的参数,必须是X的超类(包括X自己), 与 ? extends X相反,它表示类型下界。
因此下界 就是 针对 ?superX 来讲的l了。
咱们用相似的方法来理解一下 “下界不取” 的意思。
仍是上面的 Animal、Pet 和Dog类。 咱们又有一个方法:
public static void printSuper(Generic<? super Pet> a){
System.out.println(a.getData());
}
复制代码
那这个方法中的 Pet 就至关于 ? super X 里的X,因为它表示类型的下界,因此,咱们能够传Pet及其超类。 一样的,咱们也来画个图:
那 “下界不取” 中的 不取 又是什么道理呢? 咱们把以前的代码稍做改变:
对此现象咱们 能够这样来理解: ? super X 表示类型的下界,类型参数是X的超类(包括X自己)。
因此,? super X 主要用于安全地写入数据,能够写入X及其子类型。可是读取只能获得Object,并无什么实际意义,因此咱们就没有太大必要去get了。
因此 “下界不取” 的 “不取” 含义也就在于此了。
综上,咱们就知道 “上界不存,下界不取” 的真实含义其实就是: 都是出于 安全考虑,
说的官方一点,这里其实就涉及到 PESC 原则了。 即 Producer Extends Consumer Super 若是参数化类型表示一个生产者,就使用<? extends T>;若是它表示一个消费者,就使用<? super T>
即: 只读不可写时,使用Generic<? extends Animal>:Producer 只写不可读时,使用Generic<? super Pet>:Consumer
无规矩不成方圆,其实泛型的使用也是有局限性和约束的。 下面就让咱们来一一看一下,哪些地方是使用泛型时须要格外注意的~
咱们都以 Generic 这个泛型类来说:
而要用 基本数据类型对应的包装类才能够。 代码为证:
这个要稍微作个解释,先问你们一个问题 : (PS:其实这一点也能够从类型擦除角度来理解)
Generic<String> stringG=new Generic<>();
Generic<Boolean> booleanG=new Generic<>();
//输出会是什么?
System.out.println(stringG.getClass()==booleanG.getClass());
//输出又会是什么
System.out.println("stringG.getClass()的name是:"+stringG.getClass().getName());
System.out.println("booleanG.getClass()的name是:"+booleanG.getClass().getName());
复制代码
答案是:
这也就说明 运行时的类型 检测,只适用于 原始类型 Generic ,而不是Generic< String> 、Generic< Boolean>.。
因此下面这个代码就是不正确的, instanceof 在 泛型上 是不能使用的,只能用于原始类型。
也就是:
再来:
public <T extends Throwable> void doSth(T x) throws T{
try{
}catch(Throwable e){
throw x;
}
}
复制代码
虽然不容许咱们直接捕获泛型类的对象,可是咱们可把 泛型类的对象 throw 出去呀,就是这么机智✌~
最后咱们再来提一提泛型中的类型擦除吧。
泛型思想早在C++语言的模板(Template)中就开始生根发芽,在Java语言尚未出现泛型的版本时,只能经过Object是全部类型的父类和类型强制转换两个特色的配合来实现类型泛化。 因为Java语言里面全部的类型都继承于java.lang.Object,因此Object转型成任何对象都是有可能的。可是也由于有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object究竟是个什么类型的对象。在编译期间,编译器没法检查这个Object的强制转型是否成功,若是仅仅依赖程序员去保障这项操做的正确性,许多ClassCastException的风险就会转嫁到程序运行期之中。
泛型技术在C和Java之中的使用方式看似相同,但实现上却有着根本性的分歧。C里面的泛型是真实泛型,而Java中的泛型实际上是伪泛型。 这点怎么说呢?
至于为何java为何采用 类型擦除 来实现 泛型? 这是由于java 1.5以前并无泛型这个概念,泛型出现后,为了兼容以前的版本,就采用了类型擦除折中的策略:编译时对泛型要求严格,运行时却把泛型擦除了。
看下面的代码:
这是由于参数List<Integer>和List<String>编译以后都被擦除了,变成了同样的原生类型List。因此此处传入的实际上是同一个类型。不是重载。
【后记】 以初学者的心态去重学java,我忽然发现本身不懂的愈来愈多了,问号一个接一个的在脑子里跳舞。就拿泛型来讲,我仍是有不少要去学习和探究的地方,这里也只是作个学习后的整理,等下一次我有了新的收获后再继续分享,但愿能够和你们一块儿,进步进步再进一步~
积累点滴,作好本身~