赞
踩
无论是做Java后台或者Android开发,我们经常使用注解,最常用的莫过于@Override,很多流程的框架都用到了注解这个功能机制,例如Java后台开发中常用的框架Spring、MyBatis等,Android的Retrofit,Butterknife等,都是注解框架。有关注解也常常在面试中被问到,但我们了解注解的本质是什么?有哪些类别?如何自定义及使用?其中工作原理是什么?本文将做全面介绍。
关于注解首先引入官方文档的一句话:Java 注解(Annotation)用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
日常开发中新建Java类,我们使用class、interface比较多,而注解和它们一样,也是一种类的类型,用的修饰符为 @interface
作用:标识 / 解释 Java 代码。
元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。
@Retention
- @Retention(RetentionPolicy.RUNTIME)//使用该注解,只能选择其中一种属性,不能定义多个
- public @interface MyAnnotation {
- }
Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。
在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
Retention策略 :
@Target
- @Target({ElementType.FIELD,ElementType.METHOD}) //多个的时候使用{}括起来,然后用逗号分隔开
- public @interface MyAnnotation {
- }
Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型.
@Documented
- @Documented //添加文档注解
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyAnnotation {
- }
Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。
@Inherited
- @Documented
- @Inherited
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- public @interface MyAnnotation {
- }
Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,则它的子类也继承了父类的注解(子类可以同时被其他注解修饰)。
- @MyAnnotation
- public class FatherClass {
- }
-
- public class SonClass extends FatherClass {
- }
-
- public class test {
- public static void main(String[] args){
- Class<SonClass> sonClass = SonClass.class;
- MyAnnotation annotation =sonClass.getAnnotation(MyAnnotation.class);
- }
- }
上面的例子,父类加了可继承注解,子类没有被该注解修饰,但是子类获取Mynnotation注解成功,说明子类继承了父类中的MyAnnotation。
@Repeatable
Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。
Java 1.8后引进,使得作用的注解可以取多个值
下面通过例子说明其定义。
- // 1. 定义 容器注解 @RoleContainer
- // 容器注解本身也是一个注解,用于存放其他注解
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface RoleContainer {
- Role[] value();
- }
-
- // 2. 定义@Role
- // 使用@Repeatable 注解 @Role
- // 注:@Repeatable 括号中的类 = 容器注解
- @Repeatable(RoleContainer.class)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Role {
- String role() default "";
- }
-
- // 3. 在使用@Role(被@Repeatable 注解 )时,可以取多个值来解释Java代码
- // 下面注解表示:Man有多个身份
- @Role(role = "CEO")
- @Role(role = "Father")
- @Role(role = "husband")
- @Role(role = "son")
- public class Man {
- }

