码迷,mamicode.com
首页 > 其他好文 > 详细

面试官:我们天天用注解,你知道注解到底是怎样实现的吗?

时间:2020-07-23 19:03:19      阅读:88      评论:0      收藏:0      [点我收藏+]

标签:++   程序   本质   角色   例子   抽取   文档   文章   并且   

技术图片

注解,也叫元数据,是一种代码级别的说明。它是JDK1.5引入的一个特性,与类、接口、枚举类所在同一个层次。它可以声明在包、类、方法、成员变量、构造器、局部变量、方法参数等的上面,用来对这些元素进行说明、注释。也可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

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 注解

自定义注解时,还有一些要求:

  1. Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以及以上所有类型的数组;

  2. 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值时使用 default 关键字。指定初始化值的注解,在使用时可以不对成员变量进行赋值。

  3. 如果只有一个成员变量,建议使用参数名为value;

  4. 如果定义的注解含有成员变量,那么使用时必须进行赋值,除非它有默认值,赋值的格式为“成员变量名 = 值”;如果只有一个成员变量,且名称为value,则可以省略“value=”直接赋值;
  5. 没有定义成员变量的 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相关文章,学习资料都会在里面更新,整理的资料也会放在里面。

觉得写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!

面试官:我们天天用注解,你知道注解到底是怎样实现的吗?

标签:++   程序   本质   角色   例子   抽取   文档   文章   并且   

原文地址:https://blog.51cto.com/14570694/2512765

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!