Java注解一二三

注解本身是一种标记,科学的叫法叫做“元数据”;
本身并不会对代码的运行产生任何影响,如果只使用注解本身,甚至和注释没有区别。

只有与反射合作,才能发挥出注解的作用。

常见注解

最常见可能就是@Override了,表示覆写了方法,看源码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

或者@Deprecated,表示废弃,看源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

注解的写法就是这样,没有过多的东西。
写法很类似接口,不过也有细微差别----比如“@",比如可以为属性设置默认值等。

基础语法

以上面的Override注解为例,声明了两个属性,一个Target,一个Retention,其实属性种类并不只有这两种。也可称之为“元注解”。
一共有4种:

属性说明
Documented是否加入JavaDoc
Retention说明保留时段(生命周期)
Target说明作用域(可用范围)
Inherited是否继承

通过源码可以看到),其中Documented和Inherited并没有属性(方法),只需要选择是否添加;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

而Target和Retention则需要相应的值才能发挥应有的作用;
首先是Target:

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

ElementType是一个枚举类,它有一系列描述作用域的值:

属性说明
ElementType.TYPE作用于类、接口、枚举等
ElementType.FIELD作用于成员变量
ElementType.METHOD作用于方法
ElementType.PARAMETER作用于参数
ElementType.CONSTRUCTOR作用于构造方法
ElementType.LOCAL_VARIABLE作用于本地(临时)变量
ElementType.ANNOTATION_TYPE作用于注解类型
ElementType.PACKAGE作用于包
ElementType.TYPE_PARAMETER作用于泛型参数
ElementType.TYPE_USE作用于泛型

再来是Retention:

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

RetentionPolicy同样是一个枚举,有三个描述时段的值:

属性说明
RetentionPolicy.SOURCE源码阶段保留,指仅在java文件中保留,编译即丢弃注解
RetentionPolicy.CLASSclass文件保留,指类加载时保留,运行时丢弃
RetentionPolicy.RUNTIME运行时保留,指一直保留

此时回过头来看@Override,发现其Target为ElementType.METHOD,即代表了只作用于方法,而Retention为RetentionPolicy.SOURCE,意思是只在java文件中保留,编译和运行时,这个注解已经不存在了。
相应地,Deprecated不仅保留到了运行时阶段,而且作用域非常大,可广泛用于变量、方法、类中。
虽然注解本身不影响运行,但不要去尝试跨越作用域使用注解,编译器会死死拦着你的,就像这样:
forbidden

这不是warning,是error。
定好了只作用于方法,在类上使用就会被红牌error。

相反,Deprecated作用域宽广,自然就可以为所欲为了:
allow
当然,它也不是真的就可以为所欲为的,毕竟我们看到它可以作用于7个域,但总共有10个域呢(Java8的情况下),其中ANNOTATION_TYPETYPE_USETYPE_PARAMETER就不在其中,也就是说,Deprecated不能注解“注解类型”“泛型参数”“泛型”三种作用域,就像这样:
forbidden2
这是TYPE_USE,也就是泛型的作用域,明显会报错;
再比如这样:
forbidden3
这是泛型参数的作用域,也就是TYPE_PARAMETER,也会报错;

注解就不说了,本文记载的就是注解。
就注解本身的四种属性而言,每个都指定了@Target(ElementType.ANNOTATION_TYPE),即代表着仅注解类型可用,包括Target自己。

作用域就是如此严格限制了注解的作用范围,而且是在编译时就已经限制了。

了解了“保留时段”与“作用域”,接下来也只有参数的写法可说了。
写法也很简单,就像写接口方法一样,不同的是可以设置默认值。
举个例子:

@Documented
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Caution {
    String value1() default "m";
	
    int value2() default 10;
	
    String value3();
}

那么在使用注解标记某个作用域时,就是这样的:

	@Caution(value1 = "kk", value2 = 2, value3 = "n")
	public void work() {
	}

值得注意的是,设置默认值后就代表着可以不用赋值了,也就是说,只需要对未设置默认值的属性赋值,也就是这样:

	@Caution(value3 = "n")
	public void work() {
	}

上面这样写就等价于:

	@Caution(value1 = "m", value2 = 10, value3 = "n")
	public void work() {
	}

这太好理解了,其实都不用记下来的…
就像重载了多个构造方法一样,如果所有属性均有默认值,自然就不需要传入参数,仅此而已。

使用方法

基础语法已经知道,注解类本身很容易就能写出来,使用注解也很容易,在作用域上添加相应注解就可以,那么怎样才能发挥作用呢?
注解是一种标记,那么标记后呢?
标记的作用,是让使用者更容易找到。
注解结合反射,就能使用反射很容易找到被注解标记的方法或类或属性,然后对其进行后续的操作。
比如定义这样一个注解类:

@Documented
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Caution {
    String value1() default "m";

    int value2() default 10;

    String value3();
}

