Java注解(Annotation)
Java注解需要配合反射来使用
一、注解的定义及其解释
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
1.快速理解定义
注解如同标签可以贴在各个地方
可供参考的博客
二、注解的语法
1.注解通过 @interface 关键字进行定义。
public @interface myAnnotation
{
}
2.元注解及其使用
- 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
- 元注解有
@Retention、@Documented、@Target、@Inherited、@Repeatable
5 种。@Retention: 定义注解的保留策略
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含 @Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得, @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target:定义注解的作用目标
源码为:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
@Repeatable :Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 的新特性
为什么注解需要可重复?
看现实世界中的例子:一个人可以贴上输入同一类型的多张标签。这样可以更加真实的反应现实世界中的事物(面向对象思想)
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="player")
public class Me{
}
使用举例
定义带有元注解的注解
注解的属性用[返回值类型 ]名称 ()
来定义;可以为其设置默认值
注意:只用名称为value的属性可以通过 @注解名(“xxxx”)为其赋值;一般通过@注解名(value=”xxxx”)来使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationOne {
String value() default "an";
}
使用(对某个类进行注解)
//以下两种方法等价(因为其有value这个属性所以可以这么写)
@AnnotationOne("ABCD")
public class TestgetAnnotation {
}
@AnnotationOne(value="ABCD")
public class TestgetAnnotation2 {
}
三、注解的提取
需要反射机制作为基础;注解通过反射获取。
1.首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
2.然后通过 getAnnotation() 方法来获取 Annotation 对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
3.或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
举例
Check.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
String an();
}
AnnotationOne.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//@Target(ElementType.TYPE)//元注解
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationOne {
String value() default "an";
}
TestgetAnnotation.java
import java.lang.reflect.Field;
@AnnotationOne("NMY")
public class TestgetAnnotation {
@Check(an = "ABC")
String aString="ABC";
public static void main(String[] args) {
// 通过反射方式(3中方式) 获取注解(注解的提取)
// 通过Class类的静态方法 反射
try {
Class<?> cTestgetAnnotation = Class.forName("TestgetAnnotation");// 获取反射类
boolean isAnnotation = cTestgetAnnotation.isAnnotationPresent(TestOne.class);// 通过反射类调用isAnnotation方法
printAnnotation(isAnnotation);// 调用打印方法
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.通过类名反射
boolean hasAnnotation = TestgetAnnotation.class.isAnnotationPresent(TestOne.class);
printAnnotation(hasAnnotation);
// 3.通过对象名.getClass()反射
TestgetAnnotation testgetAnnotation = new TestgetAnnotation();
boolean isAno = testgetAnnotation.getClass().isAnnotationPresent(TestOne.class);
printAnnotation(isAno);
//获取字段注解
System.out.println("字段注解测试");
testGetFieldAnnotation();
}
public static void printAnnotation(boolean isAn) {
if (isAn) {
TestOne testOne = TestgetAnnotation.class.getAnnotation(TestOne.class);
System.out.println(testOne.value());
} else {
System.out.println("no");
}
}
public static void testGetFieldAnnotation() {
try {
Class<?> cTestgetAnnotation = Class.forName("TestgetAnnotation");// 获取反射类
Field field = cTestgetAnnotation.getDeclaredField("aString");
field.setAccessible(true);
Check fieldAnnotationWithString = field.getAnnotation(Check.class);
if(fieldAnnotationWithString!=null) {
//猜想的功能:用于检查该字段的内容 是否和注解提供的内容一致?
Object obj = cTestgetAnnotation.newInstance();
String string=(String)field.get(obj);
System.out.println("annotation -String"+fieldAnnotationWithString.an());
System.out.println("field - String"+string);
if(fieldAnnotationWithString.an().equals(string)) {
System.out.println("一致");
}else {
System.out.println("不一致");
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果
NMY
NMY
NMY
字段注解测试
annotation -StringABC
field - StringABC
一致
四、注解优点及其应用场景
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
注解应用实例
注解运用的地方太多了,因为我是 Android 开发者,所以我接触到的具体例子有下:
注解有什么用,给谁用?给 编译器或者 APT 用的。
注解应用实例
JUnit
JUnit 这个是一个测试框架,典型使用方法如下:
@Test 标记了要进行测试的方法 addition_isCorrect().
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
ButterKnife
ButterKnife 是 Android 开发中大名鼎鼎的 IOC 框架,它减少了大量重复的代码。
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_test)
TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
Dagger2
也是一个很有名的依赖注入框架。
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Retrofit
Http 网络访问框架
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
五、总结
1.注解的应用场景
生成doc文档;
编译器类型格式检查;
运行时处理如注入依赖等
2.自定义注解注意事项
注解的类型是@interface,里面只有以“无形参的方法”的形式来声明的成员变量。其方法名定义了成员变量的名字,返回值类型定义了该成员变量的类型。在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解可以使用default指定默认值,但只有一个成语变量时,其属性名为value,目标处使用时可省略该成员变量名的描述。
需要用元注解 @Retention、@Documented、@Target、@Inherited来描述注解的生效时间、是否生成doc文档、使用目标位置,以及是否能被子类继承(注意只有类的注解可以被子类继承保留,方法及接口注解不会被实现类继承)。
在JDK8中新增了@Repeatable 注解,表示容器注解,它的值是一个数组,该注解可以在目标位置使用多次,表示多个不同的值。
通过反射获取注解属性值,并做相应的处理。