Java注解知识梳理与简单使用

Java注解系列文章:

注解

         注解是什么?简单说注释就是一种标注(标记、标识),没有具体的功能逻辑代码。也可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

预定义的注解类型

JDK 中内置了以下注解:

  • @Override
  • @Deprecated
  • @SuppressWarnnings
  • @SafeVarargs(JDK7)
  • @FunctionalInterface(JDK8)

@Override

    @Override 旨在通知编译器该方法是覆盖父类中声明的方法

通过IDE快捷键实现接口方法和复写父类方法时,都会自动添加@Override注解。

@Deprecated

    @Deprecated 标记已弃用的元素,不应再使用。将方法,类或字段标记为@Deprecated注释时,当用户使用该方法,类或字段时,编译器就会生成警告。

    将方法,类或字段标记为为@Deprecated注释时,编译器都会将被标记@Deprecated注释的方法,类或字段被快捷使用时,用删除线进行修饰。

@SuppressWarnnings

    @SuppressWarnnings 关闭不当的编译器警告信息。

    Java语言规范列出了两个类别:deprecation 和 unchecked。"unchecked"用于抑制未经检查的警告。"deprecation" 使用了不推荐的类或方法的警告。

@SafeVarargs

    @SafeVarargs注释应用于方法或构造函数时,断言代码不对其varargs参数执行可能不安全的操作。

    @SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。

    从JVM对象的角度来看…与Object []几乎一样。

@FunctionalInterface

    @FunctionalInterface 声明接口是函数式接口。

    你用@FunctionalInterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。

    什么是函数式接口?函数式接口就是只定义一个抽象方法的接口,但是可以有多个默认方法或静态方法的接口。

    Java 8允许在接口内声明静态方法默认方法。默认方法是指提供接口方法的默认实现,用default关键字进行修饰。

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
复制代码

    Java8的谓词接口Predicate,其本身的除了唯一的抽象方法外,还定义了默认方法和静态方法。

元注解

适用于其他注释的注释称为元注释。在java.lang.annotation中定义了几种元注释类型。

  • @Retention
  • @Documented
  • @Target
  • @Inherited
  • @Repeatable(JDK8)

@Retention

    @Retention 声明注释的保留策略。

查看Retention的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
复制代码

    由源码得知,Retention的值是一个RetentionPolicy类型的变量,而RetentionPolicy是一个枚举值,其值包括:

  • RetentionPolicy.SOURCE    标记的注释仅保留在源级别中,编译器将丢弃该注释。
  • RetentionPolicy.CLASS    注释将由编译器记录在class文件中 但在运行时不需要由JVM保留。
  • RetentionPolicy.RUNTIME    注释将由编译器记录在class文件中,并在运行时由JVM保留,因此可以反射性地读取它们。

    如果注释类型声明中不存在Retention注释,则Retention默认为 RetentionPolicy.CLASS

@Documented

    无论何时使用指定的注释,都应使用Javadoc工具记录这些元素。 如果使用Documented注释类型声明,则其注释将成为带注释元素的公共API的一部分。

@Target

     @Target注释用于限制可以应用该注释的Java元素类型。

查看Target的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
复制代码

    由源码得知,Target的值是一个ElementType类型的数组变量,即可以同时设置多个值。而ElementType是一个枚举值,其值包括: Target注释指定以下元素类型之一作为其值:

  • ElementType.TYPE    应用于类,接口(包括注释类型)或枚举声明。
  • ElementType.FIELD    应用字段声明(包括枚举常量)。
  • ElementType.METHOD    应用于方法声明。
  • ElementType.PARAMETER    应用于正式参数声明。
  • ElementType.CONSTRUCTOR    应用于构造函数声明。
  • ElementType.LOCAL_VARIABLE    应用于局部变量声明。
  • ElementType.ANNOTATION_TYPE    应用于注释类型声明。
  • ElementType.PACKAGE    应用于包声明。
  • ElementType.TYPE_PARAMETER    应用于类型变量的声明语句前。(JDK8)
  • ElementType.TYPE_USE    应用于所有使用类型的任何语句(如:泛型,类型转换等)(JDK8)

    ElementType.TYPE_PARAMETER 和 ElementType.TYPE_USE属于Java 8的新特性,具体看下面Java8 注释新特性。

@Inherited

     @Inherited注释表明注释类型可以从超类继承。当用户查询注释类型并且该类没有此类型的注释时,将查询类的超类以获取注释类型。将重复此过程,直到找到此类型的注释,或者到达类层次结构(对象)的顶部。如果没有超类具有此类型的注释,则查询将指示相关类没有此类注释。此注释仅适用于类声明。

Java8 注释新特性

Java 8在两个方面对注解机制进行了改进,分别为:

  • 可以定义重复注解
  • 可以为任何类型添加注解

类型注解(Type Annotation)

    在Java 8之前,只有声明可以被注解。Java 8中,注解可以写在使用类型的任何地方,例如括new操作符、类型转换、instanceof检查、泛型类型参数,以及implements和throws子句。例如:

//列表泛型
List<@NonNull Car> cars = new ArrayList<>(); 
//对象类型转化时
myString = (@NonNull String) str;
//使用 implements 表达式时
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
 }
 //使用 throws 表达式时
 public void validateValues() throws @Critical ValidationFailedException{
  }
