Java反射之AnnotatedType接口

上一篇Java反射之获取注解的AnnotatedElement接口介绍了 AnnotatedElement 接口,它是用反射获取注解的顶级接口。其实现类或子接口包括 Class、TypeVariable、Parameter、Package、AccessibleObject、GenericDeclaration 等,这些在前边的文章中已经介绍过。另外,它还有一个重要的接口 AnnotatedType,本篇将介绍它。 1. 再说注解 JDK1.5推出了注解功能,给Java语言带来非常强大的诸多特性。Java注解有几种保留策略:源码、运行时、类,可以在创建注解时通过 @Retention 元注解来指定,它需要引用 RetentionPolicy 中定义的策略: SOURCE: 注解只在源码层面保留,编译后被丢弃 CLASS: 默认的保留策略,注解将由编译器记录在类文件中,但在运行时不需要由 JVM 保留 RUNTIME: 注解不仅保留在类文件,在运行时 JVM 也会保留它,因此可以通过反射获取注解 注解的作用大概可以分为以下几种: 提供编译期特性:Java提供的一些注解,可以进行编译期特性支持,比如 @SuppressWarnings, @Deprecated 和 @Override 等可以提供编译期检查,@FunctionalInterface 标注函数式接口等 生成帮助文档:Java的 @Documented 注解可以用来标记生成 javadoc 时保留类或接口上的注解 运行时获取注解实现特定逻辑:这个是使用最多的场景,使用注解,并通过反射获取注解,来实现自身业务逻辑,大多数框架都使用注解来实现高级特性,比如 Spring 的 @Bean、 @Service 等注解 一句话,有了注解,我们可以方便的按需实现自身业务逻辑。在JDK1.8之前呢,注解所能表示标注的位置有限,JDK1.8又新增了两种标注位置,支持将注解标注在任何类型上。 2. 再说注解标注的位置 还记得 上一篇 介绍注解标注的位置信息吗?文中提到,通过 ElementType 枚举来定义注解可标注的位置信息,其他包括下边两项: ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 它们都是在JDK1.8时新增的,TYPE_PARAMETER 主要目的在于支持将注解标注在泛型类型的参数上,而 TYPE_USE 范围更广,可以将注解标注在任何Java类型上。也就是说,JDK1.8之前,注解只能标注在特定的位置(类、方法、包、参数、域、构造器、本地变量、注解),不能标注在泛型参数上,JDK1.8之后,可以标注在任何类型上。 使用TYPE_USE就可以省略其他 ElementType 元素吗? 既然 TYPE_USE 可以表示标注在所有类型上,那么是不是就不需要其他 ElementType 元素了呢?肯定不是!TYPE_USE 有其特殊的目的,它表示 “类型使用” 类型,其实就是指任何Java类型,但是它只能用 AnnotatedType 来描述并获取注解信息(见这里)。...

2021-11-25 · 5 min · 1042 words · Hank

Java反射之获取注解的AnnotatedElement接口

在Java反射的体系结构一文中说过,AnnotatedElement 接口定义了通过反射获取注解信息的方法,它是一个顶层接口,能够被标记注解的反射类都实现了该接口从而得到反射获取注解的能力,比如 Class、Field、Constructor、Method、Parameter 都实现了该接口。本文将详细介绍 AnnotatedElement。 1. 再说注解 注解是JDK1.5推出的新特性,用于标注Java中的特定元素,以便通过反射来获取它们,并实现不同的功能。本文不会讨论注解的基本知识,有兴趣的可以自行查阅相关资料。但是注解有一些特性需要细说一番。 1.1. 注解标注的位置 Java中,注解可以标注在多个位置上,常见的包括类、接口上、方法(构造函数)、方法参数(构造函数参数)、成员域上以及标注在注解上(注解的注解),甚至可以将注解标注在 package-info.java 文件中定义的包上,这些位置是在定义注解时通过元注解 @Target 来指定的,比如向下边这样: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.FIELD}) public @interface CustomAnno { String value() default ""; } 可以看到,通过 ElementType 来指定注解可以标注的位置,它定义了以下几种位置: ElementType.TYPE: 标注在类型上,包括接口、类、枚举等 ElementType.FIELD: 标注在类、接口的成员域上,包括枚举的元素 ElementType.CONSTRUCTOR: 标注在构造器上 ElementType.ANNOTATION_TYPE: 标注在其他注解上 ElementType.METHOD: 标注在方法上 ElementType.PARAMETER: 标注在方法参数上 ElementType.LOCAL_VARIABLE: 标注本地变量上,即构造器、静态代码块、方法等的内部变量 ElementType.PACKAGE: 标注在 packag-info.java 文件中定义的包上 ElementType.TYPE_PARAMETER: 标注在类型参数上,比如泛型参数T、E上 ElementType.TYPE_USE: 使用类型,注解可以标注在任何类型变量上 上边的最后两个是在JDK1.8新增的,用于在注解标注在泛型上的情况,涉及到 AnnotatedType 接口,我们将在下一篇专门行文来介绍它。 注解用于包上的例子: package-info.java @CustomAnno("package") package com.belonk.lang.reflect; 其他标注位置比较常见,就不在赘述了。 1.2. 注解的继承 Java中定义注解时,可以指定其是否可以被继承。注解继承指的是父类标注的注解可以作用于子类,而不是说一个注解继承另一个注解。比如,自定义如下注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited (1) @interface MyAnno { String value() default ""; } 1 标记了元注解 @Inherited 即表示该注解可以继承 然后,编写一个父类,它被标注了自定义注解:...

