注解(Annotation)
参考文章:https://zhuanlan.zhihu.com/p/37701743
理解注解
注解如同一张标签。标签具备对于抽象事物的解释。
想象代码具有生命,注解就是对于代码中某些鲜活个体的贴上去的一张标签。
注解的定义
理解:一张名字为TestAnnotation的标签。
public @interface TestAnnotation {}
注解的使用
理解:将TestAnnotation标签贴到Test这个类上面
@TestAnnotation
public class Test {}
元注解
元注解是可以注解到注解上的注解(元注解是一种基本注解,但它能够应用到其它注解上面)。
元注解有5种:
@Rentention
@Documented
@Target
@Inherited
@Repeatable
@Retention
Retention 英文释义:保留期
当 @Retention 应用到一个注解上时,它解释说明了这个注解的存活时间。
取值
@Retention(参数) | 作用 |
---|---|
RetentionPolicy.SOURCE | 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 |
RetentionPolicy.CLASS | 注解只被保留到编译进行的时候,不会被加载到 JVM 中。 |
RetentionPolicy.RUNTIME | 注解可以保留到程序运行的时候,它会被加载到 JVM 中,所以程序运行时可以获取到它们。 |
实例
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {}
@Documented
它能够将注解中的元素包含到 Javadoc 中。
@Target
@Target 指定了注解运用的地方。(限制修饰目标注解运用的场景)
取值
@Target(参数) | 作用 |
---|---|
ElementType.ANNOTATION_TYPE | 可以给一个注解进行注解 |
ElementType.CONSTRUCTOR | 可以给构造方法进行注解 |
ElementType.FIELD | 可以给属性进行注解 |
ElementType.LOCAL_VARIABLE | 可以给局部变量进行注解 |
ElementType.METHOD | 可以给方法进行注解 |
ElementType.PACKAGE | 可以给一个包进行注解 |
ElementType.PARAMETER | 可以给一个方法内的参数进行注解 |
ElementType.TYPE | 可以给一个类型进行注解,比如类、接口、枚举 |
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_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Inherited
一个拥有注解的父类被 @Inherited 注解过,如果它的子类没有被任何注解应用的话,这个子类会继承父类的注解。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
@Repeatable
Java 1.8 新特性。
可以让注解的值同时取多个。
实例
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class) //Persons.class 容器注解
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan {}
@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。容器注解,就是用来存放其它注解的地方,它本身也是一个注解。
注解的属性
注解的属性也叫成员变量。注解只有成员变量,没有方法!
注解的成员变量在注解的定义中以”无形参的方法“形式来声明,其方法名定义了该成员变量的名字,返回值定义了该成员变量的类型。
TestAnnotation 注解拥有 id 和 msg 两个属性
定义属性时,属性的类型必须是8中基本数据类型 + 类、接口、注解及它们的数组。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id(); //属性 id
String msg(); //属性 msg
}
给注解赋值 id, msg
@TestAnnotation(id=3,msg="hello annotation")
public class Test {}
创建注解时,default 赋予默认值
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
@TestAnnotation() //可以不加参数,因为有默认值
public class Test {}
如果注解内仅仅有一个名字为 value 的属性时
public @interface Check {
String value(); //特殊的属性 value
}
// 下面两种方法等同
@Check("hi") //省略了 value=
@Check(value="hi")
若一个注解没有任何属性
public @interface Perform {}
应用这个注解时,可以省略括号
@Perform
public void testMethod(){}
Java预置的注解
@Override
重写父类或接口的方法
@Deprecated
这个注解用于标记已过时的元素。
public class Hero {
@Deprecated
public void say(){ //调用该方法会出现划线
System.out.println("Noting has to say!");
}
public void speak(){ //调用该方法会出现划线
System.out.println("I have a dream!");
}
}
可见,say()方法被一条划线标记。
@SuppressWarnings
忽略掉编译器的警告提示。
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
上面的代码中,编译阶段不会报错,但是运行时会抛出 ClassCastException 这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。
Java 官方文档说,未来的版本会授权编译器对这种不安全的操作产生错误警告。
@FunctionalInterface
java 1.8 新特性
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,上面源码可以看到它就被 @FunctionalInterface 注解。
可能有人会疑惑,函数式接口标记有什么用,这个原因是函数式接口可以很容易转换为 Lambda 表达式。
注解与反射
注意:如果一个注解要在运行时被成功提取,那么 @Rentention(RetentionPolicy.RUNTIME) 是必须的。
基本方法
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {} //判断它是否应用了某个注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {} //返回指定类型注解
public Annotation[] getAnnotations() {} // 返回注解到这个元素上的所有注解。
实例1
@TestAnnotation()
public class Test {
public static void main(String[] args) {
// 注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) { // 如果获取到的 Annotation 如果不为 null
// 然后通过 getAnnotation() 方法来获取 Annotation 对象。或者是 getAnnotations() 方法。
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id()); //id:-1
System.out.println("msg:"+testAnnotation.msg()); //msg:
}
}
}
实例2
@TestAnnotation(msg="hello")
public class Test {
@Check(value="hi")
int a;
@Perform
public void testMethod(){}
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
//获取类的注解
System.out.println("id:"+testAnnotation.id()); //id:-1
System.out.println("msg:"+testAnnotation.msg()); //msg:hello
}
try {
Field a = Test.class.getDeclaredField("a");
a.setAccessible(true);
//获取一个成员变量上的注解
Check check = a.getAnnotation(Check.class);
if ( check != null ) {
System.out.println("check value:"+check.value()); //check value:hi
}
Method testMethod = Test.class.getDeclaredMethod("testMethod");
if ( testMethod != null ) {
// 获取方法中的注解
Annotation[] ans = testMethod.getAnnotations();
for( int i = 0;i < ans.length;i++) {
System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName()); //method testMethod annotation:Perform
}
}
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
注解的使用场景
官方文档描述
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息。
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取。
值得注意的是,注解不是代码本身的一部分。