注解的相关概念:
注解出现的版本:jdk1.5
Annotation(注解):其实代码中的特殊标记,可以在编译、类加载、运行被读取,并通过相应的处理。通过使用Annotation,程序可与在不改变原有逻辑的情况下,在源文件嵌入一些补充信息。代码分析工具、开发工具、和部署工具可以通过这培训补充信息进行验证或者部署。
常用的框架Spring就是在使用到了上面的原理,通过注解@Controller、@Service和@RequestMapping等注解处来在编译、类加载、运行时通过反射对使用了注解的类或者方法进行相应的处理,实现对应的功能,这样就可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息和拓展一些功能。
注解的作用:用于为程序元素(类、方法、属性等)设置元数据。
1、jdk提供三个基础注解
基本的Annotation:
Java提供的基本的Annotation:
- @Override
- @Deprecated
- @SuppressWarnings
1.1限定重写父类方法:@Override
限定重写父类方法:@Override
用于指定方法覆盖的,可以强制一个子类必须要覆盖父类的方法。
例如:我们经常覆盖的toString()方法
@Override注解的定义:
从上面定义的**@Override的来看,该注解用来指定方法重写的,可以强制一个子类必须覆盖父类**的方法。在方法上适用 @Override Annotation的作用是告诉编译器检查该方法,如果父类或者接口中没有这个方法就会报错。可以避免我们在写的时候发生错误。
// 子类
public class SubClass extends SuperClass {
// 这里标注了该方法是重写父类的info方法
// 如果不小心写错成了inf0,加上@Override注解,就会在编写阶段报错
@Override
public void info(){
System.out.println("SubClass");
}
}
// 父类
class SuperClass{
public void info(){
System.out.println("SuperClass");
}
}
在IDE工具中,如果使用了@Override注解,而父类没有该方法,则会报错,可以避免在覆盖父类方法的时候出现一些错误。
1.2、标示已经过时:@Deprecated
用于表示某个类(某个方法)已经过时,当使用这些过时的类或者方法的时候,IDE编辑器就会发出警告
@Deprecated 的定义
用于表示某个程序元素(类、方法等)已过时,但其他程序使用已过时的类、方法的时候,编译器会发出警告。
public class AnnotationTest{
public static void main(String[] args) {
AnnotationTest test = new AnnotationTest();
test.deprecatedMethod();
}
@Deprecated // 使用 @Deprecated 来标注该方法已经过时间了
public void deprecatedMethod(){
System.out.println("我已经过时了");
}
}
上面的编写代码的时候,编译器会发出警告,但是该方法还是可以正常执行的
注意:@Deprecated Annotation的作用于文档注释中的**@deprecated** 标记的作用基本一样,但用法不同,前者是jdk1.5之后才出现的注解,是直接用于接口、类、方法等上面的。
1.3、抑制编译器警告:@SupperssWarnings
指示被改注解标识的程序元素(以及在该程序中所有子元素)取消指定的编译器警告。
用于取消显示指定的编译器警告,value的值可以是:
deprecation
:使用了不赞成使用的类或方法时的警告unchecked
:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。fallthrough
:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告path
:在类路径、源文件路径等中有不存在的路径时的警告serial
:当在可序列化的类上缺少 serialVersionUID 定义时的警告finally
:任何 finally 子句不能正常完成时的警告。all
:关于以上所有情况的警告
备注:其实我在这里测试了 “unchecked”,但是无法消除泛型异常,无论是IDEA还是eclipse都是一样
2、自定义Annotation
2.1、定义简单的注解
定义新的Annotation类型使用**@interface** 关键字字,它用于定义新的Annotation类型(与定义一个接口非常相似)。
实例1:定义一个简单的注解
// 定义一个非常简单的Annotation类型 Test
public @interface MyTest{
// 本注解是不含有属性的
}
使用自定义的注解类型:@MyTest
- 使用自定的注解修饰类
// 使用@Test 修饰类
@MyTest
public class ExampleClass{
}
- 使用自定义的注解修饰方法
public class UseInMethodExample(){
@MyTest //使用@Test 修饰方法
public void methodExample(){
}
}
2.2、定义带成员变量的注解
在定义Annotation的时候还可带成员变量,成员变量在Annotation定义中以参数方法的形式来声明,其方法名和返回值定义了该成员的名字和类型。
在上我们定义的Annotation可用于修饰类、接口、方法、成员属性等任何程序元素,但是在运行时是获取不到我们自定义的注解的,这是因为
- 没有使用的注解@Target,默认是可用于修饰类、接口、方法、成员属性等任何程序元素
- 没有使用的注解@Retention,默认的是RetentionPolicy.CLASS(只好保留在class中,运行时不保留)
注解中我们也可以为注解设置一定的属性,例如我们可以在MyAnnoation注解中加入需要的属性(成员变量)
- 语法:
类型 属性名()
可以使用default关键之给成员属性设置默认值,在使用的时候可以进行修改。
// 定义有成员属性的注解Person
public @interface Person{
//在注解中定义了两个成员属性
// 属性名:name ,类型:String
String name() default "";// 默认空字符串,可以再使用的时候进行修改
// 属性名:age,类型:Integer
Integer age();
}
使用@Person
public class Student{
@Person(name="Xxx",age=18) // 这里将使用时候,将name改为“Xxx”
public void info(){
//代码实现...
}
}
2.3、提取Annotation的信息:
在Java中提供了java.lang.reflect.AnnotateElement接口,该接口代表程序中可以接受注释的程序元素,实现类有:
- java.lang.reflect.Class:类定义
- java.lang.reflect.Constructor: 构造器定义
- java.lang.reflect.Field: 类的成员变量定义
- java.lang.reflect.Method:类的方法定义
- java.lang.reflect.Package:类的包定义
主要的方法:
- Annotation[] getAnnotations():获取该程序元素上的所有注解
- Annotation[] getDeclaredAnnotations():获取该程序元素上的所有运行时的注解
- default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在返回true,否返回false
示例:
注意:下面的注解只能获取运行时存在的注解,如果不是的话,就无法获取到,如果时要获取到 @MyAnnotation 注解,需要将注解修改为:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// ...
}
package learn.demo.annotations;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings(value = "unchecked")
public class TestAnnotation {
/**
* 测试获取方法 testGetAnnotation() 上的注解
* */
@MyAnnotation
@Deprecated
@Test
public void testGetAnnotation()throws Exception{
Class<TestAnnotation> thisClass = TestAnnotation.class;
Method testGetAnnotation = thisClass.getMethod("testGetAnnotation");
Annotation[] annotations = testGetAnnotation.getDeclaredAnnotations();
System.out.println("使用到的注解有"+annotations.length +"个:");
for (Annotation an: annotations){
System.out.println(an);
}
}
运行结果:
3、Meta Annotation(元注解)
元注解:用于修饰注解的注解。
jdk提供了3个基本的Annotation之外,在java.lang.annotation 包中提供了四个Meta Annotation(元注解)。
- @Retentoin
- @Target
- @Documented
- @Inherited
3.1、 元注解之@Retention
jdk中**@Retention注解的定义
@Retention中的成员变量的名称是 value,类型是RetentionPolicy**枚举类,下面是枚举类的定义
package java.lang.annotation;
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* 编译器会丢弃掉这类注解
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* 这类注解会保存在class文件中,运行时,JVM不再保留这类注释,是默认值
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* @see java.lang.reflect.AnnotatedElement
* 这类注解会保存在class文件中,运行时,JVM会保留这类注释,可以通过反射获取这类注解
*/
RUNTIME
}
使用@Retention:
@Retention 只能用于修饰一个Annotation定义,指定该注解可以保存的时间
vaule的成员变量及其含义
- RetentionPolicy.CLASS:编译器将注解保留在class文件中,但是java程序运行的时候是不加载注释的,JVM是不存在这类注释的,这也是默认值。
- RetentionPolicy.RUNTIME:编译器将注释保留在class文件中,运行的时候,JVM也会去加载这类注释,可以通过反射来获取到。
- RetentionPolicy.SOURCE:编译器直接丢弃这类注解。
示例:
@Retention(RetentionPolicy.RUNTIME)
public @interface ExampleAnnotation{
//元注解@Retention只能修饰注解,如果修饰其它会报错
}
3.2、 元注解之@Target
@Target也是用于修饰一个Annotation定义,指定被修饰的Annotation能用于修饰哪些程序元素。
@Target元注解的定义
@Target中value成员变量的类型ElementType枚举类
- ElementType.TYPE:该策略的Annotation只能修饰类、接口(包含注解类型)或者枚举。
- ElementType.FIELD:该策略的Annotation只能修饰成员变量。
- ElementType.METHOD:该策略的Annotation只能修饰方法。
- ElementType.PARAMETER:该策略的Annotation只能修饰参数。
- ElementType.CONSTRUCTOR:该策略的Annotation只能修饰构造函数。
- ElementType.LOCAL_VARIABLE:该策略的Annotation只能修饰局部变量。
- ElementType.ANNOTATION_TYPE:指定该策略的注解只能修饰Annotation(注解)。
- ElementType.PACKAGE:该策略的包只能修饰包定义。
- ElementType.TYPE_PARAMETER:可以去修饰泛型的类型(jdk1.8之后的新特性 类型注解)。
- ElementType.TYPE_USE:表示该注解能写在使用类型的任何语句中(jdk1.8之后的新特性 类型注解)。
@Target中value成员变量的类型ElementType枚举类,下面是ElementType的定义:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration
* 可用于修饰类、接口(注解)、或者枚举
*/
TYPE,
/** Field declaration (includes enum constants)
* 只能修饰成员变量
*/
FIELD,
/** Method declaration
* 只能用于修饰方法
*/
METHOD,
/** Formal parameter declaration
*只能用于修饰参数
*/
PARAMETER,
/** Constructor declaration
* 只能用于修饰构造器
*/
CONSTRUCTOR,
/** Local variable declaration
* 只能用于修饰局部变量
*/
LOCAL_VARIABLE,
/** Annotation type declaration
* 只能用于修饰注解(Annotation)
*/
ANNOTATION_TYPE,
/** Package declaration
* 只能用于修饰包
*/
PACKAGE,
/**
* Type parameter declaration
* 类型参数申明
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* 类型的使用
* @since 1.8
*/
TYPE_USE
}
3.3、 元注解之@Documented
@Documented注解的定义:
用于指定Meta Annotation 修饰的Annotation类将被javadoc工具提取成文档。
接下来定义一个实例来说明:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Documented // 定义一个 TestDocmented 注解,使用@Documented修饰
public @interface TestDocmented{
}
定义一个TestExample类,使用@TestDocmented
public class TestExample{
@TestDocmented
public void test(){
System.out.printlf("测试 @Documented 注解")
}
}
3.4、 元注解之@Inherited
@Inherited注解的定义
使用@Inherited:
@Inherited指定被它修饰的注解具有继承性,例如某个类使用了一个被@Inherited的注解,则该类的子类也将继承该注解。
接下来用一个定义来说明
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Inherited // @Inherited 修饰
public @interface InheritedTest{
// ...
}
// 父类
@InheritedTest //父类使用@InheritedTest
class Base{
//...
}
// 子类
public class SubClass extends Base{
// ...
}
/**
* 测试 @InheritedTest 注解
* */
@Test
public void TestInherited(){
Annotation[] baseAns = Base.class.getAnnotations();
System.out.println("Base 类使用的注解");
for (Annotation an: baseAns ){
System.out.println(an);
}
System.out.println(SubClass.class.isAnnotationPresent(@InheritedTest));
System.out.println("SubClass 类使用的注解");
Annotation[] subClassAns = SubClass .class.getAnnotations();
for (Annotation an: subClassAns ){
System.out.println(an);
}
}
JDK8的新特性
可重复注解:@Repeatable注解
保证两个注解的@Target 和@Retention的注解的属性值必须一致。
@Repeatable在jdk中的定义