不积跬步无以至千里,不积小流无以成江海。厚积才能薄发,水到自然渠成;

一如既往先提三个问题:注解是什么?注解怎么用?注解有什么作用?

注解是什么?

  注解就是对程序代码的补充说明,可以理解为标签,是对这段程序代码的解释,主要的目的就是提高我们的代码质量,和工作效率

注解怎么用?

1. 注解的语法

   同 classinterface 一样,注解也是一种类型,通过@interface 关键字定义,其语法如下:

public @interface TestAnnotation {}

其定义形式类似于接口,只不过在前面多了一个 @ 符号,这里表示创建了一个TestAnnotation 的注解。

2. 注解的属性

   注解的属性也叫成员变量,注解只有成员变量,没有成员方法。 注解的成员变量在注解中以无形参的方法来声明,其方法名定义了该成员变量的名字,返回类型定义了该成员变量的类型,比如:

@Inherited//该注解可以它注解的类的子类继承
@Retention(RetentionPolicy.RUNTIME)//该注解的生命周期
@Target(ElementType.TYPE)
public @interface Test {int id() default -1;String msg() default "";
}

上述代码定义了一个叫 Test 的注解,它拥有 idmsg这两个成员属性,在使用时,通过在注解的()中以value= xxx的形式赋值,多个属性通过,隔开,如下所示:

@Test(id = 2, msg = "hello annotation")
public class SuperMan {@Check("hi")int a;@Perform@Person(role = "PM")public void testMethod() {}@SuppressWarnings("deprecation")public void test() {}
}

Tips:

  • 注解中只能用public或默认(default)这两个访问权修饰;
  • 成员属性只能是八大基本类型byte,short,char,int,long,float,double,booleanString,Enum,Class,annotations等数据类型,以及这一些类型的数组;
  • 如果只有一个成员属性,建议使用value()替代
  • 注解属性必须有确认的值,可以在定义注解时使用default 指定,或者在使用注解时指定,非基本类型的注解元素的值不可为null,常使用空字符串或负数作为默认值。
/***自定义注解MyAnnotation*/
@Target(ElementType.TYPE) //目标对象是类型
@Retention(RetentionPolicy.RUNTIME) //保存至运行时
@Documented //生成javadoc文档时,该注解内容一起生成文档
@Inherited //该注解被子类继承
public @interface MyAnnotation {public String value() default ""; //当只有一个元素时,建议元素名定义为value(),这样使用时赋值可以省略"value="String name() default "devin"; //Stringint age() default 18; //intboolean isStudent() default true; //booleanString[] alias(); //数组enum Color {GREEN, BLUE, RED,} //枚举类型Color favoriteColor() default Color.GREEN; //枚举值
}

3. 注解的应用

注解的分类

  • 根据成员个数分类
    1. 标记注解
       没有定义成员的注解类型,自身就代表了某类信息,比如@override
    2. 单成员注解
       只定义了一个成员,比如@SuppressWarnings定义了一个成员String[] value,使用value={...}大括号来声明数组值,一般也可以省略“value=”
    3. 多成员注解
       定义了多个成员,使用时以name=value对分别提供数据
  • 根据用途和功能分类
    1. 元注解
       元注解,可以理解为注解的注解,即解释注解信息的一些特殊标签,它们主要的功能是确定注解的生命周期@Retention,范围@Target,是否被子类继承@Inherited,是否可重复@Repeatable,是否生成到javac文档中@Documented
  • @Retention
       Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

它的取值如下:
   - RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
   - RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
   - RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

   @Retention 相当于给一个注解盖了一张时间戳,时间戳指明了注解的时间周期。

  • @Target
      Target 是目标的意思,@Target 指定了注解运用的地方。当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。

类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值

  - ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
  - ElementType.CONSTRUCTOR 可以给构造方法进行注解
  - ElementType.FIELD 可以给属性进行注解
  - ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  - ElementType.METHOD 可以给方法进行注解
  - ElementType.PACKAGE 可以给一个包进行注解
  - ElementType.PARAMETER 可以给一个方法内的参数进行注解
  - ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

  • @Inherited
      Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。 比如:
