《重学Java系列》之 泛型(下)

不诗意的女程序媛不是好厨师~ 转载请注明出处,From李诗雨---blog.csdn.net/cjm24848365…java

在这里插入图片描述
《重学Java系列》之 泛型(上)中咱们已经知道了①什么是泛型,②为何要有泛型,③泛型类、泛型接口 和泛型方法,④给泛型变量增长约束,⑤泛型类型的继承规则。

今天要讲的就是有一点点难度的东西了,理解一个东西最好的方式就是: 本身找到类似的情景做类比→画图帮助理解→本身敲代码理解→反思回味。 毕竟只有通过本身大脑思考的东西,才能为己所用。程序员

笔记我仍是整理的很细,也很好理解。那么今天,让我来继续学习剩下的三点内容吧~♥安全

6.泛型通配符

在这里插入图片描述
你见过上面☝这样的代码吗

? 和关键字extends或者super在一块儿其实就是泛型的高级应用:通配符。函数

  • ? extends X 表示类型的上界,类型参数是X的子类
  • ? super X 表示类型的下界,类型参数是X的超类

对于它们的使用送你们八字咒语: “上界不存下界不取”学习

这个咒语什么意思,别急让我来慢慢讲:this

(1). 先看看 ? extends X

它表示传递给方法的参数,必须是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的子类,

  • 那么能够确定的是:get方法返回的必定是个X(不论是X或者X的子类)编译器是能够肯定知道的。因此,get它容许调用。
  • 可是set方法只知道传入的是个X,至于具体是X的那个子类,编译器不知道。因此,出于安全考虑,编译器干脆就不作了。

因此:? extends X 主要用于安全地访问数据,能够访问X及其子类型,可是不能写入非null的数据。

这就是 “不存” 的意思了,如今,“上界不存” 的咒语你理解了吗?

(2). 再看看 ? super X

它表示传递给方法的参数,必须是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说:“我就是底线,不能再比我低了,不然我会生气的,后果很严重!哼!☹”

“下界不取” 中的 不取 又是什么道理呢? 咱们把以前的代码稍做改变:

在这里插入图片描述
咱们发现 此时 set方法能够被调用的,且能传入的参数只能是X或者X的子类。 可是get方法只会返回一个Object类型的值,并无什么实际意义。

对此现象咱们 能够这样来理解: ? super X 表示类型的下界,类型参数是X的超类(包括X自己)。

  • 那么就能够确定的说,get方法返回的必定是个X的超类,可是究竟是哪一个超类?不知道。可是能够确定的说,Object必定是它的超类,由于Object是全部类的超类,因此get方法返回Object。可是这样取出的全是Object其实并无什么意义。
  • 对于set方法来讲,编译器不知道它须要的确切类型,可是X和X的子类能够安全的转型为X。因此,此时set是被容许的。

因此,? super X 主要用于安全地写入数据,能够写入X及其子类型。可是读取只能获得Object,并无什么实际意义,因此咱们就没有太大必要去get了。

因此 “下界不取”“不取” 含义也就在于此了。

综上,咱们就知道 “上界不存,下界不取” 的真实含义其实就是: 都是出于 安全考虑,

  • “?extends X” 主要用于安全地访问数据。它只用来读数据,不用来存放数据。
  • ? super X 主要用于安全地写入数据。它只用来写入数据,不用来取出数据。

说的官方一点,这里其实就涉及到 PESC 原则了。 即 Producer Extends Consumer Super 若是参数化类型表示一个生产者,就使用<? extends T>;若是它表示一个消费者,就使用<? super T>

即: 只读不可写时,使用Generic<? extends Animal>:Producer 只写不可读时,使用Generic<? super Pet>:Consumer

7.泛型使用的局限性

无规矩不成方圆,其实泛型的使用也是有局限性和约束的。 下面就让咱们来一一看一下,哪些地方是使用泛型时须要格外注意的~

咱们都以 Generic 这个泛型类来说:

在这里插入图片描述

1. 泛型中不能用 基本数据类型 来实例化类型参数,

而要用 基本数据类型对应的包装类才能够。 代码为证:

在这里插入图片描述

2. 运行时 只看 原始类型

这个要稍微作个解释,先问你们一个问题 : (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());

