Java注解详解

什么是注解?

  • JDK5.0通过名为Annotation(注解)的新功能将一个更通用的元数据工具合并到核心 Java 语言中
  • 注解是可以添加到代码中的修饰符,对程序代码做出一些说明和解释。 可以用于包声明、类声明、构造方法、方法、字段、参数和变量。

注解的定义

注解通过 @interface 关键字进行定义。上面我们还看到了有@Target,@Retention标记,那这些又是什么呢,其实它们是元注解,那什么又是元注解呢?先往下看,这些后面会讲。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();
}

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

注解中属性可以有默认值,默认值需要用 default 关键字指定。下面我们看如何声明注解的属性:

public @interface TestAnnotation {
    String id() default "-1";
    String name();
}

注意,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

上面的注解定义了id, name两个属性,那这个注解怎么使用呢?在使用的时候我们需要给所有的属性都赋值,如果属性有默认值的我们可以不赋值,如下:

// id有默认值, 可以不赋值
@TestAnnotation(name = "test")
public class TestUseAnnotation {
}

// 也可以都重新赋值:
@TestAnnotation(id = "test", name = "test")
public class TestUseAnnotation {
}

另外,还有一种情况。如果一个注解内仅仅只有一个属性并且名字是value(必须是value)时,应用这个注解时可以直接把属性值填写到括号内。

public @interface TestSingleAnnotation {
    String value() default "-1";
}

如上面只有一个value属性,所以可以像下面这样用:

// 简写
@TestSingleAnnotation("1")
public class TestUseAnnotation {
}

// 全写:
@TestSingleAnnotation(value = "1")
public class TestUseAnnotation {
}

还有一种情况是一个注解没有任何属性。比如:

public @interface TestEmptyAnnotation {

}

那么,在使用的时候我们可以省略注解后面的括号,如下:

@TestEmptyAnnotation
public class TestUseAnnotation {
}
编译器对默认值的限制

编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,这就是限制。为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。

注解不支持继承

注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口

元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。如果说注解是对程序代码的解释说明,那元注解就是对注解的解释说明。

元注解有 @Retention、@Documented、@Target、@Inherited,@Repeatable 五种,其中@Repeatable是Java8开始支持的。

@Retention

当@Retention 应用到一个注解上的时候,它用来约束注解的生命周期,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。

它的取值有三种:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

@Documented

它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Target

当@Target 应用到一个注解上的时候,它标记这个注解应该可以作用在哪些 Java 成员上,比如只能作用到方法上、类上、方法参数上等等。@Target 有这些值:

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
     * @hide 1.8
     */
    TYPE_PARAMETER, //表示注解能写在类型变量的声明语句中(如:class MyClass {...})

    /**
     * Use of a type
     *
     * @since 1.8
     * @hide 1.8
     */
    TYPE_USE //表示注解能写在使用类型的任何语句中(例如声明语句、泛型和强制转换语句中的类型)。
}

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}


@Test
public class A {}

public class B extends A {} // A被Test注解,因为Test有@Inherited修饰,B继承A, 那么B也被Test注解了。

@Repeatable

标识某注解可以在同一个声明上使用多次。什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
如每个设备都有功能,但手机这个设备有多个功能,手机既可以打电话,又可以浏览网页,又可以照相。

@interface Devices {
    Device[]  value();
}


@Repeatable(Devices.class)
@interface Device{
    String fuc() default "";
}


@Device(func="call_phone")
@Device(func="view_web_pages")
@Device(func="take_photo")
public class Phone{

}

内置的注解

学习了上面相关的知识,我们已经可以自己定义一个注解了。其实 Java 语言本身已经提供了一些现成的注解,我们平常写代码的过程中也经常遇到。

  • @Override - 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。

    函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。

    比如我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,通过下面的源码可以看到它就被 @FunctionalInterface 注解。

    那函数式接口标记有什么用呢?其实是函数式接口可以很容易转换为 Lambda 表达式。

@FunctionalInterface
public interface Runnable {
    void run();
}

注解的使用

一般注解都是和反射配合起来使用的,我们知道Java所有注解都继承了Annotation接口,也就是说 Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 JVM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。

下面是AnnotatedElement中相关的API方法,以上5个类都实现以下的方法:

返回值方法名称说明
<A extends Annotation>getAnnotation(Class<A> annotationClass)该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
Annotation[]getAnnotations()返回此元素上存在的所有注解,包括从父类继承的
booleanisAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组

通过反射获得某个注解后,就可以获得这个注解的属性了,然后就可以写一个我们自己的运行时注解解析器了。

注解的使用场景

注解到底有什么用?

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取

当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。

参考

https://blog.csdn.net/briblue/article/details/73824058

https://blog.csdn.net/javazejian/article/details/71860633

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值