那么使用的时候就需要通过反射去把注解找出来(这就是注解对应的主要“业务”),这里把注解和查找注解的业务逻辑写在同一个类里,就像这样:

@Caution(value3 = "type")
public class CautionTest {

	@Caution(value3 = "field")
	private String example1;

	private String example2;

	public static void main(@Caution(value1 = "ha", value3 = "params") String[] args) {
        Class ct = CautionTest.class;

		//判断类本身是否被Caution注解
        System.out.println("class has Caution: " + ct.isAnnotationPresent(Caution.class));

        System.out.println("--------------------");
        System.out.println("which method has Caution: ");

        Method[] methods = ct.getDeclaredMethods();
		for (Method m : methods) {
			//判断方法是否被Caution注解
			if (m.isAnnotationPresent(Caution.class)) {
                Caution caution = m.getAnnotation(Caution.class);
                System.out.println("    method name is " + m.getName() + ", value1=" + caution.value1() + ", value2=" + caution.value2() + ", value3=" + caution.value3());
			}

            Parameter[] parameters = m.getParameters();
			if (parameters != null && parameters.length > 0) {
				for (Parameter p : parameters) {
					//判断参数是否被Caution注解
					if (p.isAnnotationPresent(Caution.class)) {
                        Caution caution = p.getAnnotation(Caution.class);
                        System.out.println("         params type is " + p.toString() + ", value1=" + caution.value1() + ", value2=" + caution.value2() + ", value3=" + caution.value3());
					}
				}
			}
		}

        System.out.println("--------------------");
        System.out.println("field has Caution: ");

        Field[] fields = ct.getDeclaredFields();
		for (Field f : fields) {
		    //判断成员变量是否被Caution注解
			if (f.isAnnotationPresent(Caution.class)) {
                Caution caution = f.getAnnotation(Caution.class);
                System.out.println("    field name is " + f.getName() + ", value1=" + caution.value1() + ", value2=" + caution.value2() + ", value3=" + caution.value3());
			}
		}
	}

	@Caution(value3 = "method")
	public static void work() {
        System.out.println("I'm working");
	}

	public static void study(@Caution(value1 = "study", value2 = 3, value3 = "params") String subject) {
        System.out.println("I'm studying " + subject);
	}

}

可以得到输出:

class has Caution: true
--------------------
which method has Caution: 
         params type is java.lang.String[] arg0, value1=ha, value2=10, value3=params
         params type is java.lang.String arg0, value1=study, value2=3, value3=params
    method name is work, value1=m, value2=10, value3=method
--------------------
field has Caution: 
    field name is example1, value1=m, value2=10, value3=field

通过反射,就能找到注解所在的类、方法或变量,这样就已经达到了本质目的。

常见使用

许多常用的第三方开源库或框架都大量使用了注解,举个例子,EventBus。
EventBus使用@Subcribe注解标记某个方法,使得此方法可以接收post事件,而这个过程是在register时,由反射找到注解方法并建立映射关系的,而其中findUsingReflectionInSingleClass就是实现些功能的重要方法之一,来看源码:

//EventBus.java
    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

可以很清楚地看到,这里遍历了一个类的所有方法,并指定了筛选条件:1、只要public修饰的方法;2、只要参数个数为1个的方法;3、只要被Subscribe注解的方法。

其他使用注解的开源库或代码均有类似操作,注解为筛选过程提供了相当大的便利。

了解更多

  1. 注解本身是继承于Annotation接口的
public @interface Override {
}

等同于

public interface Override extends Annotation{
}

而Annotation接口位于JDK源码java.lang.annotation包内:

public interface Annotation {
    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

从方法提示中得到证据:
hint1
可以看到,注解本身是继承了Annotation的方法的。

  1. 注解本身可以自我注解的。如果需要注解其他注解类,只需要将Target设置为ANNOTATION_TYPE即可,注解可称为“元数据”,那么这种可以注解其他注解类的注解,可称为“元注解”。可以自行定义一个“元注解”。当然,因为没有写更多的使用代码(业务逻辑),这只是名义上的“元注解”。
@Self
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Self {
}
  1. 注解总是需要搭配相应的“业务”才能发挥作用的。“元注解”如Target、Retention等,内置注解如Override的“业务”都在Java的编译器javac中,所以源码中是找不到踪影的。许多时候,我们将注解对应的“业务”集合到一个工具中,这个工具就是“注解编译器”,也叫“注解处理器”。许多开源库,也是需要搭配相应的注解编译器方可使用。
    比如ButterKnife的使用,需要导入:
implementation 'com.jakewharton:butterknife:(insert latest version)'
annotationProcessor 'com.jakewharton:butterknife-compiler:(insert latest version)'
  1. Class类是实现了AnnotatedElement接口的,这个接口允许反射性地读取注解,因此类总是可以通过反射找到其注解。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值