文章目录
一、注解的基础知识
1.1、最简单的实体类或者方法
@Entity
@Override
1. 2、注解里面加入元素
Java
1. 3、如果只有一个值,可以省略元素的名字指定
@SuppressWarnings(“unchecked”)
void myMethod() { … }
1. 4、在声明上可以使用多个注解
1.5、JDK8+还支持重复注解
1.6、可以使用注解的地方:
类,属性,方法和其他程序元素的地方。
java8+,注解还可以这样使用:如下是例子:
类实例创建表达
new @Interned MyObject();
类型转化
myString = (@NonNull String) str;
实现某种约束比如只读
class UnmodifiableList<T> implements
@Readonly List<@Readonly T> { ... }
抛出异常声明
void monitorTemperature() throws
@Critical TemperatureException { ... }
二、如何声明注释类型
如果现在想要通过注解方式达到上面的注释该如何做呢?看我这里的做法如下:
我们可以申明一个类似接口一样的类但是使用@符号修饰,然后跟接口不一样的是,我们这里的抽象方法定义了默认值
@interface ClassPreamble {
String author();
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}
注意:要使@ClassPreamble中的信息出现在javadoc生成的文档中,必须使用@ documentation注释@ClassPreamble定义
idea中定义注解,可以通过/*提示生成方法注释
三、java中预定义的注释类型
Java SE API中预定义了一组注释类型。有些注释类型由Java编译器使用,有些则适用于其他注释
3.1、Java语言使用的注释类型
3.1.1、 @Deprecated
注释指出,已标记的元素已被弃用,不应该再使用。每当程序使用带有@Deprecated注释的方法、类或字段时,编译器都会生成一个警告。当一个元素被弃用时,还应该使用Javadoc @deprecated标记对其进行文档化,如下面的示例所示。在Javadoc注释和注释中使用at符号(@)并不是巧合:它们在概念上是相关的。另外,请注意Javadoc标记以小写d开头,注释以大写D开头
3.1.2、@Override
用于标注重写了父类的方法。对于子类中被@Override修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在,则报错。@Override只能作用于方法,不能作用于其他程序元素
3.1.3、 @SuppressWarnings
用于不生成@Deprecated产生的警告
3.1.3.1、 deprecation
使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
3.1.3.2、 unchecked
执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
3.1.3.3、 fallthrough
当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
3.1.3.4、 path
在类路径、源文件路径等中有不存在的路径时的警告;
3.1.3.5、 serial
当在可序列化的类上缺少 serialVersionUID 定义时的警告;
3.1.3.6、 finally
任何 finally 子句不能正常完成时的警告;
3.1.3.7、 all
关于以上所有情况的警告
3.1.3.8、抑制多个警告
@SuppressWarnings({"unchecked", "deprecation"})
3.1.4、@SafeVarargs(@SafeVarargs是JDK 7 专门为抑制堆污染警告提供的。)
当应用于方法或构造函数时,比如如下示例:
Java把引发这种错误的原因称为“堆污染”(heap pollution),当把一个不带泛型的的对象赋值给一个带泛型的变量时,往往就会发生这种“堆污染”
//不使用泛型
List list = new ArrayList<Integer>();
//添加新元素引发 unchecked 异常
list.add(20);
//下面代码引起“未经检查的转换”的警告,编译、运行时完全正常,
List<String> ls = list; //①
//但是访问ls里的元素,如下面的代码就会引起运行时异常 java.lang.ClassCastException
System.out.println(ls.get(0));
混合叠加操作的时候示例
3.1.5、 @FunctionalInterface
是Java8中新增的函数式接口。Java8规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口。如以下代码:
3.2、元注解
元注解(meta-annotation)是指注解的注解。Java5(java.lang.annotation)定义了5个标准的元注解类型,它们被用来提供对其它注解的类型作说明。接下来介绍这五个元注解*(以下未包含 Native的解释,而Native的作用主要是用来调用本地源码编译,例如本地工具的文件头信息或者指示可以引用来自本地代码定义常数值的字段)
3.2.1、@Retention
@Retention指明了该注解被保留的时间长短。包含一个名为value的成员变量,该value成员变量是RetentionPolicy枚举类型。使用@Retention时,必须为其value指定值。value成员变量的值只能是如下3个:
SOURCE:只保留在源代码中,编译器编译时,直接丢弃这种注解,不记录在.class文件中。
CLASS:编译器把注解记录在class文件中。当运行Java程序时,JVM中不可获取该注解信息,这是默认值。
RUNTIME:编译器把注解记录在class文件中。当运行Java程序时,JVM可获取该注解信息,程序可以通过反射获取该注解的信息
3.2.2、@Target
@Target指定注解用于修饰哪些程序元素。@Target也包含一个名为value的成员变量,该value成员变量类型为ElementType[],ElementType也为枚举类型,值有如下几个:
TYPE:修饰类、接口或枚举类型
FIELD:修饰成员变量(包括枚举常量)
METHOD:修饰方法
PARAMETER:修饰参数
CONSTRUCTOR:修饰构造器
LOCAL_VARIABLE:修饰局部变量
ANNOTATION_TYPE:修饰注解
PACKAGE:修饰包
TYPE_PARAMETER:Java8新增,修饰类型参数。
TYPE_USE:Java8新增,可以在任何类型上使用
类型注解(Java8新增)
在 Java8 之前的版本中,只能允许在声明式前使用注解。而在 Java8 版本中,注解可以被用在任何使用 Type 的地方,例如:初始化对象时 (new),对象类型转化时,使用 implements 表达式时,或者使用 throws 表达式时。
//初始化对象时
String myString = new @NotNull String();
//对象类型转化时
myString = (@NonNull String) str;
//使用 implements 表达式时
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表达式时
public void validateValues() throws @Critical ValidationFailedException{
...
}
定义一个类型的方法与普通的注解类似,只需要指定Target为ElementType.TYPE_PARAMETER
或者ElementType.TYPE_USE,或者同时指定这两个Target。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
...
}
ElementType.TYPE_PARAMETER表示这个注解可以用在 Type 的声明式前,而ElementType.TYPE_USE表示这个注解可以用在所有使用 Type 的地方(如:泛型,类型转换等)
与 Java 8 之前的注解类似的是,类型也可以通过设置 Retention 在编译后保留在 class 文件中(RetentionPolicy.CLASS)或者运行时可访问(RetentionPolicy.RUNTIME)。但是与之前不同的是,类型注解有两个新的特性:在本地变量上的注解可以保留在class文件中,以及泛型类型可以被保留甚至在运行时被访问。
虽然类型可以保留在 class 文件中,但是它并不会改变程序代码本身的行为。例如在一个方法前加上注解,调用此方法返回的结果和不加注解的时候一致。
Java8 通过引入类型,使得开发者可以在更多的地方使用注解,从而能够更全面地对代码进行分析以及进行更强的类型检查。
3.2.3、 @Inherited
@Inherited指定注解具有继承性。如果某个类使用了@xxx注解(定义该注解时使用了@Inherited修饰)修饰,则其子类将自动被@xxx修饰。
3.2.4、@Documented
如果定义注解A时,使用了@Documented修饰定义,则在用Javadoc命令生成API文档后,所有使用注解A修饰的程序元素,将会包含注解A的说明
3.2.5、 @Repeatable(Java8新增)
@Repeatable表示可重复注解。在实际应用中,可能会出现需要对同一个声明式或者类型加上相同的注解(包含不同的属性值)的情况。例如系统中除了管理员之外,还添加了超级管理员这一权限,对于某些只能由这两种角色调用的特定方法,可以使用可重复注解。
@Access(role="SuperAdministrator")
@Access(role="Administrator")
public void doCheck() {
...
}
Java8之前版本的 JDK 并不允许开发者在同一个声明式前加注同样的注解,(即使属性值不同)这样的代码在编译过程中会提示错误。而 Java8 解除了这一限制,开发者可以根据各自系统中的实际需求在所有可以使用注解的地方使用可重复注解。
由于兼容性的缘故,可重复注解并不是所有新定义的注解的默认特性,需要开发者根据自己的需求决定新定义的注解是否可以重复注解。Java 编译器会自动把可重复注解储存到指定的注解容器中。而为了触发编译器进行这一操作,开发者需要进行以下的定义:
首先,在需要重复标注特性的注解前加上@Repeatable标签,示例如下:
@Repeatable(AccessContainer.class)
public @interface Access {
String role();
}
@Repeatable标签后括号中的值即为指定的注解容器的类型。在这个例子中,注解容器的类型是AccessContainer,Java 编译器会把重复的 Access 对象保存在 AccessContainer 中。
AccessContainer 中必须定义返回数组类型的 value 方法。数组中元素的类型必须为对应的可重复注解类型。具体示例如下
public @interface AccessContainer {
Access[] value();
}
可以通过 Java 的反射机制获取注解的 Annotation。一种方式是通过 AnnotatedElement 接口的getAnnotationByType(Class)。首先获得 Container Annotation,然后再通过 Container Annotation 的 value 方法获得可重复注解。另一种方式是用过 AnnotatedElement 接口的getAnnotations(Class)方法一次性返回可重复注解。
可重复注解使得开发者可以根据具体的需求对同一个声明式或者类型加上同一类型的注解,从而增加代码的灵活性和可读性。
上面的基础知识已经讲解完毕了,下面开始自定义自己的注解类实践。如果有疑问可以关注公众号【小诚信驿站】或者加入扣扣群300458205
四、 实战5分钟定义自己的注解类
- 先定义一个类注解接口AnnotationXCXYZ
- 接口中定义2个抽象方法
package com.xiaochengxinyizhan.day1_selfDefineAnnotation;
import java.lang.annotation.*;
/**
* 定义一个类注解接口
*
* @author liuxiaocheng
* @create 2018-12-14 18:49
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationXCXYZ {
//定义个抽象方法
String transUpperWord();
String value();
}
- 定义个方法注解接口AnnotationMethod
- 定义一个描述方法
package com.xiaochengxinyizhan.day1_selfDefineAnnotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationMethod {
//定义一个描述方法
String description();
}
- 创建一个TestAnnotation类
package com.xiaochengxinyizhan.day1_selfDefineAnnotation;
/**
* 测试注解
*
* @author liuxiaocheng
* @create 2018-12-14 19:10
**/
@AnnotationXCXYZ(transUpperWord = "转换大小写",value = "5分钟学会写自己的注解")
public class TestAnnotation {
@AnnotationMethod(description = "测试方法的注解")
public void testAnnotationMethod(){
System.out.print("第一天 5分钟学会写自己的注解!");
}
}
- 创建一个反射类ReflectAnnotation利用反射机制获取注解信息并解析
package com.xiaochengxinyizhan.day1_selfDefineAnnotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 利用反射解析注解
*
* @author liuxiaocheng
* @create 2018-12-14 19:25
**/
public class ReflectAnnotation {
public static void main(String[] args){
//根据类路径获取该类
try {
Object annoTest = Class.forName("com.xiaochengxinyizhan.day1_selfDefineAnnotation.TestAnnotation");
//根据类获取该类的注解
AnnotationXCXYZ annotationXCXYZ=(AnnotationXCXYZ)((Class) annoTest).getAnnotation(AnnotationXCXYZ.class);
System.out.println("annotationXCXYZ:"+annotationXCXYZ.transUpperWord());
System.out.println("annotationXCXYZ:"+annotationXCXYZ.value());
Annotation[] annos=((Class) annoTest).getDeclaredAnnotations();
System.out.println("annos:"+annos.toString());
Method[] methods= ((Class) annoTest).getDeclaredMethods();
for (Method method: methods){
AnnotationMethod annotationMethod= method.getAnnotation(AnnotationMethod.class);
System.out.println("annotationMethod:"+annotationMethod.description());
if ("测试方法的注解".equals(annotationMethod.description())){
Constructor[] constructor =((Class) annoTest).getConstructors();
TestAnnotation testAnnotation=(TestAnnotation)constructor[0].newInstance();
method.invoke(testAnnotation,null);
}
}
}catch (Exception classnot){
System.out.print(classnot);
}
}
}
- 输出的结果如图
五、类型注解和注解插件
如一中所说类型注解在java8+中新增可以修饰使用的地方,以及可以使用注解插件框架来保证减少出错的概率或者自己定义注解插件。官网
六、重复注解
假设代码中要执行上个月的最后一天和每周五的23点执行定时任务
@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }
申明可重复注解类型
import java.lang.annotation.Repeatable;
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfMonth() default "first";
String dayOfWeek() default "Mon";
int hour() default 12;
}
声明包含的注释类型
public @interface Schedules {
Schedule[] value();
}
代码中如何通过反射获取注解
单个注解
AnnotatedElement.getAnnotation(Class<T>),
多个注解
AnnotatedElement.getAnnotationsByType(Class<T>).
6.1、 如何设计注解开发
现在可以使用注释零次,一次,或者,如果注释的类型标记为@Repeatable,则可以不止一次。还可以通过使用@Target元注释来限制注释类型的使用位置。例如,可以创建只能用于方法和字段的可重复注释类型。仔细设计注释类型非常重要,以确保注释尽可能灵活和强大
七、注解的测试
下列接口哪里有错?
public interface House {
@Deprecated
void open();
void openFrontDoor();
void openBackDoor();
}
A:正确代码格式
public interface House {
/**
* @deprecated use of open
* is discouraged, use
* openFrontDoor or
* openBackDoor instead.
*/
@Deprecated
public void open();
public void openFrontDoor();
public void openBackDoor();
}
假设MyHouse 实现了上面的接口的实现类,在编译的时候产生警告,如何去除警告
public class MyHouse implements House {
public void open() {}
public void openFrontDoor() {}
public void openBackDoor() {}
}
A:2种方案,一种是继续弃用,一种是抑制警告提醒
实现类中弃用
public class MyHouse implements House {
// The documentation is
// inherited from the interface.
@Deprecated
public void open() {}
public void openFrontDoor() {}
public void openBackDoor() {}
}
抑制警告提醒
public class MyHouse implements House {
@SuppressWarnings("deprecation")
public void open() {}
public void openFrontDoor() {}
public void openBackDoor() {}
}
下面的接口有错么?
public @interface Meal { ... }
@Meal("breakfast", mainDish="cereal")
@Meal("lunch", mainDish="pizza")
@Meal("dinner", mainDish="salad")
public void evaluateDiet() { ... }
A:版本必须java8+
@java.lang.annotation.Repeatable(MealContainer.class)
public @interface Meal { ... }
public @interface MealContainer {
Meal[] value();
}
实战演练:使用元素id、synopsis、engineer和date为增强请求定义注释类型。指定默认值为unassigned对于engineer和unknown对于date
/**
* Describes the Request-for-Enhancement (RFE) annotation type.
*/
public @interface RequestForEnhancement {
int id();
String synopsis();
String engineer() default "[unassigned]";
String date() default "[unknown]";
}