点亮技能之Java注解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pihailailou/article/details/81348232

 

一,写在前面

 本篇文章,将学习Java注解相关的知识点,包括

  • 注解的定义
  • 注解的属性
  • 元注解
  • 如何使用注解
  • 注解的提取

版本:Java注解是jdk1.5引入的技术

 

二,注解的定义

注解的定义,类似于Java中接口的定义,只需要在interface前面加上一个"@"。代码如下:

public @interface Clazz_Annotation {
	
}

上面就是定义了一个名字叫"Clazz_Annotation"的注解,注解和类,接口一样,都是Java的一种类型。那么,注解的花括号里都放些什么呢?不同于类和接口,注解里面只包含属性(也叫成员变量),不包含方法。

三,注解的属性

注解的属性的定义方式,也不同于类和接口。类里定义一个字段:String name,在注解里定义则是:String name()。

类里定义一个初始值为0的int型变量age是:int age = 0,在注解里则是int age() default 0。

代码如下:

public @interface Clazz_Annotation {
	String name();
	int age() default 0;
}

需要注意的是,如果注解的属性没有默认值,则在后续使用注解时需要指定该属性的值,否则编译失败。如果属性有默认的值,则可以不指定属性的值。

上面我们学习了如何定义注解,以及注解的属性,那么是不是可以使用注解了呢?莫急~,先了解一波元注解,然后再去使用注解效果更好。

四,元注解

那么,什么是元注解呢?元注解也是一个注解,它是用来给普通的注解进行注解,目的是给普通的注解添加限制条件。Java提供了五个元注解,分别是@Retention,@Target,@Inherited,@Documented,@Repeatable。

注意:由于元注解也是一种注解,因此会涉及对注解的使用,注解的使用会在后面详细介绍。

Retention:字面意思是保留期,用于确定普通注解的存活时间。

三种取值如下:

RetentionPolicy.SOURCE:普通注解在源码阶段保留,编译阶段被丢弃;

RetentionPolicy.CLASS :普通注解在编译阶段保留,在运行阶段被丢弃,不会加载进JVM;

RetentionPolicy.RUNTIME:普通注解在运行阶段也保留,它会加载进JVM;

值得一提的是,在使用反射提取注解的时候,由于反射是在运行阶段获取注解,因此该注解必须要添加元注解RetentionPolicy.RUNTIME,否则无法通过反射获取该注解。对注解的提取后面会讲到,这里了解一下即可。

栗子如下:

@Retention(value = RetentionPolicy.RUNTIME)
public @interface Clazz_Annotation {
	String name();
	int age() default 0;
}

括号里value = RetentionPolicy.RUNTIME,就是给Retention注解的value属性设置值为RetentionPolicy.RUNTIME,这里涉及注解的使用,下面会具体讲到。

Target:字面意思是目标。当一个普通注解被@Target注解时,该普通注解就限定了使用范围。

有如下取值:

ElementType.ANNOTATION_TYPE    可以给一个注解进行注解

ElementType.CONSTRUCTOR          可以给构造方法进行注解

ElementType.FIELD                            可以给属性进行注解

ElementType.LOCAL_VARIABLE       可以给局部变量进行注解

ElementType.METHOD                       可以给方法进行注解

ElementType.PACKAGE                     可以给一个进行注解

ElementType.PARAMETER                可以给一个方法内的参数进行注解

ElementType.TYPE                             可以给一个类型进行注解,比如类、接口、枚举

栗子如下:

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
public @interface Clazz_Annotation {
	String name();
	int age() default 0;
}

注解Target的属性value是一个数组类型,给数组设置值,外面用{},多个值之间用逗号隔开。上面我们给它赋值了ElementType.METHOD, ElementType.TYPE, ElementType.FIELD,表示只可以在方法,类/接口/注解,字段上使用该注解。如果在构造方法上使用该注解,则编译错误。

Inherited:继承的意思。文字解释太啰嗦,直接看代码。

栗子如下:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}


@Test
public class A {}


public class B extends A {}

注解Test被@inherited注解,类A被Test注解,且类B继承了类A。如果类B没有被其他注解注解,那么类B也被Test注解。

Documented:文档的意思,作用是能够将注解中的元素包含到 Javadoc 中去。

栗子如下:

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
@Documented
public @interface Clazz_Annotation {
	String name();
	int age() default 0;
}

Documented注解没有属性,查看Documented的源码便知。

Repeatable:可重复的意思。被Repeatable注解的普通注解,可以多次使用。

版本:Java1.8引入的元注解。

栗子如下:

public @interface ListAnnotation {
	ElementAnnotation[] lists();
}

@Repeatable(ListAnnotation.class)
public @interface ElementAnnotation {
	String elment() default "";
}

@ElementAnnotation(elment = "a")
@ElementAnnotation(elment = "b")
@ElementAnnotation(elment = "c")
public class Test {
	
}