复制代码

    定义一个类型注解(Type Annotation)的方法与普通的 Annotation 类似,只需要指定 Target 为 ElementType.TYPE_PARAMETER 或者 ElementType.TYPE_USE,或者同时指定这两个 Target。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public  @interface daqiAnnotation {
}
复制代码
  • ElementType.TYPE_PARAMETER 表示这个注释可以用在类型变量的声明语句前。

  • ElementType.TYPE_USE 表示这个注释可以用在所有使用类型的任何语句中(如:泛型,类型转换等)

    Java 8 通过引入类型注解(Type Annotation),使得开发者可以在更多的地方使用 Annotation,从而能够更全面地对代码进行分析以及进行更强的类型检查。

重复注解(Repeating Annotation)

    Java8 之前禁止对同样的注解类型声明多次。在实际应用中,可能会出现需要对同一个声明式或者类型加上相同的 Annotation(包含不同的属性值)的情况。

@interface Author { String name(); }

@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")
class Book{ }
复制代码

可以声明一个新的注解,其包含希望重复的注解数组。

@interface Author { String name(); }

@interface Authors {
 Author[] value();
}

@Authors({ @Author(name="Raoul"), @Author(name="Mario") ,@Author(name="Alan")})
class Book{} 
复制代码

Java8 之后,当一个注解在设计之初就是可重复的,可以通过两种途径实现:

  • 将注解标记为@Repeatable
  • 提供一个注解的容器(即Java8之前的实现方式)

@Repeatable示例:

@Repeatable(Authors.class)
@interface Author { String name(); } 

@interface Authors {
 Author[] value();
} 

@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")
class Book{ } 
复制代码

        编译时,Book类会被认为使用了@Authors({@Author(name="Raoul"), @Author(name =”Mario”), @Author(name=”Alan”)})的形式进行注解。所以,可以把重复注解(Repeating Annotation)看成是一种语法糖,它提供了Java程序员之前惯用的功能。

        由于兼容性的缘故,重复注解(Repeating Annotation)并不是所有新定义的 Annotation 的默认特性,需要开发者根据自己的需求决定新定义的 Annotation 是否可以重复标注。

自定义注解

以android最为熟悉的findVIewById 和 onClick为例,定义两个运行时保存的注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface findViewById {
    int value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface onClickById {
    int value();
}
复制代码

        由于onClickById 和 findViewById 的元注解Retention的值为RetentionPolicy.RUNTIME,则表示在程序运行时,可以获取到该注解。

        通过注解中携带的value,对属性或方法进行反射,从而实现属性初始化和点击事件绑定的目的。

public class daqiAnnotationUtils {

    public static void inject(Activity activity) {
        injectFiled(activity);
        injectEvent(activity);
    }

    private static void injectFiled(Activity activity){
        //获取Activity的所有属性
        Field[] fields = activity.getClass().getDeclaredFields();
        //寻找有findViewById注解的属性
        for (Field field : fields) {
            findViewById viewById =  field.getAnnotation(findViewById.class);
            if(viewById != null){
                //通过findViewById注解中的值,通过activity#findViewById找到对应的View
                View view = activity.findViewById(viewById.value());
                //设置可以反射私有变量
                field.setAccessible(true);
                try {
                    //将获取到的view赋值到对应的变量中。
                    field.set(activity,view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void injectEvent(final Activity activity){
        //获取Activity的所有方法
        Method[] methods = activity.getClass().getDeclaredMethods();
        //寻找有onClickById注解的属性
        for (Method method : methods) {
            onClickById clickById = method.getAnnotation(onClickById.class);
            if (clickById != null){
                //通过onClickById注解中的值,通过activity#findViewById找到对应的View
                final View view = activity.findViewById(clickById.value());
                if (view != null) {
                    final Method mMethod = method;
                    //设置View#setOnClickListener
                    view.setOnClickListener(new View.OnClickListener(){
                        @Override
                        public void onClick(View v) {
                            try {
                                mMethod.setAccessible(true);
                                //反射执行方法
                                mMethod.invoke(activity);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }
    }
}
复制代码

        定义两个个TextView,一个用作展示文字,对应的id为R.id.nameText;一个用作点击按钮,对应id为R.id.textBtn。

        通过daqiAnnotationUtils#inject(Activity)初始化activity中有findViewById注解的变量,并将有onClickById的方法与其对应的组件实现点击监听的绑定。

public class daqiActivity extends FragmentActivity {

    @findViewById(R.id.nameText)
    private TextView mName;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_daqi);

        //初始化注解的变量和方法
        daqiAnnotationUtils.inject(this);

        //修改名称
        mName.setText("daqi");
    }

    @onClickById(R.id.textBtn)
    public void toastName(){
        Toast.makeText(daqiActivity.this,
                "daqi",Toast.LENGTH_SHORT).show();
    }
}
复制代码

实现效果

参考文档:

甲骨文-Java注解文档

IBM-Java 8 Annotation 新特性

《 Java8 实战 》

猜你喜欢

转载自juejin.im/post/5cd8e930f265da0380438dad
0条评论
添加一条新回复