乐趣区

关于java:回炉重造注解

注解

前言

以前学习到「注解」的时候,没有好好了解注解是如何工作的,只是晓得注解能够实现一些性能,总而言之,就是懵懵懂懂。

不过,即便你不晓得什么是注解,但必定接触过注解,比方办法的重写,在办法下面写着 @Override,这个货色就是注解。

好了,上面就开始回炉重造!打好根底!

什么是注解?

注解(Annotation),Annotation 的意思有「标注、批注、附注」。

注解和泛型一样,也是从 JDK5 引入的新个性。它是与类、接口处在同一级别的,有它的语法来定义注解的

注解,人们还有另一个词来称说它,即「元数据」,所谓元数据(metadata),就是用来形容数据的数据(data about data)。初看这句话可能会有点懵,不过没关系,都是这样过去的,好好细品细品。

品不过去?能够看看这里:

什么是元数据?为何须要元数据?– 贺易之的答复 – 知乎

元数据(MetaData)- 阮一峰

好了,咱们晓得「注解 == 元数据 」,那么 它形容什么数据呢?问得好,形容的就是咱们写的源代码!能够形容 类、办法、局部变量、办法参数 等数据(这些数据也有另一个叫法,Java Element)。留神下面我说过 Annotation 的意思有 附注 的意思,是吧,所以说这个注解实际上并不是程序自身,只是向编译器提供相干的程序的附加信息,也就是说注解并不会影响程序的运行。

为什么会有注解的呈现?

这就扯到历史的倒退了,综合我所理解的,大略就是这样子:

这里也扯到了 XML(Extensible Markup Language),如果不相熟,我这里就简略说下,相熟能够跳过啦!

XML 翻译过去就是「可扩大的标记语言」,与 HTML 相似。然而 XML 是被设计用来传输数据的,并不是显示数据,具备「自我描述性」。

上面是 Jayson Tatum 写给 Kobe Bryant 的短信,存储为 XML:

<text-message>
<to>Kobe Bryant</to>
<from>Jayson Tatum</from>
<body>I got you today</body>
</text-message>

下面的短信就具备自我描述性。它领有留言,同时蕴含了发送者和接受者的信息。当然,这仅仅是形容而已,XML 并没有做任何事件,就是纯文本,咱们须要写代码,让代码辨认这些标签,赋予意义,这样程序能力读懂 XML,晓得它形容的是什么。

目前 XML 有两个作用,一是能够用 XML 格局来传输数据,二是能够作为配置文件。

在注解呈现之前,开发者根本是用 XML 来配置某些货色,因为 XML 和代码是低耦合的,合乎低耦合的需要,然而,随着从 XML 形容的数据越来越多,配置越来越简单,人们发现用 XML 形容数据太简单了,须要一种高耦合的来升高这种简单的状况,所以才呈现注解,用注解来形容数据。

不过,当初也是看状况,并不是说注解代替了 XML,而是有时应用 XML,有时应用注解,各有各的益处,两者互相联合等,这还须要具体情况具体分析。

为什么须要学习注解?

  1. 很显然,咱们当初日常开发,应用到各种开源框架,常常会用到注解,不学习注解,那天然看不懂注解相干的代码。
  2. 看起来比拟厉害

JDK 自带的注解

  • @Override:示意以后办法笼罩了父类的办法
  • @Deprecated:示意该办法因为平安、性能问题等,曾经 不举荐应用 了,即 曾经过期,办法上有横线,应用时会有正告。此外,在版本升级时,如果要打算删除一些办法,也通常会在前一个版本中,将该办法加上 @Deprecated,而后再在后续版本中删除。
  • @SuppviseWarnings(value="unchecked"):示意镇压正告信息(让编译器疏忽特定的编译正告)

    一些 value 值:

    • uncheckeddeprecation(疏忽 一些过期的 API 正告)
    • unused(疏忽 没有被应用过的代码的正告)
    • fallthrough(疏忽 switch 中缺失 break 的正告)
    • all(疏忽全副)

