首页 > 其他分享 >第十九章《类的加载与反射》第4节:注解

第十九章《类的加载与反射》第4节:注解

时间:2023-01-03 12:31:17浏览次数:53  
标签:指定 value 第十九章 修饰 注解 Testable public 加载

​在8.10小节曾经简单的介绍过注解,但当时只是简单的介绍了3个注解的作用,本小节将详细讲解注解的相关知识。注解始于JDK1.5,在Java语言中以Annotation接口表示注解。注解其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

注解就像修饰符一样,可以用于修饰包、类、构造方法、普通方法、属性、参数以及局部变量,这些信息被存储在注解的“name=value”对中。需要注意:注解不影响程序代码的执行,无论增加、删除注解,代码都始终如一地执行。如果希望让程序中的注解在运行时起一定的作用,只有通过某种配套的工具对注解中的信息进行解析和处理,或者以反射的方式对注解信息做出专门解析和处理。​

19.4.1基本注解

在程序中如果需要使用注解,也需要向引入类一样进行引入操作。java.lang包下有5个注解,这5个注解不需要进行引入就能直接使用,因此它们也被称为“基本注解”。基本注解有:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs和@FunctionalInterface,这5个基本注解中有4个已经在第8章中进行了讲解,只有@SafeVarargs没有介绍过。​

@SafeVarargs注解用来抑制“堆污染”警告。什么是“堆污染”呢?在使用集合时,如果没有用泛型对集合进行元素类型限制有可能导致运行时出现异常,例如:​

List list1 = new ArrayList<Integer>() ;​
list1.add(20) ;// ①添加元素时引发unchecked异常​
//下面代码引起“未经检查的转换"的警告,编译、运行时完全正常​
List<String> list2 = list1;//②​
//但只要访问list2里的元素,如下面代码就会引起运行时异常​
System.out.println(list2.get(0));//③​

以上代码中,语句②不会因list1和list2所指定的泛型不同而导致语法错误,因此语句②可以通过编译并能成功运行,但在运行③时则会出现异常。引起语句③在运行时出现异常的原因是编译器没有对语句②进行严格的泛型检查,导致没有指定泛型的集合引用能够指向指定了泛型的集合对象,语句②就是典型的这种情况。专业上,把指定了泛型的集合对象赋值给没有指定泛型的集合引用称为“堆污染”。​

从JDK1.7开始对可能产生的堆污染提出警告,但这种警告仅有一种情况,那就是方法参数的类型是泛型类且是可变参数,例如:​

public static void faultyMethod1(List<String>... listStrArray)//① ​
{​
List[] listArray = listStrArray;//②​
listArray[0].add(new Integer(1));//③​
}​
public static void faultyMethod2(List<String> list)//④ ​
{​
List myList = list;//⑤​
}​

在以上代码中,标记为①的位置会产生警告,警告的内容是“Type safety: Potential heap pollution via varargs parameter listStrArray”,意思是可变参数“listStrArray可能导致堆污染”。这个警告提示了程序员一种可能存在的风险,下面的语句②进行了堆污染的操作并不会被编译器判断为语法错误,而语句③则会导致运行时程序出现异常,因此警告的出现就是为了避免这种情况的发生。需要注意:编译器仅对参数是泛型类且是可变参数的情况发出堆污染警告,代码中标记为④的位置,参数list在语句⑤中赋值给原始类型的myList虽然也会导致堆污染,但并不会导致编译器发出警告。​

消除堆污染警告可以有两种方式,分别是使用@SafeVarargs和@SuppressWarnings("unchecked")进行压制。但@SafeVarargs使用上有比较严格的限制:​

  • @SafeVarargs只能用于方法,不能用于语句或类​
  • 方法必须被static或final关键字所修饰​
  • 方法中必须含有泛型类参数,且参数必须是可变类型​

以上代码中的faultyMethod1()方法就符合添加@SafeVarargs注解的条件,读者可以自己添加这个注解并观察消除警告的效果。​

19.4.2元注解

Java语言除在java.lang下提供了5个基本的注解之外,还在java.lang.annotation包下提供了6个元注解。元注解的作用是负责对他注解进行注解,也就是为其他注解提供了相关的解释说明。​