@Inherited//该注解可以它注解的类的子类继承
@Retention(RetentionPolicy.RUNTIME)//该注解的生命周期
@Target(ElementType.TYPE)
public @interface Test {int id();String msg();
}@Person(role = "产品经理")
@Person(role = "PM")
@Test(id = 2, msg = "hello annotation")
public class SuperMan {@Check("hi")int a;@Perform@Person(role = "PM")public void testMethod() {}@SuppressWarnings("deprecation")public void test() {}
}public class Man extends SuperMan {
}public class TestClass {public static void main(String[] args) {//首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解boolean hasAnnotation = SuperMan.class.isAnnotationPresent(Test.class);if (hasAnnotation) {//返回指定类型的注解Test annotation = SuperMan.class.getAnnotation(Test.class);System.out.println("id:" + annotation.id());System.out.println("msg:" + annotation.msg());//返回注解到这个元素上的所有注解。Annotation[] annotations = SuperMan.class.getAnnotations();for (Annotation an : annotations) {System.out.println("class SuperMan annotation:" + an.annotationType().getSimpleName());}}//首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解boolean manHasAnnotation = Man.class.isAnnotationPresent(Test.class);if (manHasAnnotation) {//返回指定类型的注解Test annotation = Man.class.getAnnotation(Test.class);System.out.println("Man id:" + annotation.id());System.out.println("Man msg:" + annotation.msg());//返回注解到这个元素上的所有注解。Annotation[] annotations = Man.class.getAnnotations();for (Annotation an : annotations) {System.out.println("class Man annotation:" + an.annotationType().getSimpleName());}}
}

结果:

注解 Test@Inherited 修饰,之后类 SuperMan@Test 注解,类 man 继承 SuperMan ,类 man也拥有 @Test 这个注解。

  • @Repeatable
    Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。(注:只有在java 1.8 中才能如下使用)

    • 什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
    • 举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {Person[] value();
}@RequiresApi(api = Build.VERSION_CODES.N)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Persons.class)
public @interface Person {String role() default "";
}@Person(role = "产品经理")
@Person(role = "PM")
@Test(id = 2, msg = "hello annotation")
public class SuperMan {@Check("hi")int a;@Perform@Person(role = "PM")public void testMethod() {}@SuppressWarnings("deprecation")public void test() {}
}

注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解
什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。
我们再看看代码中的相关容器注解。

@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {Person[] value();
}

  按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组**,注意它是数组。编译环境为 jdk 1.8,低于1.8 会报错
如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。

  • @Documented
    它的作用是在生成javadoc文档的时候将该Annotation也写入到文档中

2. 内置注解

  • @Override 限定重写父类方法
  • @Deprecated用于标记已过时
  • @SuppressWarnnings 抑制编译器警告

3. 自定义注解
使用@interface自定义注解,在定义注解时,不能继承其他的注解或接口。比如:

/***自定义注解MyAnnotation*/
@Target(ElementType.TYPE) //目标对象是类型
@Retention(RetentionPolicy.RUNTIME) //保存至运行时
@Documented //生成javadoc文档时,该注解内容一起生成文档
@Inherited //该注解被子类继承
public @interface MyAnnotation {public String value() default ""; //当只有一个元素时,建议元素名定义为value(),这样使用时赋值可以省略"value="String name() default "devin"; //Stringint age() default 18; //intboolean isStudent() default true; //booleanString[] alias(); //数组enum Color {GREEN, BLUE, RED,} //枚举类型Color favoriteColor() default Color.GREEN; //枚举值
}

注解的获取

我们主要通过反射来获取运行时注解,常用API 如下:

