整天都说注解注解注解,你们了解注解吗来自——面试官的灵魂拷问

131 阅读8分钟

注解

它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以‘@注解名’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问。另外,你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。

元注解作用

如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:

编写文档:通过代码里标识的元数据生成文档。

代码分析:通过代码里标识的元数据对代码进行分析。

编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查

1、注解的作用分类 (1)生成文档相关的注释说明:通过代码里标识的注解可以生成文档相关的注释说明。

下面我们就来演示一下,首先我们编写一个类,添加相关的注解。

/**

  • @author Mr.wu
  • @version 1.0
  • @since 1.8 / public class AnnotationDemo { /* *
    • @param a
    • @param b
    • @return a+b */ public int sum(int a,int b){ return a+b; } } 然后使用javadoc指令把我们创建的类生成javadoc文档。

整天都说注解注解注解,你们了解注解吗来自——面试官的灵魂拷问 生成之后,如下图所示:

整天都说注解注解注解,你们了解注解吗来自——面试官的灵魂拷问 打开文档,如下图所示:

整天都说注解注解注解,你们了解注解吗来自——面试官的灵魂拷问 我们平时使用的JDK的文档就是这样生成的。

(2)分析运行代码:通过代码里标识的注解对代码进行分析运行[使用反射]。 最后会进行演示。

(3)编译检查:通过代码里的注解让编译器实现编译检查。 例如:我们平时经常使用的override注解,当我们使用此注解时,编译器就会检查该方法是否重写了父类(或接口)中的方法。

2、JDK内置注解 JDK中内置了三个基本的注解,下面就来介绍一下:

@Override: 限定重写父类中方法, 该注解只能用于方法; @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时,通常是因为所修饰的结构危险或存在更好的选择; @SuppressWarnings: 抑制编译器警告。 3、JDK中的元注解 元注解就是修饰注解的注解。JDK中有四个元注解:

@Target:表示注解能够作用的位置。 (1) ElementType.TYPE :可以作用在类、接口和枚举类上; (2) ElementType.METHOD :可以作用在方法上; (3) ElementType.FIELD :可以作用在成员变量上; (4) ElementType.CONSTRUCTOR :可以作用在构造器上; (5) ElementType.LOCAL_VARIABLE :可以作用在局部变量上。

@Retention:表示注解的生命周期,即注解被保留的阶段。 (1) RetentionPolicy.SOURCE :在源文件中有效(即源文件保留),编译时编译器会直接丢弃这种策略的注解; (2) RetentionPolicy.CLASS : 在class文件中有效(即class保留),当运行Java程序时, JVM不会保留注解。这是默认值 (3) RetentionPolicy.RUNTIME : 在运行时有效(即运行时保留),当运行 Java 程序时, JVM会保留注解。程序可以通过反射获取该注释。

@Documented:表示该注解修饰的注解,可以被抽取到API文档中。 注意:定义为Documented的注解必须设置Retention值为RetentionPolicy.RUNTIME 。 @Inherited:表示该注解可以被人类继承。 4、自定义注解 自定义注解很简单,格式为:

元注解 public @interface 注解 自定义注解时,还有一些要求:

Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以及以上所有类型的数组; 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值时使用 default 关键字。指定初始化值的注解,在使用时可以不对成员变量进行赋值。 如果只有一个成员变量,建议使用参数名为value; 如果定义的注解含有成员变量,那么使用时必须进行赋值,除非它有默认值,赋值的格式为“成员变量名 = 值”;如果只有一个成员变量,且名称为value,则可以省略“value=”直接赋值; 没有定义成员变量的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据。 下面我们就来自定义一个简单的注解:

@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value(); } 那么注解的实质是什么呢?

我们把我们自定义的注解反编译一下,如下图所示:

整天都说注解注解注解,你们了解注解吗来自——面试官的灵魂拷问 public interface zzuli.edu.annotation.MyAnnotation extends java.lang.annotation.Annotation { public abstract java.lang.String value(); } 由反编译后的代码可知,注解的本质实际上是一个接口,并且该接口默认继承了 Annotation 接口。

5、JDK8中注解的新特性 Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

@Repeatable:可重复注解 当我们需要重复使用某个注解时,并且希望利用相同的注解来表现不同的形式时,我们可以借助@Repeatable注解。比如:我们在生活中一个人往往是具有多种身份,例如我是一家公司的员工,同时我还是我父母的孩子等等,此时我们就可以使用@Repeatable注解来完成。

在Java 8之前没有@Repeatable时,我们都是通过定义注解的数组来实现可重复注解的,如下所示:

//定义一个表示角色的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Role { String value(); }

//定义一个角色数组的注解,表示可重复的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Roles { Role[] value(); }

//使用表示可重复注解的Roles注解来表示人所扮演的不同的角色 @Roles({@Role("employee") ,@Role("son")}) public class People { } 在Java 8中出现了@Repeatable可重复注解之后,变得简单了很多。下面我们就来演示一下,还是使用上面的例子,方便我们进行对比。

//定义一个Role注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Roles.class) //表示Role注解是一个可重复注解 public @interface Role { String value(); }

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Roles { Role[] value(); }

@Role("employee") @Role("son") public class People { } 类型注解 JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER、TYPE_USE。 (1) ElementType.TYPE_PARAMETER:表示该注解能写在类型变量的声明语句中,如:参数声明、泛型声明等; (2) ElementType.TYPE_USE:表示该注解能写在使用类型的任何语句中。

6、注解的底层实现原理 我们在使用框架时经常会使用注解,那注解底层到底是怎样执行的呢?下面我们通过一个例子来演示一下:

例子:自定义一个注解,注解标注在哪里就让哪个方法执行。

//自定义一个注解 @Retention(RetentionPolicy.RUNTIME) public @interface MyJunit {

}

public class PrintNumber { public void showOdd(){ //打印奇数 for (int i = 0; i < 10; i++) { if (i % 2!=0) { System.out.print(i+" "); } } }

@MyJunit
public void showEven(){ //打印偶数
    for (int i = 0; i < 10; i++) {
        if (i % 2==0) {
            System.out.print(i+" ");
        }
    }
}

}

public class JunitTest { public static void main(String[] args) throws Exception { //1.创建PrintNumber对象 PrintNumber printNumber = new PrintNumber(); //2.获取该类的字节码文件对象 Class<? extends PrintNumber> clazz = printNumber.getClass(); //3.获取对象中的所有方法 Method[] methods = clazz.getMethods(); for (Method method:methods) { //4.判断方法上是否有@MyJunit注解 boolean flag = method.isAnnotationPresent(MyJunit.class); //5.如果方法上有@MyJunit注解,则执行 if (flag){ method.invoke(printNumber); } } } } 运行结果:

整天都说注解注解注解,你们了解注解吗来自——面试官的灵魂拷问 由上述代码可知,注解底层是通过反射实现的。

易错点:由于注解默认的生命周期是在class文件中有效,当运行Java程序时, 注解就会失效。 所以我们在自定义注解时,要想在运行时使用,则应该把注解的生命周期设置为运行时有效(RetentionPolicy.RUNTIME),否则会报错。

总结

java提供的Documented元注解跟Javadoc的作用是差不多的,其实它存在的好处是开发人员可以定制Javadoc不支持的文档属性,并在开发中应用。如果对技术文章,以及干货内容,程序员生活类文章,感兴趣的朋友们可以关注收藏转发一下,期待下一次的文章吗,那就抓紧关注我吧。