首先介绍的是@Retention,@Retention只能用于修饰注解定义,用于指定被修饰的注解可以保留多长时间。@Retention包含一个RetentionPolicy类型的value属性,所以使用@Retention时必须为该value属性指定值。value的值只能是以下三个,如表19-6所示。​

表19-6 @Retention的value值​

value值​

意义​

RetentionPolicy.CLASS​

编译器将把注解记录在class 文件中。当运行Java程序时,JVM不可获取注解信息。这是默认值​

RetentionPolicy.RUNTIME​

编译器将把注解记录在class 文件中。当运行Java程序时,JVM也可获取注解信息,程序可以通过反射获取该注解信息​

RetentionPolicy.SOURCE​

注解只保留在源代码中,编译器直接丢弃这种注解​

从表19-6可以看出:如果需要通过反射获取注解信息,就需要使用value 属性值为RetentionPolicy.RUNTIME 的@Retention。使用@Retention元注解可采用如下代码为value指定值。​

Retention (value= RetentionPolicy.RUNTIME )​
public @interface​
Testable{ }​

以上代码中,Testable是一个自定义的注解,在它上面的Retention注解表明Testable注解一直可被保留到程序运行时,如果希望注解仅在源代码中出现,编译时被丢弃,则可以把以上代码修改为:​

Retention (value= RetentionPolicy.SOURCE)​
public @interface​
Testable{ }​

实际上,指定注解value值时可以不用“name=value”的形式,也即可以把注解直接写为:​

Retention (RetentionPolicy.SOURCE)​

接下来讲解@Target注解。@Target也只能修饰注解定义,它用于指定被修饰的注解能用于修饰哪些程序单元。@Target元注解也包含一个名为value 的属性,该属性的值只能是以下几个,如表19-7所示。​

表19-7 @Target的value值​

value值​

意义​

ElementType.ANNOTATION _TYPE​

指定该策略的注解只能修饰注解​

ElementType.CONSTRUCTOR​

指定该策略的注解只能修饰构造方法​

ElementType.FIELD​

指定该策略的注解只能修饰属性​

ElementType.LOCAL_ VARIABLE​

指定该策略的注解只能修饰局部变量​

ElementType.METHOD​

指定该策略的注解只能修饰方法定义​

ElementType.PACKAGE​

指定该策略的注解只能修饰包定义​

ElementType.PARAMETER​

指定该策略的注解可以修饰参数​

ElementType.TYPE​

指定该策略的注解可以修饰类、接口(包括注解类型)或枚举定义​

与@Retention相同,使用@Target也可以直接在括号里指定value值,而无须使用“name=value”的形式。下面的代码指定@ ActionListenerFor注解只能修饰属性。​

@Target (ElementType.FIELD)​
public @interface ActionListenerFor{ }​

下面的代码片段指定@Testable注解只能修饰方法。​

@Target (ElementType.METHOD)​
public @interface Testable { }​

下面介绍@Document注解。@Documented用于指定被该元注解修饰的注解类将被javadoc工具提取成文档,如果定义注解类时使用了@Documented修饰,则所有使用该注解修饰的程序元素的API文档中将会包含该注解说明。​

下面代码定义了一个Testable注解,代码中使用@Documented来修饰@Testable注解定义,所以该注解将被javadoc工具所提取。​

@Retention (RetentionPolicy . RUNTIME)​
@Target (E1 ementType.METHOD)​
//定义Testable注解将被javadoc工具提取​
@Documented​
public @interface Testable​
{​
}​

最后介绍@Inherited注解。@Inherited注解指定被它修饰的注解将具有继承性,即如果某个类使用了@Xxx注解(定义该注解时使用了@Inherited修饰)修饰,则其子类将自动被@Xxx修饰。​

下面使用@Inherited元注解修饰@Inheritable定义,则该注解将具有继承性。​

@Target(ElementType.TYPE)​
@Retention(RetentionPolicy.RUNTIME)​
@ Inherited​
public @interface Inheritable​
{​
}​

