如果你是个Java开发者,一定用过,至少听说过注解。 注解是Java的一种手段,它的官方定义是
In the Java computer programming language, an annotation is a form of syntactic "metadata“ that can be added to Java source code.Classes, methods, variables, parameters and packages may be annotated.
看不懂吧,翻译成中文就是
注解是一种在Java源码上的附带的 metadata,类/方法/变量/参数和包都可以用到注解。
metadata有些翻译称作元数据,字面意思对于初学者来说实在不好理解,通俗的说就是一种"冗余数据",在功能之外的描述性数据。 有了这些基本概念后来看看一个注解怎么定义,怎么使用,还有原理是怎么实现的。 实现原理这块比较复杂,要涉及到class文件的数据结构,初学者可以选择性阅读这块内容,当然对进阶高级开发来说有必要了解一下。 原理这块我们下篇单独讲。
注解--Annotation, 首先有几个方面的概念需要明确
除了这两个之外还有类似于@Inhertied,@Documented 这种语法,这些高级语法我们不准备介绍,在明白了注解的基本脉络后可以自行学习。 一个注解至少需要有@Retention定义才能正常使用和工作。 回想在平时开发中见到的像 @Override,@TargetApi这些系统注解,是由系统开发者预先定义好了,我们再使用。 从这个角度出发可以把注解分成两类, 一种是已经定义好的像 @Override这种,归属于系统注解, 另外一种是 @Target 这种,归属于 元注解, 如果还没晕的话请往下看,晕了的缓一缓,先记住@Target和@Retention这两个注解。
圣人有云,read the fucking source code,下面贴个自定义注解的源码
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sexual {
String value() default "Default";
}
一行行解释, 这段代码定义了一个自定义注解 Sexual,定义它的主要方法是用 @interface修饰。 定义完之后使用这个注解的方法跟@Override这种系统注解一样,还是拿一个万年例子类Student来说,
public class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Sexual(value="male") // <- 使用注解Sexual
public static Student createStudent(String name, int age) {
return new Student(name, age);
}
public void displayInfo() {
System.out.println("name: " + this.name + " age: " + this.age);
}
}
注释的这一行就使用了@Sexual注解了。
现在回到注解的代码里来解释,@Target在参数中的是修饰对象,有以下这些
ElementType.Constructor 构造方法中
ElementType.Field 成员变量和枚举常量
ElementType.Local_Variable 局部变量
ElementType.Method 方法
ElementType.package 包
ElementType.parameter 方法参数
ElementType.type 接口,类,枚举,注解
比如@Sexual中用的是 METHOD,那么这个注解就可以用来修饰方法。
@Sexual中的Retention是 RUNTIME,除此之外还有 SOURCE, CLASS, 这三个分别表示的意思是
拿Sexual的代码来做例子,这里用的是 RUNTIME,意为着在运行时我们还可以拿到这个注解和它所附加的数据。
具体的用法,我们在下面这个代码里给出,这段代码在运行时构造一个Student对象,然后从Student中被注解的方法里拿到注解所设定的value值。
import java.lang.Class;
import java.lang.reflect.Method;
public class AnnotationDemo {
public static void main(String[] args) {
Student michael = Student.createStudent("Michael", 10);
michael.displayInfo();
Class clazz = michael.getClass();
Method[] methods = clazz.getMethods();
for(Method method : methods) {
if(method.isAnnotationPresent(Sexual.class)) {
Sexual sexual = method.getAnnotation(Sexual.class);
System.out.println("Student: " + michael.name + " sexual: " + sexual.value());
}
}
}
}
简单解释这段代码, 先 createStudent传入了参数构造了对象 michael,这里很简单。 然后我们从 michael对象中拿到它的类,并用 getAnnotation方法拿到类中所注解的内容。 如果你是第一次接触注解,那么需要留意一下,注解是针对类/文件的,它的目标并不是对象,上面的例子我们只是从对象中去拿它的类而已,如果直接通过Student拿类,也是可以的。
解释下这个demo做的事情。 Method类提供了一些提取注解的方法,
所以逻辑上是通过反射拿到各个方法之后,检查方法是否有注解,然后拿到这个注解的 value。
定义注解的时候参数是可选的,各种基本类型都可以,像上面我们定义的Sexual就带了个String类型叫value的参数。 在Student类中传入了"male",并且它的类型是RUNTIME,所以在运行时就可以拿到。对应的如果是SOURCE类型的话那在运行时就拿不到了。
上面的例子只是说明了注解的工作流程,对于注解来说应用不止于此。 很多牛逼的框架都是通过注解来工作的,比方在Android上的ButterKnife,你肯定体会过它的方便,只用注解就代替了我们传统的 findViewById等繁琐的重复劳动。 它的原理也就是在编译时通过注解拿到这些方法/对象后,替我们插入 findViewById这些方法。 所以说在通往Java高级开发的路上,总会在某个点发现注解的强大作用,如果能掌握好注解的原理,在理解高级框架上会节省很多时间呢。希望这篇文章能帮你理清注解的脉络,以后遇到了问题可以自学解决。