2021-11-21 · 3 min · 569 words · Hank

Java反射之获取泛型类定义的具体类型

通过上一篇Java反射之表示所有类型的Type接口一文,我们知道 Type 接口及其五大组件是用来描述Java的各种类型,主要包括原始类型和泛型类型。很多时候,我们需要获取到泛型类上定义的具体类型,从而完成一些业务逻辑。比如,最常见的情景就是JSON的反序列化,需要将JSON字符串反序列化为泛型的具体类型,比如反序列化为 List<User>,这就要求每一个 List 中的元素都是 User 对象。那么,如何获取到泛型类上定义的具体类型呢?这就是本文要阐述的内容。 1. 通过Class能获取泛型类型吗? 在/2021/11/09/reflect-class.html中说过,Class 类上有一个 getTypeParameters() 方法用来获取类上的类型变量。通过它能不能获取到泛型类型呢?看下边的示例: class Parent<T> { (1) } 1 定义一个泛型类 这里定义一个泛型类,现在尝试通过它的实例对象来获取泛型类型,代码如下: public void printParent() { Parent<String> parent = new Parent<>(); (1) TypeVariable<? extends Class<? extends Parent>>[] parentTypeParameters = parent.getClass().getTypeParameters(); (2) System.out.println(Arrays.toString(parentTypeParameters)); } 1 创建 Parent 类的实例 2 获取类型变量 上边的示例并不能拿到实例上的 String 类型,而只能拿到 Parent 类上定义的泛型类型 T,因此,输出结果为: [T] 很显然,这个结果不是我们想要的,我们想要获取到 String 这个类。在 Java反射之创建泛型数组 一文中说过,泛型是编译期特性,在运行时泛型会被擦除。因此,我们想通过`Class` 在运行时用反射直接去获取泛型类型,看起来是不行的。 既然直接获取不行,那么有没有别的办法呢? 在上一篇Java反射之表示所有类型的Type接口一文中说过,Type 的参数化类型组件 ParameterizedType 可以获取整个泛型定义,那么要获取类上的泛型,肯定少不了它! 但是,Field 通过 getGenericType() 方法可以获取到参数化类型,Constructor、Method 可以通过 getGenericParameterTypes() 获取泛型参数或者通过 getGenericReturnType() 方法获取泛型值的泛型类型,而翻看 Class 类的 api,与泛型相关的看似有用的只有两个方法:...

2021-11-15 · 3 min · 495 words · Hank

Java反射之表示所有类型的Type接口

