重拾Java基础知识:注解

前言

注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方式,注解是 Java 5 所引入的众多语言变化之一。它们提供了 Java 无法表达的但是你需要完整表述程序所需的信息。因此,注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,你可以将元数据保存在 Java 源代码中。并拥有如下优势:简单易读的代码,编译器类型检查,使用 annotation API 为自己的注解构造处理工具。即使 Java 定义了一些类型的元数据,但是一般来说注解类型的添加和如何使用完全取决于你。

基本语法

注解的语法十分简单,主要是在现有语法中添加 @ 符号。比如常用的:

  • @Override:检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
public class AnnotationTest {
    @Override
    public String toString() {
        return super.toString();
    }
}
  • @Deprecated:标记过时方法。如果使用该方法,会报编译警告。
public class AnnotationTest {
    @Deprecated
    public void getOne(){
        System.out.println("getOne");
    }

    public static void main(String[] args) {
        AnnotationTest annotationTest = new AnnotationTest();
        annotationTest.getOne();
    }
}
  • @SuppressWarnings:每当创建涉及重复工作的类或接口时,你通常可以使用注解来自动化和简化流程。
public class AnnotationTest {
    @SuppressWarnings("all")
    public void execute(Object item) {
        String abcd = (String) item;
    }
}

被注解标注的方法和其他的方法没有任何区别。在这个例子中,注解可以和任何修饰符共同用于方法,诸如 publicstaticvoid。从语法的角度上看,注解的使用方式和修饰符的使用方式一致。

元注解

用于定义注解的注解,通常用于注解的定义上:

  • @Target:中文翻译为目标,描述自定义注解的使用范围,比如:类、接口、枚举、方法等等。

ElementType有很多类型提供,可以定义你注释目标:

说明
TYPE类、接口、注解、枚举
FIELD属性
MEHOD方法
PARAMETER方法参数
CONSTRUCTOR构造函数
LOCAL_VARIABLE局部变量(如循环变量、catch参数)
ANNOTATION_TYPE注解
PACKAGE
TYPE_PARAMETERJDK1.8后新增,类型参数声明
TYPE_USEJDK1.8后新增,类型的使用

我们定义了一个适用于类的注解,代码如下所示:

@Target({ElementType.TYPE})
public @interface AnnotationTest {
}
@AnnotationTest
public class Test {
    //@AnnotationTest   Erro:'@AnnotationTest' not applicable to method
    public void method(){

    }
}

如果我们将这个注解在方法上使用时,你会发现编写错误,因为你定义注解只适用于指定类型上。其它不同类型也是如此,你需要在合适地方去使用。如果你需要定义多个不同类型使用,可以使用,分隔,这样就不会产生不必要的错误:

@Target({ElementType.TYPE,ElementType.METHOD})
public @interface AnnotationTest {
}
@AnnotationTest
public class Test {
    @AnnotationTest
    public void method(){}
}
  • @Retention:中文翻译为保留的意思,表示注解信息保存的时长。
说明
SOURCE注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等
RUNTIMEVM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。

下面分别介绍它们的区别:

@Retention(RetentionPolicy.SOURCE)
public @interface AnnotationTest {
}
@AnnotationTest
public class Test {
}

编译后:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.test;

public class Test {
    public Test() {
    }
}

可以看到注解并未带入到 .class文件中去,再来对比CLASS区别:

@Retention(RetentionPolicy.CLASS)
public @interface AnnotationTest {
}
@AnnotationTest
public class Test {
}

编译后:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.test;

@AnnotationTest
public class Test {
    public Test() {
    }
}

我们可以看到编译后的代码保留了注解信息,再来看下RUNTIME的区别:

@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
}
@AnnotationTest
public class Test {
}

编译后:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.test;

@AnnotationTest
public class Test {
    public Test() {
    }
}

你会发现和CLASS没有什么区别,但是RUNTIME保留在运行时,可以通过反射做读取,这也是平时开发中用的最多的。

  • @Documented:生成的JavaDoc文档。

生成JavaDoc命令:

C:\study\work\src\com\test>javadoc -encoding utf-8 AnnotationTest.java
正在加载源文件AnnotationTest.java...
正在构造 Javadoc 信息...
标准 Doclet 版本 1.8.0_101
正在构建所有程序包和类的树...
正在生成.\com\test\AnnotationTest.html...
正在生成.\com\test\package-frame.html...
正在生成.\com\test\package-summary.html...
正在生成.\com\test\package-tree.html...
正在生成.\constant-values.html...
正在构建所有程序包和类的索引...
正在生成.\overview-tree.html...
正在生成.\index-all.html...
正在生成.\deprecated-list.html...
正在构建所有类的索引...
正在生成.\allclasses-frame.html...
正在生成.\allclasses-noframe.html...
正在生成.\index.html...
正在生成.\help-doc.html...

