Java七分钟学习之注解(Annotation)


Java注解(Annotation)

Java注解需要配合反射来使用

一、注解的定义及其解释

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。

1.快速理解定义

注解如同标签可以贴在各个地方
可供参考的博客

二、注解的语法

1.注解通过 @interface 关键字进行定义。

public @interface myAnnotation
{
}

2.元注解及其使用

  • 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
  • 元注解有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。
    @Retention: 定义注解的保留策略
    @Retention(RetentionPolicy.SOURCE)   //注解仅存在于源码中,在class字节码文件中不包含
    @Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
    @Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
    首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
@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 注解,表示容器注解,它的值是一个数组,该注解可以在目标位置使用多次,表示多个不同的值。

  • 通过反射获取注解属性值,并做相应的处理。


文章作者: Bxan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Bxan !
  目录