Java高级特性入门——泛型、反射和注解

摘要: 只有掌握了Java的高级特性,这门语言才算真正地登堂入室。本文将带领你们一同了解Java语言的三个经常使用的高级特性——泛型、反射和注解。java

本次直播视频精彩回顾,戳这里编程

专家简介:
澳明,阿里巴巴高级开发工程师,来自于阿里巴巴研发效能事业部-研发平台-代码智能化团队。jvm

如下内容根据演讲嘉宾视频分享以及PPT整理而成。ide

本次的分享主要围绕如下三个方面:性能

1、泛型介绍
2、反射机制
3、注解的使用spa

1、泛型介绍

在平常编程的过程当中,泛型在这三个特性之中使用频率是最高的。”泛型”一词中的泛字能够理解为泛化的意思,即由具体的、个别的扩大为通常的。Oracle对泛型的官方定义是:泛型类型是经过类型参数化的泛型类或接口。一言以蔽之,泛型就是经过类型参数化,来解决程序的通用性设计和实现的若干问题。
Java泛型是1.5版本后引入的特性,它主要被用于解决三类问题:
一、编译器类型检查设计

clipboard.png

例如上图中的实例1设计了一个简单的Box类,在其中定义了一个private的object的属性,同时定义了get()和set()两个行为,其中set()用于保存object到Box内,set()用于获取Box中的object对象。从抽象的角度看,Box类抽象了一个用于在盒子中存放物品对象和存取的行为,存取的方法接受或者返回Object类型的对象。在这个抽象的基础上,能够存放除原始类型外任意类型的对象,Object类型的声明体现了面向对象中继承的理念。
在实例2中,实现了不一样业务场景下对Box的使用方式。其中列举了两种不一样的业务场景,场景一须要在Box中存放String类型的对象,场景二须要在Box中存放Integer类型的对象,这种状况下,在实际开发时,场景二中颇有可能会错误地传入一个String对象,致使运行时错误的发生,而这正是由于Box能够被只有传入任意类型的对象致使的,这种状况在集合类操做时尤其突出。例如实例3中的状况:视频

首先声明了一个List类型的boxes对象,其中存放了两个对象,一个是String类型的“aaaaa”,另外一个是Integer类型的11111。在业务场景一下,使用者认为boxes中存放的全部对象都是String类型的,所以在取出第二个对象并进行类型转换的时候就发生了错误。这种状况每每让使用者十分迷惑,明明编译时没有问题,可是在运行时却产生了异常。也就是说,在这种面向对象的抽象过程当中,没法经过编译来验证类型该如何进行使用。对象

那么泛型是如何解决这类问题的呢?继承

clipboard.png

Oracle意识到了上述的问题,在引入泛型以后,经过将代码中的“public class Box”更改成“public class Box<T>”来建立泛型类型的声明,而这个声明的背后实质上是引入了能够在类中任何地方使用的类型变量T。如实例4中所示:能够看到,除了新增的泛型类型声明<T>外,全部在原来代码中出现的Object都被类型变量T所替换。
乍一看类型变量这个词,感受有点晦涩难懂,但其实若是仔细思量一番会发现它其实并不难理解,上面的实例4能够理解为“在使用泛型时,能够将类型参数T传递给Box类型自己”,结合Oracle给出的官方定义“泛型的本质是类型参数化”会有更深的理解。
在实例5中,在对象声明和初始化的时候,都指定了类型参数T,在场景一种,T为String;在场景二中,T为Integer。这样,在场景二中向IntegerBox中传入String类型的数据“aaaaa”时,程序会报错。实例6中的泛型集合对象的操做也与之相似,在声明了一个List<String>的boxes对象以后,若是向boxes中传入Integer对象11111,程序会报错。
能够看到,经过对于泛型的使用,以前的多业务场景中的问题都获得了解决,由于如今在编译阶段就能够解决以前类型不匹配的问题,而不用等到运行时才暴露问题,只要合理使用泛型,就能在很大程度上规避此类风险。对于泛型的使用,这种参数化类型的做用表面上看是声明,背后实际上是约定。

二、强制类型转换

clipboard.png

再回顾一下实例3,在List类型的boxes对象中存放了两个对象,分别是String类型的“aaaaa”和Integer类型的11111。其中存在一个问题,在对于boxes的声明中,使用者不知道boxes的list中到底应该存放什么类型的对象,而编译器也不知道集合存放的数据类型,只能经过实际的业务场景来决定这个box是什么类型,采用将Object强制转换成String的方式,来达到业务要求的效果。

在使用泛型以后,解决了这种场景下必须进行强制类型转换的问题。如实例7中,经过泛型声明,指定集合内元素的类型参数为String类型,这样编译器就直接知晓了元素的类型,而无需依靠实际的业务逻辑进行转换,从而解决了这类类型强制转换的问题。

三、可读性和灵活性

clipboard.png

泛型除了能进行编译器类型检查和规避类型强制转换外,还能有效地提升代码的可读性。对于实例3,若是不使用泛型,当一个不清楚业务场景的人在对集合进行操做时,没法知道list中存储的是什么类型的对象,若是使用了泛型,就可以经过其类型参数判断出当前的业务场景,也增长了代码的可读性,同时也能够大胆地在抽象继承的基础上进行开发了。
泛型使用上的灵活性体如今不少方面,由于它自己实质上就是对于继承在使用上的一种加强。由于泛型在具体工做时,当编译器在编译源码的时候,首先要进行泛型类型参数的检查,检查出类型不匹配等问题,而后进行类型擦除并同时在类型参数出现的位置插入强制转换指令,从而实现泛型。
除了上述的基础用法以外,泛型还有几种特殊的高阶用法:

clipboard.png

通配符的设计存在必定的场景,例如在使用泛型后,首先声明了一个Animal的类,然后声明了一个继承自Animal类的Cat类,显然Cat类是Animal类的子类,可是List<Cat>却不是List<Animal>的子类型,而在程序中每每须要表达这样的逻辑关系。为了解决这种相似的场景,在泛型的参数类型的基础上新增了通配符的用法,具体来讲有三种用法:<? extends T>、<? super T>、<?>。其中前二者被称为限定通配符,<?>被称为非限定通配符。
一、<? extends T> 上界通配符
上界通配符顾名思义,<? extends T>表示的是类型的上界(包含自身),所以通配的参数化类型多是T或T的子类。正由于没法肯定具体的类型是什么,add方法受限(能够添加null,由于null表示任何类型),但能够从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个add()操做会受限,缘由在于List<Animal>和List<Cat>是List<? extends Animal>的子类型。

二、<? super T> 下界通配符
下界通配符<? super T>表示的是参数化类型是T的超类型(包含自身),层层至上,直至Object,编译器无从判断get()返回的对象的类型是什么,所以get()方法受限。可是能够进行add()方法,add()方法能够添加T类型和T类型的子类型,如第二个例子中首先添加了一个Cat类型对象,而后添加了两个Cat子类类型的对象,这种方法是可行的,可是若是添加一个Animal类型的对象,显然将继承的关系弄反了,是不可行的。

三、<?> 无界通配符
在理解了上界通配符和下界通配符以后,其实也天然而然的理解了无界通配符。无界通配符用<?>表示,?表明了任何的一种类型,能表明任何一种类型的只有null(Object自己也算是一种类型,但却不能表明任何一种类型,因此List<Object>和List<null>的含义是不一样的,前者类型是Object,也就是继承树的最上层,然后者的类型彻底是未知的)。

2、反射机制

反射是Java语言自己具有的一个重要的动态机制。用一句话来解释反射的定义:自控制,自描述。即经过反射能够动态的获取类、属性、方法的信息,也能构造对象并控制对象的属性和行为。

clipboard.png

上图中有一个Apple类,它有两个构造器、一个属性和get()、set()两个行为。在左侧的“自描述”中主要是尝试在动态的过程当中借助反射获取Apple类的构造器信息和对应的参数个数、类的属性信息和类的方法信息。其中有一个Class类型,它能够产生Class对象被ClassLoader加载,从而在jvm中实现对它的调用。在这段程序中,打印了一些类的信息、类的属性信息和类的方法信息。在右侧的“自控制”的代码中,实现了在运行的过程当中建立了一些对象并触发这个对象的一些行为,最后还尝试对对象的属性进行赋值。反射的基本使用方法较为简单,可是这种机制却加强了Java语言的灵活性。

clipboard.png

如上图所示,非反射的Java类的大体运行流程是:编写源文件Apple.java,而后编译器将其编译成字节码文件Apple.class,最后加载到jvm中并运行。而采用反射的方式时,编译器一开始对其类型(编译类型和动态类型)是一无所知的,只有在运行事后,编译器才知道其真正的类型。
反射的优点主要在于两点:一、在一些场景中,这种“未知类型”实际上大大加强了程序运行时的灵活性,可是其性能会有一些损耗;二、对于对象的类型能够在运行时判断,这样的特性实质上是对多态极大地加强,进一步地将上层的抽象与下层的具体实现进行解耦。
这两点在JDBC Driver中体现的很是明显,例如上图中的实例中,JDBC的驱动加载方式是经过反射机制实现的,从而保证运行时能够动态选择要加载的驱动程序,程序灵活性大大加强。另外,JDBC只是设计了驱动须要实现的接口,并不关心驱动厂商的个数和实现方式,只要安装统一的规范便可,至于类型的判断和具体方法的触发,交给运行期动态判断便可,这种反射机制的使用淋漓尽致的体现了多态,而且下降了类与类之间的耦合度。

3、注解的使用

注解是在1.5版本引入的,如今已经成为平常程序开发中很是重要的一部分。注解是一种元数据,自己没有任何做用,若是要有,必须依附在具体的对象上,在平常使用中最多见的两个注解是@Override和@Deprecated。
先不考虑注解具体的概念、用法和如何工做等问题,注解与“标签”的概念十分类似,@Override能够理解为在方法上添加了一个标签,其表明的就是“这是一个继承关系中,子类已经重写的方法。”更进一步理解,这个标签在某个方法上加上以后,若是父类中没有该方法,那么在编译的时候就会报错,并且能够解决在继承场景下一些不留心将方法名拼错的状况,同时加强了一些程序的可读性。

clipboard.png

如上图所示,一样以@Override为例,对注解进行进一步的提取和抽象。具体抽象出了四个方面:首先在做用域方面,它只能做用于子类重写的方法上;其次在生命周期方面,注解只是在编译时进行检查,在编译结束后便没有了任何做用;除此以外,在文档支持方面,为例解决可读性的问题,设计了@Documented的注解,用来表示注解的说明注释是否包含在JavaDoc中;在层级结构设计方面,设计了@inherited用来表示注解是否能够被子类继承。

在上图中定义了一个苹果描述注解,包含了@Target、@Retention、@Inherited和@Documented四个注解,表示它生命周期是程序运行的声明周期、能够被子类继承、文档能够被包含。在设计出这个注解以后,能够将其用在前文中的Apple实例上,如图中在类和方法上各添加了一个注解,在添加完后,即可以配合反射看到注解的效果,这样能够更好的增强其自描述的能力和配置的灵活性。

本文做者:汪星人1997

阅读原文

本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索