注解第一部分基础介绍及使用

Posted xzj_2013

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了注解第一部分基础介绍及使用相关的知识,希望对你有一定的参考价值。

注解概述

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明

在java.lang.annotation这个接口的描述中有这么一句话,用来描述『注解』

The common interface extended by all annotation types.

注解的作用

1、用于其他工具来产生额外的代码或者配置文件

 生成文档
 这是最常见的,也是Java最早提供的注解。常用的有@param、@return等。

 跟踪代码依赖性,实现替代配置文件功能
 IOC依赖注入,可以使用大量注解配置,具有很大用处。

 配合工具生成代码
 配合一些第三方的工具,可以在编译阶段生成java文件,或者在运行时在指定的位置插入一段代码
 在框架中会使用的比较多

2、提供信息给编译器或者IDE工具

  比如在编译时进行格式检查
  如@Override放在方法上,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出错误。

3、部分注解在程序运行时访问,增加程序的动态性

  一般配合反射使用

4.使代码更加简洁,方便使用
比如现在很多的框架 butterknife,retrofit都大量的使用注解封装代码

注解的基本元素

一般常用的注解可以分为三类:

  1. 一类是Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。
  2. 一类为元注解,元注解是用于定义注解的注解
  3. 一类为自定义注解,可以根据自己的需求定义注解

注解的基本元素:
@Retention(标明注解被保留的阶段)— —注解的生命周期

 RetentionPolicy.SOURCE:只在源码期有效,在编译时会被编译器丢弃,不会写入 class 文件
	
	即生命周期只存在Java源文件这一阶段,是3种生命周期中最短的注解。	当在Java源程序上加了一个
	注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时
	会把Java源程序上的源码注解给去掉。需要注意的是,在编译器处理期间源码注解还存在,即注解处
	理器Processor 也能处理源码注解,编译器处理完之后就没有该注解信息了;
    这个应用场景主要是用于APT处理 生成一些额外的辅助类,同时还可以用于一些语法检查 比如androidx中的@IntDef

 RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
 		
 		这时候就存在疑问SOURCE和Class有啥区别呢?
 		虽然把Class修改为Source编译时不会存在问题,但是有一个点很明显,就是Class中是否写入了该注解
 		而Android的打包过程是
 		Java源码 —> Class文件 —> Dex文件 —> apk
 		显然,在 Class文件 —> Dex文件我们是可以通过操作class文件获取到对应的注解信息,从而
 		在这个阶段通过asm技术动态的对我们的代码做一些处理
 		主要应用场景是用于字节码增强,比如 插桩
 		
 RetentionPolicy.RUNTIME :永久保存,可以反射获取
 
 具体的区别如下:
 运行时注解就是就是运行时运用反射,动态获取对象、属性、方法等,一般的IOC框架就是这样,可能会牺牲一点效率。
 编译时注解就是在程序编译时根据注解进行一些额外的操作,大名鼎鼎的ButterKnife运用的就是编译时注解,ButterKnife在我们编译时,就根据注解,自动生成了一些辅助类。要玩编译时注解呢,你得先依赖apt,然后自己写一个类继承AbstractProcessor,重写process方法,在里面实现如何把配置或注解的信息变成所需要的类。

@Target(标明注解使用的范围) — —注解的作用目标

 ANNOTATION_TYPE //允许被修饰的注解作用在类、接口和枚举上
 ElementType.FIELD  //注解作用于属性字段
 ElementType.METHOD //注解作用于方法
 ElementType.PARAMETER //注解作用于方法参数上
 ElementType.CONSTRUCTOR //注解作用于构造方法
 ElementType.LOCAL_VARIABLE //注解作用于在本地局部变量上
 ElementType.ANNOTATION_TYPE //允许作用在注解上就是元注解
 ElementType.PACKAGE //注解作用于包

@Inherited(标明注解可继承)

   是否可以被继承,默认为 false
   @Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型
   被用于一个class,则这个annotation将被用于该class的子类

@Documented(生成说明文档,添加类的解释 )

   是否会保存到 Javadoc 文档中
   @Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例
   如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

自定义注解

注解使用示例如下:
使用关键字@interface进行自定义注解,注解内容可以指定注解属性的类型,缺省值等
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation 

	int id() default 0;
    String value() default "";


注意,在注解的定义中 id()以及value(),其实并不是注解的方法,我们应该把它看做注解的属性,id()以及value()其实就表示注解的一个成员变量id和value,其默认值分别是0和空字符;
就这样我们定义好了一个自己的注解;
那么怎么去使用这个注解呢?
如下:

public class MainActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Utils.log();
    

    @TestAnnotation(id=1,value="zhangsan")
    public void testAno()
        Log.d("tag","testAno");
    
    @TestAnnotation(id=2)
    public void testAno3()
        Log.d("tag","testAno");
    

显然TestAnnotation 的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方,
就如我们的示例一样,写在testAno()方法上;

由于我们在TestAnnotation 中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;

注意元素的类型只能使用基本数据类型,String,Class、enum、Annotation,或者这些类型的数组类型。像Long Integer编译报错。如上代码,可以定义为int、long、String、Class、Target[]、ElementType[] 。
同时默认值不能设置为null

注解声明时,定义了默认值,那么使用时也可以不用传

还有一种情况:只有一个成员变量而且名字就叫value的时候我们就可以省略掉value=的写法,写成@ViewAnnotation(“acd”)这种写法和@ViewAnnotation(value=“acd”)是一样的效果;
那么我们的注解是这样:

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewAnnotation 
    String value() default "";


 @ViewAnnotation("acd")
    public void TestAno2()
        Log.d("tag","testAno2");
    

最后,还需要注意的一种情况是一个注解没有任何属性。那么我们连@ViewAnnotation后面的()都可以省略

我们定义了我们自己的注解后应该怎么使用呢?
先简单的说下,注解的原理其实质就是反射;

java.lang.reflect.AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响

所以我们使用注解都是通过反射使用:

public class MainActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Utils.log();
    

    @TestAnnotation(id=1,value="zhangsan")
    public void testAno()
        Log.d("tag","testAno");
    
    @TestAnnotation(id=2)
    public void testAno3()
        Log.d("tag","testAno");
    

	 public  void useAnnotation()
       
        try 
            Class classObj = getClass();
        Method[] methods = classObj.getDeclaredMethods();

        for (int i = 0; i < methods.length; i++) 
            Method method = methods[i];
           TestAnnotation ano = method.getAnnotation(TestAnnotation.class);
                if (ano!=null)
                    //表示该方法存在了该注解
                    Log.d("Eric","get Annottaion info "+ano.getid());
                
        
         catch (Exception e) 
            e.printStackTrace();
        
    



以上是关于注解第一部分基础介绍及使用的主要内容,如果未能解决你的问题,请参考以下文章

(JAVA): 注解和元注解的详细介绍,要了解,看源码时会遇到。

Java 基础SpringBoot 中 @Transactional 注解的使用与实践

第一部分:Twisted理论基础

及第二章思维导图和相关注解

Java Lambda基础——Function, Consumer, Predicate, Supplier, 及FunctionalInterface接口

Linux基础 | 分区及目录结构