在 JDK 7 和 8,别离退出了 @SafeVarargs@FunctionalInterface 这两个注解。这两个后续再来填坑吧。

@Override 有什么用?不加行不行?

@Override 是咱们最早接触的注解了,不加它行不行?

行!不加也行!只有重写了办法正确就能够了,要是写错了,举个例子:

public class Pet {public void eat() {System.out.println("吃...");
    }
}
public class Cat extends Pet{public void aet() {System.out.println("吃鱼...");
    }
}

这里 Cat 继承了 Pet,重写 eat() 办法,然而办法名写错了,写成 aet()

Pet pet = new Cat();
pet.eat();    // 父类援用调用子类办法

因为子类重写的办法写错办法名,那么此时父类援用调用子类办法时,就找不到子类的 eat() 这个办法,而后就只能回去父类找,这显然不是咱们想要的。

所以加上 @Override 有益处,写错办法名会有提醒,也就是说,能够确保重写的办法,确实存在于父类 / 接口中,能够无效的防止单词拼错等状况。

注解的分类

有两种分类形式:

  • 依照运行机制分类
  • 依照起源分类

依照运行机制分类(何时保留,何时不保留,某个期间保留,某个期间不保留,生命周期,Retention)

  • SOURCE——源码注解:注解 只在源码 中存在,编译成.class 文件就不存在了
  • CLASS——编译时注解:注解在 源码和 .class 文件 中都存在(如:JDK 自带注解)
  • RUNTIME——运行时注解:在 运行阶段还起作用,甚至会影响运行逻辑的注解(如:Spring 中@Autowired

依照起源分类

  • 元注解 :给注解进行注解,也就是来润饰注解的,有 4 个;这里的元注解就好比下面说过的元数据,如果还是不能很好的了解的话,就把它了解成 形容词

    • @Target({ElementType.METHOD, ElementType.TYPE})
    • @Retention(RetentionPolicy.RUNTIME)
    • @Inherited
    • @Documented

    @Target:用于申明注解的作用域,能够是

    • ElementType.CONSTRUCTOR 可作用于构造方法
    • ElementType.FIELD 字段申明
    • ElementType.LOCAL_VARIABLE 局部变量申明
    • ElementType.METHOD 办法申明
    • ElementType.PACKAGE 包申明
    • ElementType.PARAMETER 参数申明
    • ElementType.TYPE 类、接口、枚举、注解申明。

    @Retention:用于申明注解何时保留,也有人们称为生命周期,能够是

    • RetentionPolicy.SOURCE 只在源码显示,编译时会抛弃
    • RetentionPolicy.CLASS 编译时会记录到 class 中,运行时疏忽
    • RetentionPolicy.RUNTIME 运行时存在,能够通过反射读取。

    @Inherited:容许子类继承,即申明该注解会被应用了该注解的类的子类所继承。

    @Documented:应用了这个注解,那么在生成 Javadoc 的时候会蕴含注解信息。

    所以,应用这 4 个元注解,就是用来润饰咱们自定义的注解的,规定咱们自定义的注解在哪些地方能用,什么时候会保留注解信息等等。

    在 JDK 8 新加了一个元注解 @Repeatable,这个也后续再来填坑啦。

  • JDK 自带的注解
  • 常见第三方注解(Spring、MyBatis 等等)
  • 自定义注解

如何自定义注解

应用 @interface 关键字进行注解的自定义,写出你本人定义的注解~

public @interface 注解名 {
    成员变量... 以无参数无异样的形式来申明
    能够用 defalt 关键字来指定默认值
}

举个例子:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TextMessage { // 应用 @interface 关键字定义注解,注解名为 TextMessage
    String to();                            // 成员变量以无参数无异样的形式来申明
    String from() default "Jayson Tatum";    // 能够用 defalt 关键字来指定默认值
    String body();}

注意事项:

  • 注解 只有成员变量,没有办法,尽管这个变量有小括号,看起来挺像办法的。
  • 注解中定义成员变量时它的类型只能是 8 种根本数据类型外加 String、类、接口、注解、枚举及它们的数组。
  • 若注解只有一个成员变量,则该成员名必须取名为 value(),而后应用注解的时候参数间接写。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface A {String value();
}

public class B {@A("god23bin")
    public int var;
}
  • 注解能够没有成员变量,这样的注解称为标识注解。

获取注解信息

还记得结尾我说过的话吗?

注解实际上并不是程序自身,只是向编译器提供相干的程序的附加信息,也就是说注解并不会影响程序的运行。

是的,注解这样子,基本不能帮咱们做什么事件,顶多是给咱们人看的,咱们晓得它形容什么,此时它就和正文的性能很像了,然而!咱们晓得,注解不仅仅是给咱们人看的,也是给机器看的啊,那么机器是如何看的呢?这就波及到反射了,须要同个反射去获取注解的信息,进而进行下一步的操作,实现咱们想要的性能!

举个例子:

我这里自定义了一个注解 B,作用域为类、接口、枚举、注解、办法、字段(成员变量)。

注解的保留期间(Retention)是运行时(RetentionPolicy.RUNTIME),这是必须的,毕竟反射是运行时动静获取的,不选这个,就不能应用反射获取注解信息了。

该注解有两个成员变量,或者说两个属性,即 idtextid 默认 -1

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface B {int id() default "-1";
    String text();}

当初,咱们把这个注解申明到 Test 类上:

@B()
public class Test {}

那如何应用反射获取呢?咱们能够通过反射中的 Class 对象isAnnotationPresent() 办法判断该类是否应用了某个注解。

    /**
     * {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     * @since 1.5
     */
    @Override
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

而后通过它的 getAnnotation() 办法来获取 Annotation 对象

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.5
     */
    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }

或者是 getAnnotations() 办法来获取 Annotation 数组对象

    /**
     * @since 1.5
     */
    public Annotation[] getAnnotations() {return AnnotationParser.toArray(annotationData().annotations);
    }

所以,咱们能够这样获取注解信息:

@B("god23bin")
public class Test {public static void main(String[] args) {boolean isB = Test.class.isAnnotationPresent(B.class);
        if (isB) {B b = Test.class.getAnnotation(B.class);
            System.out.println("id:" + b.id());
            System.out.println("text:" + b.text());
        }
    }
}

输入后果:

id:-1
text:god23bin

同理,位于接口、属性(字段)、办法上的注解,同样能够通过反射获取。

@B(text="I got you today")
public class Test {@B(id=1, text="hello")
    public int var;

    @B(text="world")
    public void empty() {}

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {boolean isB = Test.class.isAnnotationPresent(B.class);
        if (isB) {
            // 获取类上的注解
            B b1 = Test.class.getAnnotation(B.class);
            System.out.println("1-id:" + b1.id());
            System.out.println("1-text:" + b1.text());
        }
        Field field = Test.class.getDeclaredField("var");
        field.setAccessible(true);
        // 获取字段上的注解
        B b2 = field.getAnnotation(B.class);
        if (b2 != null) {System.out.println("2-id:" + b2.id());
            System.out.println("2-text:" + b2.text());
        }
        // 获取办法上的注解
        Method method = Test.class.getDeclaredMethod("empty");
        B b3 = method.getAnnotation(B.class);
        if (b3 != null) {System.out.println("3-id:" + b3.id());
            System.out.println("3-text:" + b3.text());
        }
    }
}

输入后果:

1-id:-1
1-text:I got you today
2-id:1
2-text:hello
3-id:-1
3-text:world

好了,到这里,下面曾经晓得咱们能够通过反射获取注解信息,然而目前仅仅只是获取信息而已,如何实现其余性能呢?这个问题问的好,后续再来填坑吧,不过还是一样,离不开反射。

以上就是注解的根本内容了。

最初的最初

由自己程度所限,不免有谬误以及不足之处,屏幕前的靓仔靓女们 如有发现,恳请指出!

最初,谢谢你看到这里,谢谢你认真对待我的致力,心愿这篇博客对你有所帮忙!

你轻轻地点了个赞,那将在我的心里世界削减一颗亮堂而夺目的星!

退出移动版