<T extends Annotation> T getAnnotation(Class<T> annotationClass) :返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;Annotation[] getDeclaredAnnotation(Class<T>):返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解;Annotation[] getAnnotations():返回该程序元素上存在的所有注解;Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;Annotation[] getAnnotationsByType(Class<T>):返回直接存在于此元素上指定注解类型的所有注解;Annotation[] getDeclaredAnnotationsByType(Class<T>):返回直接存在于此元素上指定注解类型的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注解;boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false;

例如:

public class TestClass {public static void main(String[] args) {//首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解boolean hasAnnotation = SuperMan.class.isAnnotationPresent(Test.class);if (hasAnnotation) {//返回指定类型的注解Test annotation = SuperMan.class.getAnnotation(Test.class);System.out.println("id:" + annotation.id());System.out.println("msg:" + annotation.msg());//返回注解到这个元素上的所有注解。Annotation[] annotations = SuperMan.class.getAnnotations();for (Annotation an : annotations) {System.out.println("class SuperMan annotation:" + an.annotationType().getSimpleName());}}//首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解boolean manHasAnnotation = Man.class.isAnnotationPresent(Test.class);if (manHasAnnotation) {//返回指定类型的注解Test annotation = Man.class.getAnnotation(Test.class);System.out.println("Man id:" + annotation.id());System.out.println("Man msg:" + annotation.msg());//返回注解到这个元素上的所有注解。Annotation[] annotations = Man.class.getAnnotations();for (Annotation an : annotations) {System.out.println("class Man annotation:" + an.annotationType().getSimpleName());}}try {Field a = SuperMan.class.getDeclaredField("a");a.setAccessible(true);//获取一个成员变量上的注解Check check = a.getAnnotation(Check.class);if (null != check) {System.out.println("check value:" + check.value());}Method testMethod = SuperMan.class.getDeclaredMethod("testMethod");if (null != testMethod) {Annotation[] ans = testMethod.getAnnotations();for (Annotation an : ans) {System.out.println("method testMethod annotation:" + an.annotationType().getSimpleName());}}} catch (NoSuchFieldException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}}}

运行结果:

注解的使用

栗子:

Android 中的 findViewById 可以用注解这样写:

  @BindView(R.id.hello_tv)TextView helloTv;

点击事件可以这样写:

    @OnClick({R.id.toast_btn, R.id.change_btn})public void onClick(View view) {switch (view.getId()) {case R.id.toast_btn:Toast.makeText(MainActivity.this, "click toast button", Toast.LENGTH_SHORT).show();break;case R.id.change_btn:helloTv.setText(getResources().getString(R.string.changebtn));break;default:Toast.makeText(MainActivity.this, "default is called ", Toast.LENGTH_SHORT).show();break;}}
BindView