以上程序中,自定义的Inheritable注解被@ Inherited注解修饰,这说明@Inheritable具有继承性,如果某个类使用了@Inheritable修饰,则该类的子类将自动使用@Inheritable修饰。​

19.4.3自定义注解

自定义注解类型使用@interface关键字,定义一个新的注解与定义一个接口非常像,下面的代码可定义一个简单的注解类型。​

public @interface Test{​
}​

定义了该注解之后,就可以在程序的任何地方使用该注解。使用注解的语法非常类似于public、final这样的修饰符,通常可用于修饰程序中的类、方法、变量、接口等定义。通常会把注解放在所有修饰符之前,而且由于使用注解时可能还需要为属性指定值,因而注解的长度可能较长,所以通常把注解另放一行,如下面的代码所示。​

//使用@Test修饰类定义​
@Test​
public class MyClass{​
}​

在默认情况下,注解可用于修饰任何程序元素,包括类、接口、方法等,下面的代码是使用@Test来修饰方法。​

public class t MyClass{​
//使用CTest注解修饰方法​
@Test​
public void info(){​
}​
}​

注解不仅可以是这种简单的注解,还可以带属性,属性在注解定义中以无形参的方法形式来声明,其方法名和返回值定义了该属性的名字和类型。如下代码可以定义一个有属性的注解。​

public @interface MyTag{ ​
//定义带两个属性的注解​
//注解中的属性以方法的形式来定义​
String name() ;​
int age() ;​
}​

一旦在注解里定义了属性之后,使用该注解时就应该为它的属性指定值,例如:​

public class Test{​
//使用带属性的注解时,需要为属性赋值​
@MyTag (name="xx", age=6)​
public void info(){​
}​
}​

如果只有一个属性需要赋值,并且属性的名称是,value,则value可以省略,直接定义值即可,例如之前介绍的@ Retention注解就是如此。​

程序员也可以在定义注解的属性时为其指定初始值(默认值),指定属性的初始值可使用default关键字。下面的代码定义了@MyTag注解,该注解里包含了两个属性: name 和age,这两个属性使用default指定了初始值。​

public @interface MyTag{​
//定义了两个属性的注解​
//使用default为两个属性指定初始值​
String name() default "Jack" ;​
int age() default 22;​
}​

如果为注解的属性指定了默认值,使用该注解时则可以不为这些属性指定值,而是直接使用默认值。​

根据注解是否可以包含成员变量,可以把注解分为如下两类:​

  • 标记注解:没有定义成员变量的注解类型被称为标记。这种注解仅利用自身的存在与否来提供信息,如前面介绍的@Override、@Test 等注解。​
  • 元数据注解:包含成员变量的注解,因为它们可以接受更多的元数据,所以也被称为元数据注解。​

使用注解修饰了类、方法、成员变量等成员之后,这些注解不会自己生效,必须由开发者提供相应的工具来提取并处理注解信息。Java使用java.lang. annotation.Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。JDK1.5在java.lang.reflect包下新增了AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素。该接口主要的实现类有:Class、Constructor、Field、Method、Package,它们分别代表类、构造方法、属性、普通方法、包。这些类在之前讲解反射相关知识时都已经讲解过,并且也讲解了如何通过反射的方式读取注解。下面的【例19_14】展示了自定义注解在实际开发过程中是如何标识某个方法是可测试的。​

【例19_14 自定义注解应用】

Exam19_14.java​