前边说过,JDK1.5推出了反射功能,并介绍了反射的类设计结构,其中提到了 Type 接口,它是JDK1.5新增的接口,表示Java中的所有类型。本文介绍这个接口及其五大组件。 Figure 1. Type及其组件 1. Type的五大组件概览 Type 接口表示Java中的类型,这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型,前边介绍的 Class 就是其组件之一。Type 及其组件用于通过反射来描述Java各种类型,比如描述泛型的定义,获取泛型参数个数、类型、泛型通配符的上下边界等等。一个典型的应用场景是获取真实的泛型类型,我们将在后续文章单独介绍。 Type 用其五大组件来描述上边提到这些类型,类图如下: Figure 2. Type类图 ParameterizedType: 表示参数化类型,即整个泛型的定义,如 List<String> 可以用 ParameterizedType 来描述; TypeVariable: 表示类型变量,即泛型申明中的具体类型,比如: List<E> 用参数化类型来描述,而其中的 E 就用 TypeVariable 来描述; WildcardType: 表示泛型通配符类型,即泛型申明中带 ? 通配符,比如:List<? extends Number> 中 ? extends Number 就用 WildcardType 来描述; GenericArrayType: 表示泛型数组,比如申明一个泛型数组的域 T[] array,它就用 GenericArrayType 来描述; Class: 表示运行时的类或接口,在前边Java反射的体系结构一文中已经介绍过了,不再赘述。 一句话总结,Type 的 Class 组件用来描述Java类或接口等原始类型和基本类型,其他几个组件描述泛型类型,包括泛型的变量、通配符以及泛型数组。 现在,来看看这几个组件的用法。 2. 参数化类型ParameterizedType 什么是参数化类型?其实就是指泛型,具体而言,JDK5推出泛型之后,原来的具体的类型可以申明为泛型,变得"可被参数化"了,比如:List 是一个具体类型,但是可以申明为 List<String>、List<Integer> ,就仿佛可以申明不同的参数,类似方法可以申明不同的参数一样(其实泛型是编译期特性,它两实际上是同一类型,都是 List,这在 Java反射之创建泛型数组 已经说过了)。 所以,ParameterizedType 用来描述泛型类型,比如:List<T>、List<String>、User<T extends Number>、List<?...

2021-11-14 · 4 min · 804 words · Hank

Java反射之创建泛型数组

前边介绍了Java泛型类设计体系中的一些常用类,本篇将着重介绍 Array 类和如何创建泛型数组。 1. Java能创建泛型数组吗? 1.1. 泛型类型擦除 首先,我们知道,泛型是JDK1.5推出的编译期特性,什么是编译期?即是说在编译的时候有效,编译代码时编译器能够对泛型进行检查和校验。但是如果在运行时,泛型其实已经无效了,泛型类型被擦除了。比如下面的代码: 代码清单1 List<String> stringList = new ArrayList<>(); List<Integer> integerList = new ArrayList<>(); System.out.println(stringList.getClass() == integerList.getClass()); 结果输出为 true。stringList 只能添加 String 类型的数据,而 integerList 也只能添加 Integer,不能加入别的类型的数据。为什么?因为它们申明了泛型类型,编译器就会对类型兼容性进行检查,类型不符则会抛出异常。但是,他们的 Class 确是相同的,即是说,在运行时它们是相同的类型,都是 ArrayList,而泛型类型被擦除了。 1.2. 尝试创建泛型数组 那么,我们申明泛型数组会怎么样呢?看下边的代码: 代码清单2 class GenericArray1<T> { private final T[] array; (1) private int index; public GenericArray1(int size) { this.array = (T[]) new Object[size]; (2) } public void add(T item) { (3) array[index++] = item; } public T[] array() { return array; } } 1 申明泛型数组成员域 2 构造函数中创建Object数组,然后强转为泛型类型 3 数组中只能添加特定类型的元素 上边的代码,试图创建一个泛型数组,它只能添加某一种类型的数据,然后通过 array() 方法返回这个数组。然而,标记3的 add 方法虽然得益于泛型实现了限制了数组中只能添加特定的某一类型元素,看似没有问题,实际上运行时标记2的这行代码会抛出 ClassCastException,告诉你Object数组不能转为特定类型的数组。创建了 Object 数组, 它可以容纳任何元素,但是如果强制转型为特定类型的数组,可容纳的元素类型范围变窄了,这不是向下转型吗?jvm不允许这么做。...

2021-11-10 · 3 min · 469 words · Hank

Java反射的体系结构

