Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java高级特性入门——泛型、反射和注解!

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

作者头像
马士兵的朋友圈
发布于 2020-09-08 08:39:06
发布于 2020-09-08 08:39:06
1.1K0
举报

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

专家简介:

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

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

本次的分享主要围绕以下三个方面:

一、泛型介绍

二、反射机制

三、注解的使用

一、泛型介绍

在日常编程的过程中,泛型在这三个特性之中使用频率是最高的。”泛型”一词中的泛字可以理解为泛化的意思,即由具体的、个别的扩大为一般的。Oracle对泛型的官方定义是:泛型类型是通过类型参数化的泛型类或接口。一言以蔽之,泛型就是通过类型参数化,来解决程序的通用性设计和实现的若干问题。

Java泛型是1.5版本后引入的特性,它主要被用于解决三类问题:

1、编译器类型检查

例如上图中的实例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类型的,因此在取出第二个对象并进行类型转换的时候就发生了错误。这种情况往往让使用者十分迷惑,明明编译时没有问题,但是在运行时却产生了异常。也就是说,在这种面向对象的抽象过程中,无法通过编译来验证类型该如何进行使用。

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

Oracle意识到了上述的问题,在引入泛型之后,通过将代码中的“public class Box”更改为“public class Box”来创建泛型类型的声明,而这个声明的背后实质上是引入了可以在类中任何地方使用的类型变量T。如实例4中所示:可以看到,除了新增的泛型类型声明外,所有在原来代码中出现的Object都被类型变量T所替换。

乍一看类型变量这个词,感觉有点晦涩难懂,但其实如果仔细思量一番会发现它其实并不难理解,上面的实例4可以理解为“在使用泛型时,可以将类型参数T传递给Box类型本身”,结合Oracle给出的官方定义“泛型的本质是类型参数化”会有更深的理解。

在实例5中,在对象声明和初始化的时候,都指定了类型参数T,在场景一种,T为String;在场景二中,T为Integer。这样,在场景二中向IntegerBox中传入String类型的数据“aaaaa”时,程序会报错。实例6中的泛型集合对象的操作也与之类似,在声明了一个List的boxes对象之后,如果向boxes中传入Integer对象11111,程序会报错。

可以看到,通过对于泛型的使用,之前的多业务场景中的问题都得到了解决,因为现在在编译阶段就可以解决之前类型不匹配的问题,而不用等到运行时才暴露问题,只要合理使用泛型,就能在很大程度上规避此类风险。对于泛型的使用,这种参数化类型的作用表面上看是声明,背后其实是约定。

2、强制类型转换

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

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

3、可读性和灵活性

泛型除了能进行编译器类型检查和规避类型强制转换外,还能有效地提高代码的可读性。对于实例3,如果不使用泛型,当一个不清楚业务场景的人在对集合进行操作时,无法知道list中存储的是什么类型的对象,如果使用了泛型,就能够通过其类型参数判断出当前的业务场景,也增加了代码的可读性,同时也可以大胆地在抽象继承的基础上进行开发了。

泛型使用上的灵活性体现在很多方面,因为它本身实质上就是对于继承在使用上的一种增强。因为泛型在具体工作时,当编译器在编译源码的时候,首先要进行泛型类型参数的检查,检查出类型不匹配等问题,然后进行类型擦除并同时在类型参数出现的位置插入强制转换指令,从而实现泛型。

除了上述的基础用法之外,泛型还有几种特殊的高阶用法:

通配符的设计存在一定的场景,例如在使用泛型后,首先声明了一个Animal的类,而后声明了一个继承自Animal类的Cat类,显然Cat类是Animal类的子类,但是List却不是List的子类型,而在程序中往往需要表达这样的逻辑关系。为了解决这种类似的场景,在泛型的参数类型的基础上新增了通配符的用法,具体来说有三种用法:、、。其中前两者被称为限定通配符,被称为非限定通配符。

1、 上界通配符

上界通配符顾名思义,表示的是类型的上界(包含自身),因此通配的参数化类型可能是T或T的子类。正因为无法确定具体的类型是什么,add方法受限(可以添加null,因为null表示任何类型),但可以从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个add()操作会受限,原因在于List和List是List的子类型。