import java.lang.annotation.*;
import java.lang.reflect.*;
//使用@Retention指定注解的保留到运行时
@Retention(RetentionPolicy.RUNTIME )
//使用@Target指定被修饰的注解可用于修饰方法
@Target (ElementType.METHOD)
//定义一个标记注解,不包含任何成员变量,即不可传入元数据
@interface Testable{
}
class MyTest{
@Testable
public static void m1(){}
public static void m2(){}
@Testable
public static void m3(){ throw new IllegalArgumentException("参数异常!");}
public static void m4(){}
@Testable
public static void m5(){}
public static void m6(){}
@Testable
public static void m7(){throw new RuntimeException("业务出现异常!");}
public static void m8(){}
}
public class Exam19_14 {
public static void process (String clazz) throws ClassNotFoundException{
int passed = 0;
int failed = 0;
//遍历clazz对应的类里的所有方法
for (Method m : Class. forName (clazz) . getMethods() ) {
//如果该方法使用了@Testable修饰
if (m.isAnnotationPresent(Testable.class)) {
try {
//调用m方法
m.invoke(null);
//测试成功,passed计数器加1
passed++;
} catch (Exception e) {
System.out.println("方法" + m + "运行失败,异常:" + e.getCause());
// 测试出现异常, failed计数器加1
failed++;
}
}
}
//统计测试结果
System.out.println("共运行了:" + (passed + failed) + "个方法,其中"
+ "失败了" + failed + "个," + "成功了" + passed + "个! ");
}

public static void main(String[] args) throws Exception{
process("MyTest");
}
}

【例19_14】中,MyTest类有很多方法,那些被@ Testable注解标记的方法是可以测试的方法。在Exam19_14类的process()方法中用反射的方式获取了MyTest类中的所定义的方法,并依次检查这些方法是否有@ Testable注解,如果有@ Testable注解,则认为它是一个可以测试的方法,并以反射的形式运行了这些可测试的方法以检测这些方法是否能运行成功。【例19_14】的运行结果如图19-15所示。

第十九章《类的加载与反射》第4节:注解_注解

图19-15【例19_14】运行结果​

通过上面例子读者不难看出,其实注解十分简单,它是对源代码增加的一些特殊标记,这些特殊标记可通过反射获取,当程序获取这些特殊标记后,程序可以做出相应的处理。​

本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。

标签:指定,value,第十九章,修饰,注解,Testable,public,加载
From: https://blog.51cto.com/mugexuetang/5985279

相关文章

  • 第十九章《类的加载与反射》第3节:反射
    ​JAVA的反射机制是指在运行状态中,对于任意一个类都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对......
  • 通过分析ExcelProperty注解的文字信息,实现自动表格。提高大多数日常业务查看,编辑页面
     importorg.springframework.util.ReflectionUtils;ReflectionUtils.doWithFields循环遍历对象中的所有属性。只处理其中ExcelProperty注解的字段。这个代码对很多单......
  • 第十九章《类的加载与反射》第1节:类的加载、连接和初始化
    ​在6.1小节中曾经讲过:创建对象前会完成类加载的操作。实际上,如果在程序中使用new关键字来创建一个对象,虚拟机会在创建对象之前需要完成一系列准备工作,类的加载只是这些工作......
  • devexpress控件教程 能加载任何控件的下拉菜单
    DevExpress控件很强大,今天开始写一些关于这个控件的博客,希望能对小伙伴们有所帮助。今天的内容是打造一个万能的下拉菜单控件。一般来说,ComboBoxEdit控件已经够用了,加载编......
  • 加载白屏指南
    据说jsdeliver国内被污染,导致页面难以加载,“阅读全文”后文字图片都显示不出来。我发现这个问题只在PC端出现,不影响手机端浏览,PC端不管换什么浏览器结果都打不开,目前没找到......
  • webpack4.15.1 学习笔记(七) — 懒加载(Lazy Loading)
    懒加载或者按需加载,是一种很好的优化网页或应用的方式。实际上是先把代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或引用另外一些新的代码块。这......
  • spring redis 注解开发 单片机 集群 主从复制
    失败记录,虽然最终没有成功,但是原理还是知道的 1、springreids简单实现注解 1、导入相应依赖 <dependency><groupId>redis.clients</groupId>jedis<......
  • 03.基础框架场景和资源加载模块
    ​​​https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadSceneAsync.html​​usingSystem.Collections;usingSystem.Collections.Generic;usi......
  • @LoadBalanced注解原理
    在使用springcloudribbon客户端负载均衡的时候,可以给RestTemplatebean加一个@LoadBalanced注解,就能让这个RestTemplate在请求时拥有客户端负载均衡的能力:@Bean@Lo......
  • Spring注解综合应用
    需求通过注解方式,实现下面xml的配置(实现“控制层(controller)--业务逻辑层(service)--dao层--数据源”的关系)<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="ht......