复制代码

答案是:

在这里插入图片描述
哇,stringG.getClass() 和 booleanG.getClass() 居然相等,他们对应的是同一个类型:Generic ,而不是 Generic< String> ,Generic< Boolean>.

这也就说明 运行时的类型 检测,只适用于 原始类型 Generic ,而不是Generic< String> 、Generic< Boolean>.。

因此下面这个代码就是不正确的, instanceof 在 泛型上 是不能使用的,只能用于原始类型。

在这里插入图片描述

3. 不能在静态域或方法中引用类型变量

在这里插入图片描述
这是为何呢: 由于泛型是要在对象建立的时候才知道是什么类型的,而对象建立的代码执行顺序是,先static的部分,而后才是构造函数。 因此在对象初始化以前static的部分就已经执行了,若是你在静态部分引用了泛型,那么毫无疑问虚拟机根本不知道是什么东西,由于这个时候类尚未初始化。

4. 不能实例化类型变量,

也就是:

在这里插入图片描述
这个T原本就是虚的,都没有摸清底细,你就乱new 确定是不行的。

5. 不能捕获泛型类的实例

在这里插入图片描述
因此,要注意一点:泛型类是不能extends Exception/Throwable的。

再来:

在这里插入图片描述
咱们是不能直接捕获泛型类的对象的。那若是咱们想捕获 泛型 的异常就没有办法了吗?咱们但是伟大的码农,因此方法仍是有的:

public <T extends Throwable> void doSth(T x) throws T{
        try{

        }catch(Throwable e){
            throw x;
        }
    }
复制代码

虽然不容许咱们直接捕获泛型类的对象,可是咱们可把 泛型类的对象 throw 出去呀,就是这么机智✌~

7.浅谈类型擦除

最后咱们再来提一提泛型中的类型擦除吧。

泛型思想早在C++语言的模板(Template)中就开始生根发芽,在Java语言尚未出现泛型的版本时,只能经过Object是全部类型的父类类型强制转换两个特色的配合来实现类型泛化。 因为Java语言里面全部的类型都继承于java.lang.Object,因此Object转型成任何对象都是有可能的。可是也由于有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object究竟是个什么类型的对象。在编译期间,编译器没法检查这个Object的强制转型是否成功,若是仅仅依赖程序员去保障这项操做的正确性,许多ClassCastException的风险就会转嫁到程序运行期之中。

泛型技术在C和Java之中的使用方式看似相同,但实现上却有着根本性的分歧。C里面的泛型是真实泛型,而Java中的泛型实际上是伪泛型。 这点怎么说呢?

  • 是这样的,在C里面泛型不管在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符),或是运行期的CLR中,都是切实存在的,List<Integer>与List<String>就是两个不一样的类型,它们在系统运行期生成,有本身的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型
  • Java中的泛型则不同,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,而且在相应的地方插入了强制转型代码,所以,对于运行期的Java语言来讲,ArrayList<Integer>与ArrayList<String>就是同一个类,因此泛型技术其实是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型

至于为何java为何采用 类型擦除 来实现 泛型? 这是由于java 1.5以前并无泛型这个概念,泛型出现后,为了兼容以前的版本,就采用了类型擦除折中的策略:编译时对泛型要求严格,运行时却把泛型擦除了。

看下面的代码:

在这里插入图片描述
编译不经过:
在这里插入图片描述
按理说一个方法,我传入的参数类型不同的话,是能够重载的呀?那这里为何会呢? 上面这段代码之因此不能编译,是由于参数List<Integer>和List<String>编译以后都被擦除了,变成了同样的原生类型List,擦除动做致使这两种方法的特征签名变得如出一辙。

这是由于参数List<Integer>和List<String>编译以后都被擦除了,变成了同样的原生类型List。因此此处传入的实际上是同一个类型。不是重载。

【后记】 以初学者的心态去重学java,我忽然发现本身不懂的愈来愈多了,问号一个接一个的在脑子里跳舞。就拿泛型来讲,我仍是有不少要去学习和探究的地方,这里也只是作个学习后的整理,等下一次我有了新的收获后再继续分享,但愿能够和你们一块儿,进步进步再进一步~

积累点滴,作好本身~

相关文章
相关标签/搜索