反射是JDK5推出的非常重要的特性,通过反射,开发者可以在运行时动态的创建类实例,获取、更改、调用类的相关信息、方法等。反射也是各大框架使用的主要技术,如知名的Spring framework框架。 本文将介绍反射的类设计体系,并配备一些简单示例来介绍反射常用API的使用,本文示例都是采用JDK1.8编写。 1. 反射类设计 Java专门为反射设计了名为java.lang.reflect包,官方API文档在这里: 反射官方文档,该包提供了一系列类共同组成反射体系。 1.1. 反射类概览 既然反射是在运行时动态描述类信息的,那么描述类信息到底是些什么东西?类信息除了类、接口、数组、枚举等常见类型的基本信息外,还包括类型中的域、方法、构造器等以及它们的修饰符(public、static、volatile、private、final、native、synchronized 等),另外,还包括方法参数和注解。反射类如下表所示: Table 1. 反射中的类 类 描述 Class 位于java.lang包,描述类、接口的运行时状态,枚举、数组也是一种类,而注解是一种特殊的接口。每一个类型都有一个Class对象与之对应 Field 描述类型中的域信息 Constructor 描述类型中的构造函数信息 Method 类型中的方法信息描述 Parameter JDK8新增,提供有关方法参数的信息,包括其名称和修饰符。它还提供了一种获取参数属性的替代方法。 Modifier 类、域、方法、构造函数等的访问修饰信息工具类 AnnotatedElement 反射获取注解的顶层接口 Type Java中的所有类型的顶层接口 Array 用于动态创建和处理数组 Proxy 动态代理的支持类 这里暂时介绍基础的Class、Field、Constructor、Method、Parameter、Modifier,其他几个类会在后续文章中单独介绍。现在,我们来看看类图的设计。 1.2. 反射类图 整个反射的类设计见下图: Figure 1. 反射类图 图中只列出了比较基础且重要的类,从图中可以看到,反射的类结构并不算复杂,顶层主要包括几个重要接口和类: AnnotatedElement: 定义了反射获取java注解的方法,如 getAnnotations() 等,实现该接口的类意味着可以获取注解信息; Member: 定义获取类成员(域、构造函数、方法)相关的标识符信息的接口,比如获取名称、标识符等; Type: 所有类型的顶层接口,用来描述java中的类型,比如泛型类型、类字节码等,包含五大组件,其中包括最重要和常见的 Class 对象,后续文章将单独介绍这个接口和其组件; GenericDeclaration: 表示泛型申明的接口,继承自 AnnotatedElement,内部定义了 getTypeParameters() 方法,用于获取类型变量(TypeVariable,该接口为 Type 接口的一个子组件); AccessibleObject: 表示可访问对象,Field、Method 和 Constructor 对象的基类,用来检查和修改它们的访问权限:公共、包、私有访问,比如:调用其 setAccessible(boolean) 方法修改域的访问权限,然后才能修改域的值; Executable: 表示可执行的对象,其实就是方法和构造函数的基类;...

2021-11-09 · 5 min · 869 words · Hank

Java编程思想(第4版)中文高清版PDF下载

Java编程思想一书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 1. 站长评价 Java程序员必备参考书、工具书,长期霸占Java编程书籍排行榜榜首的神级著作!Java编程必读书籍,需要反复研读、反复体会编程思想! 丰富指数:☆☆☆☆ 难度指数:☆☆☆ 推荐指数:☆☆☆☆☆ 迫不及待了?点此立即下载! 2. 内容简介 本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作。本书的作者拥有多年教学经验,对C、C++以及Java语言都有独到、深入的见解,以通俗易懂及小而直接的示例解释了一个个晦涩抽象的概念。本书共22章,包括操作符、控制执行流程、访问权限控制、复用类、多态、接口、通过异常处理错误、字符串、泛型、数组、容器深入研究、Java I/O系统、枚举类型、并发以及图形化用户界面等内容。这些丰富的内容,包含了Java语言基础语法以及高级特性,适合各个层次的Java程序员阅读,同时也是高等院校讲授面向对象程序设计语言以及Java语言的绝佳教材和参考书。 第4版特点: 适合初学者与专业人员的经典的面向对象叙述方式,为更新的Java SE5/6增加了新的示例和章节。 测验框架显示程序输出。 设计模式贯穿于众多示例中:适配器、桥接器、职责链、命令、装饰器、外观、工厂方法、享元、点名、数据传输对象、空对象、代理、单例、状态、策略、模板方法以及访问者。 为数据传输引入了XML,为用户界面引入了SWT和Flash。 重新撰写了有关并发的章节,有助于读者掌握线程的相关知识。 专门为第4版以及Java SE5/6重写了700多个编译文件中的500多个程序。 支持网站包含了所有源代码、带注解的解决方案指南、网络日志以及多媒体学习资料。 覆盖了所有基础知识,同时论述了高级特性。 详细地阐述了面向对象原理。 在线可获得Java讲座CD,其中包含Bruce Eckel的全部多媒体讲座。 3. 作者简介 Bruce Eckel是MindView公司(www.MindView.net)的总裁,该公司向客户提供软件咨询和培训。他是C标准委员会拥有表决权的成员之一,拥有应用物理学学士和计算机工程硕士学位。除本书外,他还是《C编程思想》的作者,并与人合著了《C++编程思想 第2卷》(这两本书的英文影印版及中文版均已由机械工业出版社引进出版)及其他著作。他已经发表了150多篇论文,还经常参加世界各地的研讨会并进行演讲。 4. 目录 读者评论 前言 简介 第1章 对象导论 1.1 抽象过程 1.2 每个对象都有一个接口 1.3 每个对象都提供服务 1.4 被隐藏的具体实现 1.5 复用具体实现 1.6 继承 1.6.1 “是一个”(is-a)与“像是一个”(is-like-a)关系 1.7 伴随多态的可互换对象 1.8 单根继承结构 1.9 容器 1.9.1 参数化类型(范型) 1.10 对象的创建和生命期 1.11 异常处理:处理错误 1.12 并发编程...

