搞懂Java高级特性---注解

1,067 阅读6分钟

这是我参与更文挑战的第2天,活动详情查看: 更文挑战

1、注解是什么?

Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制,注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据,注解对它们注解的代码没有直接影响。 怎么理解呢? 可以理解成字面意思,他就是个注解,用来注释用的,和商场里的标签一样,标记这个东西是黄瓜,标记这个是西瓜似得 我们看看Java里怎么自定义一个注解?

2、注解的定义

Java中所有的注解,默认实现Annotation接口:

package java.lang.annotation;

public interface Annotation {
    boolean equals(Object var1);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

3、元注解

元注解:在定义注解时,注解类也能够使用其它的注解声明,对注解类型进行注解的注解类,我们称之为元注解(meta-annotation),一般我们在定义自定义注解时,需要指定的元注解有两个

@Documented和@Inherited这两个元注解,前者用于被Javadoc工具提取成文档,后者表示允许子类集成父类中定义的注解

@Target 注解标记另一个注解,以限制可以应用注解的Java元素类型。目标注解指定以下元素类型之一作为其值

  • ElementType.TYPE 用于类的任何元素(类 、接口,注解,枚举)
  • ElementType.FIELD 应用于字段或属性
  • ElementType.METHOD 应用于方法级注解
  • ElementType.PARAMETER 应用于方法的参数
  • ElementType.CONSTRUCTOR 用用于构造方法
  • ElementType.LOCAL_VARIABLE 应用于局部变量
  • ElementType.ANNOTATION_TYPE 应用于注解类型
  • ElementType.PACKAGE 应用于包声明

@ Retention 注解指定标记注解的存储方式,指定注解的生效时期就用这个注解

  • RetentionPolicy.SOURCE 标记注解仅保留在 源码级别中,并被编译器忽略
  • RetentionPolicy.CLASS 标记注解在编译时由编译器保留,单JVM会忽略
  • RetentionPolicy.RUNTIME 便捷的注解由JVM保留,因此运行时环境可以使用它 编译器保留的时候 ,源码级别的时期也是能使用的,而不是单单在编译时被使用,RUNTIME的时候同理,RUNTIME时期包含了SOURCE时期和CLASS时期

4、自定义一个注解

在元注解中,我们看到,允许在使用注解时传递参数,我们也能在自定义注解中传递参数(让自定义注解的主体包含annotation type element 注解类型元素声明),看起来很像方法,可以定义可选的默认值

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS) //注解的保留时期 到编译期
@Target({ElementType.TYPE,ElementType.FIELD})//应用与类 的元素  和  属性 字段
public @interface BindView {
    String value();//没有默认值
    int age() default  1; //有默认值
}

使用

public class MainActivity extends AppCompatActivity {
//    @BindView("amszlk")
    @BindView(value = "amszlk",age=10)
    private String name;
}

使用注解时,如果定义的注解中的 类型元素 无默认值,则必须传值

5、注解应用场景

按照@Retention元注解 定义的注解存储方式,注解可以被在三种场景下使用

5.1 SOURCE:作用于源码级别的注解,可以提供给IDE语法检查,APT等场景使用

5.1.1、IDE语法检查

在Android开发中,在support-annotation和androidx.annotation中均提供了@IntDef注解,此注解的定义如下

@Retention(SOURCE)//源码级注解
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;

    /**
     * Whether any other values are allowed. Normally this is
     * not the case, but this allows you to specify a set of
     * expected constants, which helps code completion in the IDE
     * and documentation generation and so on, but without
     * flagging compilation warnings if other values are specified.
     */
    boolean open() default false;
}

Java中的枚举的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中,比常量多5到10倍的内存占用,该注解的意义在于能够取代枚举,实现方法入参限制 例: 我们定义一个方法test,此方法接收参数coder需要在,Java和JavaScript中选一个

public enum Coder {
    JAVA,JAVASCRIPT
}
public void test(Coder coder){
    
}

我们现在为了进行内存优化,不再使用枚举

public class Test {
    public static  final  int JAVA=1;
    public static final int JAVASCRIPT=2;
    
   public void test(int coder){
       
   }
}

然而此时,调用test方法,由于用的是test,无法进行类型限定,可以传1和2之外的数,这是我们可以用@IntDef增加自定义注解

    @IntDef(value={JAVA,JAVASCRIPT})//限定为JAVA和JAVASCRIPT
    @Target(ElementType.PARAMETER)//作用于参数
    @Retention(RetentionPolicy.SOURCE)//源码级别的注解
    public @interface Coder {
    }
   public void test(@Coder int coder){

   }

这样调用的时候传递的参数如果不是JAVA和JAVASCRIPT就会报错 在这里插入图片描述

5.1.2APT注解处理器

APT:Annotation Processor Tools 注解处理器,用于处理注解,编写好的Java源文件,需要经过javac编译,翻译为虚拟机能够加载解析的字节码文件Class文件,注解处理器是javac自带的一个工具,用来编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器,注册的注解处理器由javac调起,并将注解信息传递给注解处理器进行处理 注解处理器是对注解应用最广泛的场景。Glide,ARouter,ButterKnifer,Tinker,EventBus等框架中都有注解处理器的影子,这些框架中对注解定义并不是Source级别,更多的是Class级别,因为Class级别包含了Source级别

5.2CLASS

定义为CLASS的注解,会保留在class文件中,但是会被虚拟机忽略,即无法在运行期反射获取注解。此时完全符合此种注解的应用场景为字节码操作,如热修复Roubust中 字节码操作:直接修改字节码class文件达到修改代码执行逻辑的目的 举个栗子:登录拦截,在我们程序中有多处需要进行是否登录的判断,判断是否登录,是通过验证,未登录就进入登录,如果使用普通的编码方式,就是在许多方法里加上if else判断,但是有了CLASS时期的注解,我们可以使用AOP,将程序中所有功能点划分为需要登录与不需要登录两种切面,用注解来区分切面

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface Login{
        
    }
    @Login
    public void jumpAActivity(){
        
    }
    public void jumpBActivity(){
        
    }

在以上代码中,我们能够在该类所在编译的字节码中获取得注解为Login的方法,然后去操作字节码,修改class中的内容,加入if else

//Class文件
@Login
public void jumpAActivity(){
	if(this.isLogin)  {
		this.startActivity(new Intent(this,LogingActivity.class))
	} else {
		this.startActivity(new Intent(this,AActivity.class))
	}     
 } 

5.3 RUNTIME

注解保留至运行期,意味着我们能够在运行期间结合反射技术来获取注解中的所有信息 反射与动态代理点我查看

6.总结

这篇文章从注解的定义,使用和使用场景把注解学习了一遍,以后也会以别的技术结合起来再学习的,学技术不能只为了学而学,我们要把他们落地,实践起来!一起加油!希望大佬们一键三连!