概述
注解
- 前言
- 基本语法
- 元注解
- 定义注解
- 注解元素
- 使用反射操作注解
- 注解不支持继承
- 本章小结
前言
注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方式,注解是 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;
}
}
被注解标注的方法和其他的方法没有任何区别。在这个例子中,注解可以和任何修饰符共同用于方法,诸如 public、static 或 void。从语法的角度上看,注解的使用方式和修饰符的使用方式一致。
元注解
用于定义注解的注解,通常用于注解的定义上:
- @Target:中文翻译为目标,描述自定义注解的使用范围,比如:类、接口、枚举、方法等等。
ElementType有很多类型提供,可以定义你注释目标:
值 | 说明 |
---|---|
TYPE | 类、接口、注解、枚举 |
FIELD | 属性 |
MEHOD | 方法 |
PARAMETER | 方法参数 |
CONSTRUCTOR | 构造函数 |
LOCAL_VARIABLE | 局部变量(如循环变量、catch参数) |
ANNOTATION_TYPE | 注解 |
PACKAGE | 包 |
TYPE_PARAMETER | JDK1.8后新增,类型参数声明 |
TYPE_USE | JDK1.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等 |
RUNTIME | VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 |
下面分别介绍它们的区别:
@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:studyworksrccomtest>javadoc -encoding utf-8 AnnotationTest.java
正在加载源文件AnnotationTest.java...
正在构造 Javadoc 信息...
标准 Doclet 版本 1.8.0_101
正在构建所有程序包和类的树...
正在生成.comtestAnnotationTest.html...
正在生成.comtestpackage-frame.html...
正在生成.comtestpackage-summary.html...
正在生成.comtestpackage-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 就是标记注解。
注解元素
注解元素可用的类型如下所示:
- 所有基本类型(int、float、boolean等)
- 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:studyworksrccomtest>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 编程体验。
最后
以上就是风趣海燕为你收集整理的重拾Java基础知识:注解前言的全部内容,希望文章能够帮你解决重拾Java基础知识:注解前言所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复