生成后找到index.html,打开,如图:
在这里插入图片描述

  • @Inherited:允许子类继承父类的注解,一般情况下获取不到父类注解。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnotationTest {
}
@AnnotationTest
public class A {
}
public class B extends A{
}
public class Test {
    public static void main(String[] args) {
        Class c = B.class;
        Annotation[] annotation = c.getAnnotations();
        for (Annotation a:annotation) {
            System.out.println(a);
        }
        /** Output:
         *  @com.test.AnnotationTest()
         */
    }
}
  • @Repeatable:允许一个注解可以被使用一次或者多次(Java 8)。
@Repeatable(AnnotationTests.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnotationTest {
    int index() default 0;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnotationTests {
    AnnotationTest[] value();
}
@AnnotationTest(index = 1)
@AnnotationTest(index = 2)
public class Test {
    public static void main(String[] args) throws NoSuchFieldException {
        Class c = Test.class;
        Annotation[] annotation = c.getAnnotations();
        for (Annotation a:annotation) {
            System.out.println(a);
        }
        /** Output:
         *  @com.test.AnnotationTests(value=[@com.test.AnnotationTest(index=1), @com.test.AnnotationTest(index=2)])
         */
    }
}

这个注解其实是一个语法糖,虽然我们标注的是多个@AnnotationTest,其实会给我们返回一个@AnnotationTests ,相当于是Java帮我们把重复的注解放入了一个数组属性中。

定义注解

注解的定义看起来很像接口的定义。事实上,它们和其他 Java 接口一样,也会被编译成 .class 文件。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
public @interface AnnotationTest {
}

看起来更像一个空接口。注解通常会包含一些表示特定值的元素。当分析处理注解的时候,程序或工具可以利用这些值。注解的元素看起来就像接口的方法,但是可以为其指定默认值。

不包含任何元素的注解称为标记注解(marker annotation),例如上例中的 @AnnotationTest 就是标记注解。

注解元素

注解元素可用的类型如下所示:

  • 所有基本类型(intfloatboolean等)
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组

不允许使用任何包装类型,但是由于自动装箱的存在,这不算是什么限制。注解也可以作为元素的类型。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface AnnotationTest {
    int id();
    String value() default "value";
    double[] number();
}
public class Test {
    @AnnotationTest(id=1,value = "123",number = {1,2,3})
    public void method(){

    }
    @AnnotationTest(id=1,number = {1,2,3})
    public void method2(){

    }
}

编译器会进行类型检查,如果在注解某个方法时没有给出指定值时,会编译错误;使用default 关键字,则该注解的处理器会使用此元素的默认值。

使用反射操作注解

Java 拓展了反射机制的 API 用于帮助你创造这类工具。同时他还提供了 javac 编译器钩子在编译时使用注解。

方法说明
getAnnotations()获取元素上所有的注解
getDeclaredAnnotations()获取元素上所有的注解,不包括从父类(inherited)继承
getAnnotation()获取元素上指定注解
getDeclaredAnnotation()获取元素上指定注解,不包括从父类(inherited)继承
isAnnotationPresent()指定类型的注解存在于此元素上,则返回 true,否则返回 false。

当然还有很多方法,有兴趣的可以自己尝试去使用。案例如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
@Documented
public @interface AnnotationTest {
    int id();
    String value() default "value";
    double[] number();
}
public class A {
    @AnnotationTest(id=1,value = "123",number = {1,2,3})
    public void method(){

    }
    @AnnotationTest(id=1,number = {1,2,3})
    public void method2(){

    }
    public static void main(String[] args) {
        Class c = A.class;
        for (Method method:c.getMethods()) {
            if(method.isAnnotationPresent(AnnotationTest.class)){
                AnnotationTest annotation = method.getAnnotation(AnnotationTest.class);
                System.out.println("id:"+annotation.id()+",value:"+annotation.value()+",number:"+Arrays.toString(annotation.number()));
            }
        }
        /** Output:
         *  id:1,value:123,number:[1.0, 2.0, 3.0]
         *  id:1,value:value,number:[1.0, 2.0, 3.0]
         */
    }
}

注解不支持继承

注解是不支持继承的,因此不能使用关键字extends来继承某个 @interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口,我们反编译代码后就能知晓其原理:

C:\study\work\src\com\test>javap AnnotationTest
警告: 二进制文件AnnotationTest包含com.test.AnnotationTest
Compiled from "AnnotationTest.java"
public interface com.test.AnnotationTest extends java.lang.annotation.Annotation {
  public abstract int id();
  public abstract java.lang.String value();
  public abstract double[] number();
}

你会发现本质上还是接口且已经继承了Annotation 接口。

本章小结

注解是 Java 引入的一项非常受欢迎的补充,它提供了一种结构化,并且具有类型检查能力的新途径,从而使得你能够为代码中加入元数据,而且不会导致代码杂乱并难以阅读。使用注解能够帮助我们避免编写累赘的部署描述性文件,以及其他的生成文件。与注释性文字相比,注解绝对更适用于描述类相关的信息。

Java 提供了很少的内置注解。这意味着如果你在别处找不到可用的类库,那么就只能自己创建新的注解以及相应的处理器。通过将注解处理器链接到 javac,你可以一步完成编译新生成的文件,简化了构造过程。

API 的提供方和框架将会将注解作为他们工具的一部分。注解会极大的改变我们的 Java 编程体验。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值