  这里我们定义了一个 @BindView 注解,因为是使用在属性上的,所以声明Targe 注解为 Field,又因为是在程序运行中使用,故Retention 指定为 RUNTIME,这里我们需要传入了控件id,所以需要再定义一个int 类型的属性接收,完整代码如下所示:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {int value();
}
OnClick
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {int[] value();//setOnClickListener 为点击事件的set 方法默认名String listenerSetter() default "setOnClickListener";//OnClickListener 点击事件监听的类型Class listenerType() default View.OnClickListener.class;//点击事件的默认方法名String methodName() default "onClick";}
ViewHandler

  点击事件动态代理类

public class ViewHandler implements InvocationHandler {private Object mObject;private String mMethodName;private Method mMethod;public ViewHandler(Object object, String methodName, Method method) {mObject = object;this.mMethodName = methodName;this.mMethod = method;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals(mMethodName)) {return mMethod.invoke(mObject, args);}return null;}
}

现在我们已经可以拿到控件的id 和注解,但此时我们还不能直接使用这个注解,因为它和我们的控件还没什么关联,现在我们通过反射来实现注解的注入。如下:

public class AnnoUtils {private static final String TAG = "AnnoUtils";public static void inject(Activity activity) {if (null == activity) {return;}try {//获取注入的 activity 的Class 对象Class clazz = activity.getClass();//获取该 Aty 的所有属性Field[] fields = clazz.getDeclaredFields();if (null == fields || fields.length <= 0) {return;}//遍历所有属性for (Field field : fields) {//判断当前字段是否支持 BindView 注解if (field.isAnnotationPresent(BindView.class)) {//获取注解对象BindView annotation = field.getAnnotation(BindView.class);if (null != annotation) {//获取注解内容int id = annotation.value();View view = activity.findViewById(id);if (view != null) {//把view赋给字段filedfield.setAccessible(true);field.set(activity, view);}}}}Method[] methods = clazz.getDeclaredMethods();if (null == methods || methods.length <= 0) {return;}for (Method method : methods) {if (method.isAnnotationPresent(OnClick.class)) {OnClick annotation = method.getAnnotation(OnClick.class);if (annotation != null) {//获取所有的idint[] ids = annotation.value();for (int id : ids) {//找到相应的viewView view = activity.findViewById(id);if (view != null) {method.setAccessible(true);String listenerSetter = annotation.listenerSetter();Class listenerType = annotation.listenerType();String methodName = annotation.methodName();ViewHandler viewHandler = new ViewHandler(activity, methodName, method);//获取onClickListener代理对象Object onClickListener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, viewHandler);//获取view的setOnClickListener方法Method setOnListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);//实现setOnClickListener方法setOnListenerMethod.invoke(view,onClickListener);}}}}}} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}
}

具体意思代码中都有注释,就不多说了,接着看下我们的Activity代码

public class MainActivity extends AppCompatActivity {@BindView(R.id.hello_tv)TextView helloTv;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);AnnoUtils.inject(this);helloTv.setText(getResources().getString(R.string.change_by_annotation));}@OnClick({R.id.toast_btn, R.id.change_btn})public void onClick(View view) {switch (view.getId()) {case R.id.toast_btn:Toast.makeText(MainActivity.this, "click toast button", Toast.LENGTH_SHORT).show();break;case R.id.change_btn:helloTv.setText(getResources().getString(R.string.changebtn));break;default:Toast.makeText(MainActivity.this, "default is called ", Toast.LENGTH_SHORT).show();break;}}
}

XML 代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/hello_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/change_btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/change"android:textAllCaps="false"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@+id/hello_tv" /><Buttonandroid:id="@+id/toast_btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/toast"android:textAllCaps="false"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@+id/change_btn" /></androidx.constraintlayout.widget.ConstraintLayout>

效果图如下:

GitHub demo链接

注解有什么作用?

  • 编写文档:通过代码里标识的元数据生成文档。
  • 代码分析:通过代码里标识的元数据对代码进行分析。
  • 编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查。
  • 通过不同注解明规范功能和一些统一的操作,比如retrofit、Junit、dagger2
    个人思考:
      注解它的主要功能就是提供它承载的信息。其实我们在项目开发中,主要有几个指标,一是开发效率要高,二是代码质量要高,三是开发完项目的维护成本要低,其实注解就能很好的承担这些角色,比如提高开发效率的RertofitButterknife 这些框架中的注解,提高代码质量的有Junit,降低维护成本的比如内置注解@Deprecated@Override这些,他们都是为了帮助我们理解代码,分析代码,统一规范行为,提高代码质量,因为一个无法避免的问题就是随着项目的迭代,项目的可读性、可靠性会下降,它的维护开发成本会提升,注解是解决这些问题的一个很好的方式,但是注解的提取需要借助于 Java 的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本。

参考文章:
https://blog.csdn.net/vv_bug/article/details/64500453
https://blog.csdn.net/briblue/article/details/73824058

Java 夯实基础之注解相关推荐

  1. java夯实基础系列:反射

    一.反射定义&通俗理解   反射之中包含了一个"反"的概念,所以要想解释反射就必须先从"正"开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类 ...

  2. java夯实基础-基本数据类型