由代码可知:注解ElementAnnotation被@Repeatable注解,且Repeatable注解的属性值为ListAnnotation.class。ListAnnotation是一个注解,它是一个容器注解,用于存放注解ElementAnnotation。需要注意的是,注意注解ListAnnotation的属性为ElementAnnotation类型的数组。

然后使用注解ElementAnnotation给类Test添加注解,而且是3个ElementAnnotation注解,但是注解的属性值不同,分别为"a","b","c"。

五,注解的使用

那么如何使用注解呢?我们可以给类/接口/注解,一般方法,构造方法,字段等添加注解。

注意:如需限制某一注解使用的场景,使用元注解@Target即可,前面已经有所分析。

下面以给一个类Test添加注解为例,描述注解的使用。

首先定义一个注解Clazz_Annatation,代码如下:

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
public @interface Clazz_Annotation {
	String value() default "";
}

定义一个类Test,使用注解Clazz_Annatation,代码如下:

@Clazz_Annotation(value = "class_Test")
public class Test {
    //...
}

使用注解需要给属性赋值,是以括号内(属性名 = 值,属性名 = 值,...)形式呈现,多个属性的赋值以逗号隔开。本例中,由于value属性在注解中设置了默认值"",使用注解时可以不给value赋值。

如果注解只有一个value属性,可以简写为:@Clazz_Annotation("class_Test");

如果注解只有一个非value的属性,不可以简写,仍需要以(属性名 = 值)的格式;

如果注解没有属性,可以简写为:@Clazz_Annotation;

六,注解的提取

前面介绍了注解的使用,有木有发现注解并没有起到具体的作用,我们并不知道这样注解后有啥效果,于是涉及到注解的提取。注解的提取需要用Java的反射技术,在运行阶段获取注解。获取了注解,就获取了注解的属性相关的信息。

下面粘贴一个栗子,分别提取了类,字段,方法的注解,代码如下:

@Clazz_Annotation("class_Test")
public class Test {
	
	@Clazz_Annotation("field_obj")
	public Object obj = null;

	@Clazz_Annotation("method_main")
	public static void main(String[] args) {
		print();
	}
	
	private static void print() {
//----class-----------
		boolean isExist = Test.class.isAnnotationPresent(Clazz_Annotation.class);
	    if (isExist) {
	    	Clazz_Annotation clazz_Annotation = Test.class.getAnnotation(Clazz_Annotation.class);
	    	String value = clazz_Annotation.value();
	    	System.out.println("value = " + value); 	

	    } else {
	    	System.out.println("Clazz_Annotation is not exist on class");
	    }
	    
//------field-------
	    try {
			Field field = Test.class.getField("obj");
			boolean isExist1 = field.isAnnotationPresent(Clazz_Annotation.class);
			if (isExist1) {
				Clazz_Annotation clazz_Annotation1 = field.getAnnotation(Clazz_Annotation.class);
				String value1 = clazz_Annotation1.value();
				System.out.println("value = " + value1);
			} else {
				System.out.println("Clazz_Annotation is not exist on field");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	    
//-----method----------
	    try {
			Method method = Test.class.getMethod("main", String[].class);
			boolean isExist2 = method.isAnnotationPresent(Clazz_Annotation.class);
			if (isExist2) {
				Clazz_Annotation clazz_Annotation2 = method.getAnnotation(Clazz_Annotation.class);
				String value2 = clazz_Annotation2.value();
				System.out.println("value = " + value2);
			} else {
				System.out.println("Clazz_Annotation is not exist on method");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	    
	}
}

上述代码分别给类Test,字段obj,方法main添加了@Clazz_Annotation,且value属性的值分别为"class_Test","field_obj","method_main"。

调用反射类的isAnnotationPresent方法,判断类/字段/方法是否添加了Clazz_Annotation注解;

调用反射类的getAnnotation方法,返回一个Clazz_Annotation注解;

调用反射类的getAnnotations()方法,返回一个注解的数组Annotation[](如果有多个注解的话);

获取了注解Clazz_Annotation,再调用clazz_Annotation.value()使用注解的属性。

需要注意的是,要想通过反射成功提取注解,@Clazz_Annotation必须要使用元注解@Retention(RetentionPolicy.RUNTIME),否则程序运行后获取不到Annotation,也就是isAnnotationPresent方法返回null。

七,最后

使用注解去修饰类,方法,字段,annotation不会自动生效,需要开发者提供相应的代码提取,并处理注解。提取并处理注解的代码,称之为APT,即Annotaton Processing Tool,注解解析工具。

注解的提取是利用的Java的反射机制,在运行阶段先获取注解,然后获取注解的属性。

在运行阶段使用反射技术,对性能会有一定的损耗,毕竟反射没有直接调用节约时间,这也是反射提取注解的缺点。

猜你喜欢

转载自blog.csdn.net/pihailailou/article/details/81348232