定义:即Java内部已经实现好的注解
Java中 内置的注解有5类,具体包括:
@Deprecated:过时注解,用于标记已过时 & 被抛弃的元素(类、方法等)
@Override:复写注解,用于标记该方法需要被子类复写
@SuppressWarnings:阻止警告注解,用于标记的元素会阻止编译器发出警告提醒
@SafeVarargs:参数安全类型注解,用于提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告,Java 1.7 后引入
- // 以下是官方例子
- // 虽然编译阶段不报错,但运行时会抛出 ClassCastException 异常
- // 所以该注解只是作提示作用,但是实际上还是要开发者自己处理问题
- @SafeVarargs // Not actually safe!
- static void m(List<String>... stringLists) {
- Object[] array = stringLists;
- List<Integer> tmpList = Arrays.asList(42);
- array[0] = tmpList; // Semantically invalid, but compiles without warnings
- String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
- }
@FunctionalInterface:函数式接口注解,数式接口 (Functional Interface) = 1个具有1个方法的普通接口,Java 1.8 后引入的新特性
- // 多线程开发中常用的 Runnable 就是一个典型的函数式接口(被 @FunctionalInterface 注解)
- @FunctionalInterface
- public interface Runnable {
-
- public abstract void run();
- }
定义:开发者自定义注解
下一节介绍如何自定义注解,及学习注解属性和如何获取注解的属性。
定义注解形式类似接口,只是在interface前面加@:
- public @interface Role {
- String role() default "";
- }
注解的本质就是一个Annotion接口。
- /**Annotation接口源码*/
- public interface Annotation {
-
- boolean equals(Object obj);
-
- int hashCode();
-
- Class<? extends Annotation> annotationType();
- }
通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,也就说为什么注解只有属性成员变量,其实他就是接口的方法,这就是为什么成员变量会有括号,不同于接口我们可以在注解的括号中给成员变量赋值。
- public @interface Role {
- //注解的属性,注解只有属性,没有方法,方法名=属性名,方法返回值 = 属性类型
- String role() default "";
-
- int id();
- }
-
- //注解括号内以 属性名=xx 形式赋值, 用,隔开多个属性
- Role(role="father" ,id =1)
-
- //若注解只有一个属性,则赋值时”value“可以省略
- Role("father")
注解属性类型可以有以下列出的类型:
在类/成员变量/方法定义前 加上 “@注解名” 就可以使用该注解。
- //表示Tony的角色是CEO
- @Role(role = "CEO")
- public class Tony {
- }
前面介绍Java自带的注解和如何自定义注解,如何使用,接下来学习如何获取注解,这是使用注解的关键,而使用注解的目的是为了获取注解属性值。
如何获取注解?当然是Java的反射技术了。
用于Java反射会带来一定的耗时,因此使用运行注解需要考虑对性能的影响。
获取注解的方法:
- /**是否存在对应 Annotation 对象*/
- public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
- return GenericDeclaration.super.isAnnotationPresent(annotationClass);
- }
-
- /**获取 Annotation 对象*/
- public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
- Objects.requireNonNull(annotationClass);
-
- return (A) annotationData().annotations.get(annotationClass);
- }
- /**获取所有 Annotation 对象数组*/
- public Annotation[] getAnnotations() {
- return AnnotationParser.toArray(annotationData().annotations);
- }
获取注解及注解属性的示例:
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Role {
- String role() default "";
- int id();
- }
-
-
- @Role(role = "CEO", id = 1)
- public class Tony {
- @Role(role = "teacher", id = 2)
- public String son;
-
- @Role(role = "doctor", id = 3)
- public String getDaughter() {
- return "daughter is doctor";
- }
- }
-
- public class TestAnnotation {
- public static void test() {
- //判断是否被注解修饰
- boolean isRoleAnnotation = Tony.class.isAnnotationPresent(Role.class);
- Log.d(TAG, "tony is decorated by RoleAnnotation:" + isRoleAnnotation);
-
- //获取类注解属性
- Role roleAnnotation = Tony.class.getAnnotation(Role.class);
- if (roleAnnotation != null) {
- Log.d(TAG, "tony role is " + roleAnnotation.role());
- }
-
- //获取方法注解属性
- Class<Tony> tony = Tony.class;
- try {
- Field field = tony.getField("son");
- roleAnnotation = field.getAnnotation(Role.class);
- if (roleAnnotation != null) {
- Log.d(TAG, "field role is " + roleAnnotation.role());
- }
-
- Method method = tony.getDeclaredMethod("getDaughter");
- roleAnnotation = field.getAnnotation(Role.class);
- if (roleAnnotation != null) {
- Log.d(TAG, "method role is " + roleAnnotation.role());
- }
- } catch (Exception e) {
-
- }
- }
- }

打印结果:
- D/TestAnnotation: tony is decorated by RoleAnnotation:true
- D/TestAnnotation: tony role is CEO
- D/TestAnnotation: field role is teacher
- D/TestAnnotation: method role is teacher
按照注解的Retention属性其应用分为3个场景:
应用场景1:测试代码
如出名的测试框架JUnit = 采用注解进行代码测试。
- public class ExampleUnitTest {
- @Test
- public void Method() throws Exception {
- ...
- }
- }
- // @Test 标记了要进行测试的方法Method()
应用场景2:解耦&简化代码,提高开发效率
Android 开发中大名鼎鼎的 IOC 框架ButterKnife,它减少了大量重复的代码。
- public class TestActivity extends AppCompatActivity {
-
- @BindView(R.id.tvTest)
- TextView tvTest;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_test);
-
- ButterKnife.bind(this);
- }
-
很牛逼的 Http 网络访问框架Retrofit,采用注解描述网络请求参数:
- public interface UserService {
- @GET("users/all")
- Call<List<UserInfo>> listUsers();
- }
-
- Retrofit retrofit = new Retrofit.Builder()
- .baseUrl("https://xxx.com/")
- .build();
-
- UserService userService = retrofit.create(UserService.class)
- Call<Response<List<UserInfo>>> call = userService.listUsers();
编译时处理——APT
编译时处理需要使用到APT技术,该技术提供了一套编译期的注解处理流程。
在编译期扫描.java文件的注解,并传递到注解处理器,注解处理器可根据注解生成新的.java文件,这些新的.java问和原来的.java一起被javac编译。
注解处理器是一个在javac编译期处理注解的工具,你可以创建注解处理器并注册,在编译期你创建的处理器以Java代码作为输入,生成文件.java文件作为输出。注解处理器不能修改已有的Java类,只能生成新的Java类。
运行时处理
通过反射技术处理运行时注解,这里参考一下反射的原理,运行时注解就是通过反射技术获取带有注解修饰的属性、方法。
注解在我们开发中很常见,会使用注解是我们必须掌握的技能。本文结合例子全面总结了注解的定义、类型,如何自定义及使用注解,注解的工作原理,及应用场景。巧用注解可以提高开发效率,写出优雅的代码,但也要注意到运行注解会带来性能问题,需要我们深入理解注解的原理,权衡使用。
>关注公众号 “码农翻身记”,回复888,免费下载。关注后,你将不定期收到优质技术及职场干货分享,希望陪伴有梦想的你一起前进。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。