2020-04-14 · 2 min · 350 words · Hank

JVM垃圾回收器工作原理及使用实例介绍

本文首先介绍了JVM各类垃圾回收器及其工作原理,接着通过实例演示它们的使用方式及需注意事项,最后总结了垃圾回收器的配置方式及参数意义。 1. 垃圾收集基础 Java语言的一大特点就是可以进行自动垃圾回收处理,而无需开发人员过于关注系统资源,例如内存资源的释放情况。自动垃圾收集虽然大大减轻了开发人员的工作量,但是也增加了软件系统的负担。 拥有垃圾收集器可以说是Java语言与C语言的一项显著区别。在 C语言中,程序员必须小心谨慎地处理每一项内存分配,且内存使用完后必须手工释放曾经占用的内存空间。当内存释放不够完全时,即存在分配但永不释放的内存块,就会引起内存泄漏,严重时甚至导致程序瘫痪。 以下列举了垃圾回收器常用的算法及实验原理: 1.1. 1、引用计数法 (Reference Counting) 引用计数器在微软的 COM 组件技术中、Adobe 的 ActionScript3 种都有使用。 引用计数器的实现很简单,对于一个对象 A,只要有任何一个对象引用了 A,则 A 的引用计数器就加 1,当引用失效时,引用计数器就减 1。只要对象 A 的引用计数器的值为 0,则对象 A 就不可能再被使用。 引用计数器的实现也非常简单,只需要为每个对象配置一个整形的计数器即可。但是引用计数器有一个严重的问题,即无法处理循环引用的情况。因此,在 Java 的垃圾回收器中没有使用这种算法。 一个简单的循环引用问题描述如下:有对象 A 和对象 B,对象 A 中含有对象 B 的引用,对象 B 中含有对象 A 的引用。此时,对象 A 和对象 B 的引用计数器都不为 0。但是在系统中却不存在任何第 3 个对象引用了 A 或 B。也就是说,A 和 B 是应该被回收的垃圾对象,但由于垃圾对象间相互引用,从而使垃圾回收器无法识别,引起内存泄漏。 1.2. 2、标记-清除算法 (Mark-Sweep) 标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段首先通过根节点,标记所有从根节点开始的较大对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。该算法最大的问题是存在大量的空间碎片,因为回收后的空间是不连续的。在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续的内存空间的工作效率要低于连续的空间。 1.3. 3、复制算法 (Copying) 将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。 如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大。因此在真正需要垃圾回收的时刻,复制算法的效率是很高的。又由于对象在垃圾回收过程中统一被复制到新的内存空间中,因此,可确保回收后的内存空间是没有碎片的。该算法的缺点是将系统内存折半。 Java 的新生代串行垃圾回收器中使用了复制算法的思想。新生代分为 eden 空间、from 空间、to 空间 3 个部分。其中 from 空间和 to 空间可以视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。from 和 to 空间也称为 survivor 空间,即幸存者空间,用于存放未被回收的对象。...

2017-04-03 · 7 min · 1413 words · Hank