注解
前言
以前学习到「注解」的时候,没有好好了解注解是如何工作的,只是晓得注解能够实现一些性能,总而言之,就是懵懵懂懂。
不过,即便你不晓得什么是注解,但必定接触过注解,比方办法的重写,在办法下面写着 @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,有时应用注解,各有各的益处,两者互相联合等,这还须要具体情况具体分析。
为什么须要学习注解?
- 很显然,咱们当初日常开发,应用到各种开源框架,常常会用到注解,不学习注解,那天然看不懂注解相干的代码。
- 看起来比拟厉害
JDK 自带的注解
@Override
:示意以后办法笼罩了父类的办法@Deprecated
:示意该办法因为平安、性能问题等,曾经 不举荐应用 了,即 曾经过期,办法上有横线,应用时会有正告。此外,在版本升级时,如果要打算删除一些办法,也通常会在前一个版本中,将该办法加上 @Deprecated,而后再在后续版本中删除。-
@SuppviseWarnings(value="unchecked")
:示意镇压正告信息(让编译器疏忽特定的编译正告)一些
value
值:unchecked
,deprecation
(疏忽 一些过期的 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
),这是必须的,毕竟反射是运行时动静获取的,不选这个,就不能应用反射获取注解信息了。
该注解有两个成员变量,或者说两个属性,即 id
和 text
,id
默认 -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
好了,到这里,下面曾经晓得咱们能够通过反射获取注解信息,然而目前仅仅只是获取信息而已,如何实现其余性能呢?这个问题问的好,后续再来填坑吧,不过还是一样,离不开反射。
以上就是注解的根本内容了。
最初的最初
由自己程度所限,不免有谬误以及不足之处,屏幕前的靓仔靓女们
如有发现,恳请指出!
最初,谢谢你看到这里,谢谢你认真对待我的致力,心愿这篇博客对你有所帮忙!
你轻轻地点了个赞,那将在我的心里世界削减一颗亮堂而夺目的星!