文章目录
Java熟悉又陌生的特性:注解
0. 前言
遥想当年,当我第一次接触Java的注解时,心中不免泛起波澜:Java 代码还能这么写,很多功能,通过一个注解九年实现了!作为一名刚毕业不久的小白,在学校考完C仿佛还是昨日的事,印象中,C好像尚未提供注解。Java项目渐渐写了一些之后,时常还是会感叹:用注解真爽!
我们有时在阅读框架源码的时候,不难发现,源码也用了大量注解,可见注解的强大以及使用的广泛。
让我们开始学习!
学习目标
- 了解常用基本内置注解
- 尝试自定义注解
- 通过拓展资料,了解注解的原理
参考教程
优快云-注解原理优秀参考博文:https://blog.youkuaiyun.com/nanhuaibeian/article/details/121290597
1. 基本内置注解
这里列举一些常见的基本内置注解。
1.1 @Override
@Override 表示重写 ,是我们最常见,最常用的注解之一。作用于方法上,表示方法的重写。
@Override源码
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
源码解析
- @Target 表示 该注解作用于哪块区域,从此处得知,@Override作用于方法上
- @Retention用于指定注解的保留策略,从此处得知,只在源代码中存在,编译后就会丢失,因此不会产生额外开销
作用
@Override 作用于方法上,用于帮助开发者在编译时捕获一些潜在的错误。如果你错误地将一个方法标记为重写,但实际上并没有重写父类中的方法,编译器会发出警告或错误信息,提醒你检查代码逻辑。
举个例子:
public class HelloAnnotation {
@Override
public String toString() {
return "Hello Annotation";
}
/**
* 重写hashCode方法,但由于单词拼错了,加上@Override注解后,
* 编译器会出现提醒我们方法写错了,下面代码会报错
*/
@Override
public int toHashCode() {
return 1;
}
}
1.2 @Deprecated
表示这个方法已过期。这个注解已经很少用了,大家了解一下即可。
@Deprecated源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
public @interface Deprecated {
}
源码解析
- @Documented 注解用于指示这个自定义注解应该在生成的JavaDoc中进行文档化。
- @Retention(RetentionPolicy.RUNTIME) 注解指定了这个自定义注解应该在运行时保留,并且可以通过反射访问。
- @Target注解指定了该注解可以作用于构造方法、字段、局部变量、方法、包、参数和类型上。
1.3 @SuppressWarnings
源码
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
源码解析
源码上的注解意思和上文的解释一致;
但是这个SupperessWarnings内置了一个方法,这个方法返回一个String类型的数组。这个方法用于表示注解的参数
作用
用于手动忽略一些告警,具体哪些告警,由该注解的参数决定。常见的参数如下:
- deprecation:使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
- unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
- fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
- path:在类路径、源文件路径等中有不存在的路径时的警告;
- serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
- finally:任何 finally 子句不能正常完成时的警告;
- rawtypes 泛型类型未指明
- unused 引用定义了,但是没有被使用
- all:关于以上所有情况的警告。
举例
@SuppressWarnings(value = {"unchecked", "deprecation"})
public void sayHi() {
List list = new ArrayList();
}
当然,我们在日常使用中也能得知,有时我们也可以省略参数名,例如上述代码可写成:
@SuppressWarnings({ "unchecked", "deprecation" })
public void sayHi() {
List list = new ArrayList();
}
1.4 @SafeVarargs
Java 1.7 以后 的一个较新的内置注解,用于可变长参数的泛型静态方法或final修饰的方法去掉告警。这个注解也用得很少,我们了解一下即可:
@SafeVarargs源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}
举例
@SafeVarargs
public static <T> T getFirstOne(T... elements) {
return elements.length > 0 ? elements[0] : null;
}
1.5 @FunctionalInterface
函数式接口标记注解,用于标记这是一个函数式接口。
@FunctionalInterface源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface FunctionalInterface {
}
由于接口是一种特殊的数据类型,因此@Target注解的参数为Element.TYPE,表示这个注解作用于接口。
举例
@FunctionalInterface
public interface Hello {
void sayHello();
}
1.6 小结
以上是Java 常见的内置注解。其中,最常用的只有 @Override,其余注解的学习与解读,可以当作为读其他注解作准备。
2. 元注解
元用于注解、自定义注解的注解。是注解的注解。
在上面内置注解的学习中我们已经接触过。
元注解有这么几种:
@Target
@Retention
@Inherited
@Documented
@Repeatable (java1.8 新增)
2.1 @Target
指定注解的作用域。
源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
属性值
- ElementType.TYPE:能修饰类、接口或枚举类型
- ElementType.FIELD:能修饰成员变量
- ElementType.METHOD:能修饰方法
- ElementType.PARAMETER:能修饰参数
- ElementType.CONSTRUCTOR:能修饰构造器
- ElementType.LOCAL_VARIABLE:能修饰局部变量
- ElementType.ANNOTATION_TYPE:能修饰注解
- ElementType.PACKAGE:能修饰包
2.2 @Retention
指定注解的保留策略。
源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Retention {
RetentionPolicy value();
}
属性值
- RetentionPolicy.SOURCE: 注解只在源代码中存在,编译成class之后,就没了。
- RetentionPolicy.CLASS: 注解在java文件编程成.class文件后,依然存在,但是运行起来后就没了。@Retention的默认值。
- RetentionPolicy.RUNTIME: 注解在运行起来之后依然存在,程序可以通过反射获取这些信息。
2.3 @Inherited
@Inherited 是一个Java内置注解,用于标记一个注解是否可以被子类继承。当一个注解被@Inherited修饰时,子类会自动继承父类的注解。这个注解在实际应用中可以用于一些框架或库中,以便在继承关系中传递注解的信息。 例如我们的@Transactional注解就用了该元注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
......
}
2.4 @Documented
@Documented 注解添加后,我们就可以导出生成 java doc 文档了。日常开发中我们实际用得最少,篇幅有限,暂时不作细致讲解。
2.5 @Repeatable
由于默认情况下,同一个注解在同一个作用域只能出现一次。@Repeatable注解表示,由该注解修饰的注解,是否能在同一个作用域出现多次。
举个例子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RepeatableAnnotationContainer {
RepeatableAnnotation[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(RepeatableAnnotationContainer.class)
public @interface RepeatableAnnotation {
String value();
}
3. 自定义注解
学习完上述内置注解、元注解的基本概念与略微分析源码后,我们就可以自定义注解了。
通过观察发现,注解都有@interface这个关键字。
在Java中,用于定义注解的关键字是@interface。这是Java语言中专门用于声明自定义注解的关键字。通过使用@interface关键字,我们可以在代码中定义新的注解类型,并为该注解类型指定各种属性和行为。
例如,我们可以写一个自定义注解,用于标记排序方法:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SortMethod {
String value() default "ascending";
}
再例如,在前面提到过的@Repeatable 注解举得例子,就是一个自定义注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RepeatableAnnotationContainer {
RepeatableAnnotation[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(RepeatableAnnotationContainer.class)
public @interface RepeatableAnnotation {
String value();
}
以上注解用起来时:
@RepeatableAnnotation("First Annotation")
@RepeatableAnnotation("Second Annotation")
public class MyClass {
// Class body
}
4. 总结与补充
4.1 补充
注解是Java中的一种元数据形式,可以添加到类、方法、字段和参数等代码元素上。它们提供了关于代码的额外信息,并可以在编译时或运行时由工具和框架进行处理。
在Java中,注解被定义为扩展了 java.lang.annotation.Annotation 接口的接口。这意味着注解本质上是接口。当你定义一个自定义注解时,实际上是隐式地创建了一个继承了 Annotation 接口的接口。
而实现注解的原理是反射,具体详见本文开头提到的参考教程或者其他参考教程。
4.2 总结
在学习注解的过程中,由于不习惯注解本身的写法,例如@interface关键字的使用,也容易和一般的接口搞混,难以理解,因此我个人在学习注解的过程中,将注解当成一种Java特有的语法来学习,通过这样学习,结合日常频繁的使用,或许就能渐渐熟悉并适应。