注解
注解是什么?简单说注解就是一种标注(标记、标识),没有具体的功能逻辑代码。也可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
预定义的注解类型
JDK 中内置了以下注解:
- @Override
- @Deprecated
- @SuppressWarnnings
- @SafeVarargs(JDK7)
- @FunctionalInterface(JDK8)
@Override
@Override 旨在通知编译器该方法是覆盖父类中声明的方法
通过IDE快捷键实现接口方法和复写父类方法时,都会自动添加@Override注解。
@Deprecated
@Deprecated 标记已弃用的元素,不应再使用。将方法,类或字段标记为@Deprecated注解时,当用户使用该方法,类或字段时,编译器就会生成警告。
image image将方法,类或字段标记为为@Deprecated注解时,编译器都会将被标记@Deprecated注解的方法,类或字段被快捷使用时,用删除线进行修饰。
@SuppressWarnnings
@SuppressWarnnings 关闭不当的编译器警告信息。
Java语言规范列出了两个类别:deprecation 和 unchecked。"unchecked"用于抑制未经检查的警告。"deprecation" 使用了不推荐的类或方法的警告。
@SafeVarargs
@SafeVarargs注解应用于方法或构造函数时,断言代码不对其varargs参数执行可能不安全的操作。
@SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。
从JVM对象的角度来看…与Object []几乎一样。
imageimage
@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();
}
}
实现效果
image参考文档:
《 Java8 实战 》
网友评论