2、 下界通配符

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

3、 无界通配符

在理解了上界通配符和下界通配符之后,其实也自然而然的理解了无界通配符。无界通配符用表示,?代表了任何的一种类型,能代表任何一种类型的只有null(Object本身也算是一种类型,但却不能代表任何一种类型,所以List和List的含义是不同的,前者类型是Object,也就是继承树的最上层,而后者的类型完全是未知的)。

二、反射机制

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

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

如上图所示,非反射的Java类的大致运行流程是:编写源文件Apple.java,然后编译器将其编译成字节码文件Apple.class,最后加载到jvm中并运行。而采用反射的方式时,编译器一开始对其类型(编译类型和动态类型)是一无所知的,只有在运行过后,编译器才知道其真正的类型。

反射的优势主要在于两点:1、在一些场景中,这种“未知类型”实际上大大增强了程序运行时的灵活性,但是其性能会有一些损耗;2、对于对象的类型可以在运行时判断,这样的特性实质上是对多态极大地增强,进一步地将上层的抽象与下层的具体实现进行解耦。

这两点在JDBC Driver中体现的非常明显,例如上图中的实例中,JDBC的驱动加载方式是通过反射机制实现的,从而保证运行时可以动态选择要加载的驱动程序,程序灵活性大大增强。另外,JDBC只是设计了驱动需要实现的接口,并不关心驱动厂商的个数和实现方式,只要安装统一的规范即可,至于类型的判断和具体方法的触发,交给运行期动态判断即可,这种反射机制的使用淋漓尽致的体现了多态,并且降低了类与类之间的耦合度。

三、注解的使用

注解是在1.5版本引入的,现在已经成为日常程序开发中非常重要的一部分。注解是一种元数据,本身没有任何作用,如果要有,必须依附在具体的对象上,在日常使用中最常见的两个注解是@Override和@Deprecated。