    学习任何一门语言,基础永远都是最重要的.本文测试java中八种基本数据类型(int,short,long,float,double,char,boolean,byte),包括其占用空间大小,能够存储的 ...

  3. java夯实基础:final脑图

    final的作用从两个方面理解:基本的锁定功能,帮助JVM实现效率和安全.关于final的解释非常多,本文仅做整理的总结. 基本用法:锁定 final的锁定功能通过final修饰的对象来具体实现,fi ...

  4. java 夯实基础_夯实基础-java equals使用,和 == 的区别

    在研究hashcode的用法,发现自己对equals的理解还不够深,废话少说,继续专研,记录如下: 要想搞清楚equals和==的区别,就先要搞清楚JAVA的数据类型: java的数据类型只要分两大类 ...

  5. java夯实基础-关键字

    本文总结了java中51个关键字,分别对他们的含义.作用有一定说明. 1.访问权限 public    共有权限       可跨包访问 protected   保护权限      同一包可访问:不同 ...

  6. java夯实基础-输入输出流

    本文测试java中的字节流.字符流.缓冲流.随机流.文件锁. 一.字符流:Reader和Writer package com.wllfengshu.test;import java.io.File; ...

  7. java夯实基础-运算符

    本文测试了  算术运算符: + - * / %   +=  -=    *=   /=   %= :逻辑运算符: &&  ||  !    &   |   :关系运算符:< ...

  8. final关键字_夯实基础:Java中final关键字的几种用法

    导语 在java的关键字中,static和final是两个我们必须掌握的关键字.不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构. 关于static请查 ...

  9. 黑马就业班(01.JavaSE Java语言基础-11.Java基础加强)——基础加强:Junit单元测试、反射、注解

       1.Junit单元测试 测试分类: 1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值. 2. 白盒测试:需要写代码的.关注程序具体的执行流程. Junit使用:白盒测试 步骤: ...

  10. Java学习路线-夯实基础

    Java学习路线-夯实基础 第一部分:网络与操作系统 第二部分:数据结构与算法 第三部分:Java基础 第四部分:Java设计模式 第五部分:数据库 第六部分:Redis 第七部分:并发与多线程 第八 ...

最新文章

  1. QIIME 2教程. 15样品分类和回归q2-sample-classifier(2020.11)
  2. 【 MATLAB 】信号处理工具箱的信号产生函数之 square 函数简记
  3. pytorch 归一化与反归一化
  4. Django 的视图层
  5. 用C语言实现控制台模拟显示时
  6. Django基础篇之MVC与MTV模型
  7. 用我对HTML的点点理解来做个简单的百度首页
  8. MongoDB初学者教程
  9. omnicppcomplete php,vim 中OmniCppComplete的安装和使用
  10. label之间展示间距_工法样板如何做?碧桂园质量工法样板展示区做法标准
  11. QCA9531修改寄存器值控制GPIO
  12. Android webview缓存机制
  13. 输出0~1000内的质数 C语言
  14. 1 10000以内的质数表C语言,求1万以内的质数表,有急用
  15. 程序员面试时会遇上哪些问题
  16. 笔记:Linux系统调用在文件中的分布情况
  17. Docker容器内部无法访问外网原因之一
  18. 剑走偏锋——老女人教你另类情人节攻略
  19. linux 磁盘碎片整理
  20. 梦幻模拟战手游服务器维护,梦幻模拟战手游11月21日更新公告 执行者降临限时召唤开启[多图]...

热门文章

  1. 关于计算机病毒的试题,计算机病毒测试题.doc
  2. lockdir_59103.exe 怎样解密?
  3. 游戏服务器高性能设计-游戏设计与开发(3)
  4. 「建站指南」小白搭建网站一共分几步?
  5. redhat下载镜像官方地址
  6. Mybatis之select元素
  7. 继电保护原理4-自动重合闸
  8. Djangobook
  9. [Qt]一个关于galgame的练手项目的总结
  10. xmpp即时通讯协议的特性---长处和缺点!