先不考虑注解具体的概念、用法和如何工作等问题,注解与“标签”的概念十分相似,@Override可以理解为在方法上添加了一个标签,其代表的就是“这是一个继承关系中,子类已经重写的方法。”更进一步理解,这个标签在某个方法上加上之后,如果父类中没有该方法,那么在编译的时候就会报错,而且可以解决在继承场景下一些不留心将方法名拼错的情况,同时增强了一些程序的可读性。

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
java 泛型全解 - 绝对最详细
对于java的泛型我一直属于一知半解的,平常真心用的不多。直到阅读《Effect Java》,看到很多平常不了解的用法,才下定决心,需要系统的学习,并且记录下来。
程序狗
2021/12/20
4.1K0
Java泛型详解,史上最全图文详解「建议收藏」
毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课。
全栈程序员站长
2022/09/08
9720
java MS之泛型
http://blog.csdn.net/stypace/article/details/42102567
bear_fish
2018/09/20
8360
java MS之泛型
Java泛型总结
泛型是jdk5引入的类型机制,就是将类型参数化,它是早在1999年就制定的jsr14的实现。
pollyduan
2019/11/04
1K0
【Java】泛型
🍊 泛型(Generics)是Java编程语言中的一个强大的特性,它提供了 编译时类型安全检测机制,这意味着可以在编译期间检测到非法的类型。泛型的使用减少了程序中的强制类型转换和运行时错误的可能性。
IsLand1314
2024/10/21
1740
【Java】泛型
Kotlin入门潜修之类和对象篇—泛型及其原理
如果我们了解java中的泛型,那么本篇文章提到的kotlin泛型我们也不会陌生。但是如果之前没有接触过泛型或者没有真正理解泛型,本篇文章理解起来可能有些困难,不过我会尽量阐述的通俗易懂。
Android架构
2019/06/24
9830
Java高级特性之泛型
前言 ---- 想写一下关于 Java 一些高级特性的文章,虽然这些特性在平常实现普通业务时不必使用,但如果想写出优雅而高扩展性的代码,或是想读得懂一些优秀的源码,这些特性又是不可避免的。 如果对这些特性不了解,不熟悉特性的应用场景,使用时又因为语法等原因困难重重,很难让人克服惰性去使用它们,所以身边总有一些同事,工作了很多年,却从没有用过 Java 的某些高级特性,写出的代码总是差那么一点儿感觉。 为了避免几年后自己的代码还是非常 low,我准备从现在开始深入理解一下这些特性。本文先写一下应用场景最多的泛
枕边书
2018/05/28
7960
Java之泛型详解
实际上,也不需要这样做,因为一个 ArrayList 类可以聚集任何类型的对象。这是一个泛型程序设计的实例。
用户5224393
2019/08/20
6050
细说 Java 泛型及其应用
当获取列表中的第二个元素时,会报错,java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。这是常见的类型转换错误。
aoho求索
2019/05/07
7310
Java基础教程(14)-Java中的枚举类,泛型和注解
枚举类型(enum type)是指由一组固定的常量组成合法的类型。Java 中由关键字enum 来定义一个枚举类型
JQ实验室
2024/05/05
4130
Java 泛型:理解和应用
这就是泛型的概念,是 Java 后期的重大变化之一。泛型实现了参数化类型,可以适用于多种类型。泛型为 Java 的动态类型机制提供很好的补充,但是 Java 的泛型本质上是一种高级语法糖,也存在类型擦除导致的信息丢失等多种缺点,我们可以在本篇文章中深度探讨和分析。
phoenix.xiao
2023/08/28
2880
Java 泛型:理解和应用
【Java 基础篇】Java 泛型程序设计详解
Java 泛型程序设计是 Java 5 版本引入的一项重要特性,它允许我们在编写代码时使用参数化类型,从而实现更加通用和类型安全的代码。本文将深入介绍 Java 泛型的概念、使用方法和常见技巧,并提供一些示例代码。
繁依Fanyi
2023/10/12
2730
Java核心技术之什么是泛型
Java泛型(Generic)是J2SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
全栈程序员站长
2022/07/04
7320
Java核心技术之什么是泛型
Java泛型深入理解「建议收藏」
在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数类型设置为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数,这样的方法将会更具有通用性。此外,如果将方法参数声明为接口,将会更加灵活。
全栈程序员站长
2022/09/10
9080
Java基础11:Java泛型详解
这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”资料“即可领取 3T 免费技术学习资源以及我我原创的程序员校招指南、Java学习指南等资源)
程序员黄小斜
2019/04/07
5070
深入理解泛型
泛型(Generics)是Java编程语言中的一个特性,它允许在编译时提供类型检查并消除类型转换。Java中的泛型用于类、接口和方法的创建,它使得代码能够被不同的数据类型重用。
程序员朱永胜
2023/11/09
3340
如何使用 Java 泛型来避免 ClassCastException
泛型是相关语言特性的集合,它允许类或方法对各种类型的对象进行操作,同时提供编译时类型安全性检查
Java宝典
2021/01/28
2.3K0
Java 中文官方教程 2022 版(六)
大多数情况下,如果您使用单个字符值,您将使用基本的char类型。然而,有时您需要将 char 用作对象—例如,作为期望对象的方法参数。Java 编程语言为此提供了一个包装类,将char包装在Character对象中。Character类型的对象包含一个类型为char的单个字段。这个Character类还提供了许多有用的类(即静态)方法来操作字符。
ApacheCN_飞龙
2024/05/24
3170
Java 中文官方教程 2022 版(六)
泛型接口,泛型类和泛型通配符
泛型的使用位置,除了最常见的约束集合元素,还可以使用在接口,类,方法上面。最本质的原因就是为了在使用接口,类,方法的时候,可以将类型作为参数,进行类型的参数传递。这样可以使程序的编写更加的灵活,在创建对象,调用方法的时候动态的指定类型,所以泛型也可以理解为类型的参数化。
用户5745563
2019/07/04
2.6K0
泛型接口,泛型类和泛型通配符
深入分析Java反射(三)-泛型
Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Debug。
Throwable
2020/06/23
1.6K0
相关推荐
java 泛型全解 - 绝对最详细
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档