Java 8 新特性:扩展注解(类型注解和重复注解)


注解


文 | 莫若吻     


注:先回顾下JDK1.5版本出现的注解 ,然后再解释JDK 8的注解 更新内容。

一.注解(JDK1.5)

1.注解@

注解就相当于一种标记,在程序中加了注解就等于为程序加了某种标记。(JDK1.5新特性)

2.作用:

告诉javac编译器或者java开发工具……向其传递某种信息,作为一个标记。

3.了解注解:

一个注解就是一个类。
标记 可以加在包、类、字段、方法,方法参数以及局部变量上。 可以 同时 存在 多个注解。
每一个注解 结尾都没有“ 或者 其他 特别 符号

eg:

@SuppressWarnings("deprecation") 	//编译器警告过时(source阶段)
@Deprecated							//过时(Runtime阶段)
@Override								//重写(source阶段)
@Retention(RetentionPolicy.RUNTIME)	
//保留注解到程序运行时。(Runtime阶段)
@Target({ElementType.METHOD,ElementType.TYPE})
//标记既能定义在方法上,又能定义在类、接口、枚举上等。

N ote
1) 添加 注解需要有注解类 。RetentionPolicy 一个枚举类 (有 三个成员)。
2) Target 中可以存放数组。 的默认值为任何元素。

ElementType.METHOD:表示只能标记在方法上。

ElementType.TYPE:表示只能标记定义在类上、接口上、枚举上等。

3)ElementType也是枚举类。成员包括ANNOTATION_TYPE(注解CONSTRUCTOR(构造方法)FIEID(成员变量)LOCAL_VARIABLE变量)、METHOD(方法、PACKAGE(包、PARAMETER(参数、TYPE。


4.注解应用结构图:



5.简单相关知识了解:

元注解 :注解的注解(理解:给一个注解类再加注解)

数据:数据的数据

信息:信息的信息

6.注解分为三个阶段

java源文--> class件 --> 内存中的字节码。


Retention的注解有三种取值 :(分别对应注解的三个阶段)
RetentionPolicy. SOURCE
RetentionPolicy. CLASS
RetentionPolicy. RUNTIME

Note:注解的默认阶段是Class

7.注解的属性类型

原始类型(就是八个基本数据类型)、String类型、Class类型、数组类型、枚举类型、注解类型。

8.为注解增加属性:

value: 是一个特殊的属性,若在设置值时只有一个value属性需要设置或者其他属性都采用默认值时 ,那么 value = 可以省略,直接写所设置的值即可。

eg@SuppressWarnings("deprecation")


为属性指定 缺省值 (默认值):
e g String value() default   "blue" ; //定义在注解类中
 
数组 类型的属性:
e g int [] arrayArr()  default  {3,4,5,5};//定义在注解类中
SunAnnotation (arrayArr={3,9,8}) //设置数组值
Note: 如果数组属性中只有一个元素时,属性值部分可以省略大括号。
e g SunAnnotation (arrayArr=9)
 
枚举 类型的属性:
e g EnumDemo.TrafficLamp lamp()
default  EnumDemo.TrafficLamp. RED ;

//枚举类型属性, 定义在注解类中,这里使用了自定义的枚举类EnumDemo.java并没有给出相关代码,这里只是举个例子

注解 类型的属性:
e g MetaAnnotation annotationAttr()

default   @MetaAnnotation ( "lhm" );
//定义在一个注解类中,并指定缺省值,
//此属性关联到注解类:MetaAnnotation.java, 
 
@SunAnnotation (annotationAttr= @MetaAnnotation ( "flx" ))

//设置注解属性值


9.注解应用示例:

注:下面只是示例,涉及枚举的类,并没有给出相关代码。)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)			//元注解:注解的注解。此注解表示使注解保留到运行时。

//Target中可以存放数组。ElementType.METHOD:表示只能标记在方法上。ElementType.TYPE:表示只能标记定义在类上、接口上、枚举上等。
@Target({ElementType.METHOD,ElementType.TYPE})	

public @interface SunAnnotation {
	String color() default "blue"; //表示有一个color属性,以方法的形式。设置默认属性值为蓝色
	String value();		
	//是一个特殊的属性,若在设置值时只有一个value属性需要设置 ,那么value可以省略,直接写所设置的值即可。
	
	//数组属性
	int[] arrayArr() default {3,4,5,5};
	
	//枚举类型属性,这里使用了自定的枚举类EnumDemo.java
	EnumDemo.TrafficLamp lamp() default EnumDemo.TrafficLamp.RED;
	
	//注解类型属性,此时关联到注解类:MetaAnnotation.java,并指定缺省值
	MetaAnnotation annotationAttr() default @MetaAnnotation("lhm");
	
	//Class类属性:设置默认值 ReflectDemo.java	类
	Class annotationClass() default ReflectDemo.class;

}

@SunAnnotation(annotationClass=ReflectDemo2.class,annotationAttr=@MetaAnnotation("flx"),color="red",value="abc",arrayArr={1,3,9})		//注解:为注解添加属性值
//若在设置值时只有一个value属性需要设置 ,那么value可以省略,直接写所设置的值即可。
public class AnnotationDemo {

	/**
	 * @param args
	 */
	
	
	@SunAnnotation("xyz")
	@SuppressWarnings("deprecation")  //注解:告诉编译器或者开发工具……
	//若在设置值时只有一个value属性需要设置 ,那么value可以省略,直接写所设置的值即可。
	//上面"deprecation"即是省略的value后的值。
	
	public static void main(String[] args)throws Exception {
		System.runFinalizersOnExit(true);//中间带横线的部分,说明该方法已经过时了。
		
		//检查某注解是否存在,使用反射;并返回该注解
		if(AnnotationDemo.class.isAnnotationPresent(SunAnnotation.class))
		{
			//获取注解
			SunAnnotation annotation=(SunAnnotation)AnnotationDemo.class.getAnnotation(SunAnnotation.class);
			//获取属性值
			System.out.println("属性color值:"+annotation.color());//调用color属性方法
			System.out.println(annotation.value());
			System.out.println(annotation.arrayArr().length);//这里输出的是数组长度,不能直接输出数组的每一个值,调用得到的是数组哈希地址值
			System.out.println(annotation.lamp().nextLamp().name());//调用注解默认灯的nextLamp方法
			annotation.annotationAttr().value();
			//获取类类型注解中的 类名称
			System.out.println(annotation.annotationClass());
		
		}
	}
	@Deprecated //表示下面的方法过时了
	public static void sayHello()
	{
		System.out.println("hi");
	}

}


二.扩展注解(JDK 8)

对于注解(也被称做元数据),Java 8 主要有两点改进:类型注解重复注解

1.类型注解

1)Java 8 的类型注解扩展了注解使用的范围。

在java 8之前,注解只能是在声明的地方所使用,java8开始,注解可以应用在任何地方。

eg:

  • 创建类实例
new@Interned MyObject();
  • 类型映射
myString = (@NonNull String) str;
  • implements 语句中
class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }
  • throw exception声明
void monitorTemperature() throws@Critical TemperatureException { ... }


Note:

  • 在Java 8里面,当类型转化甚至分配新对象的时候,都可以在声明变量或者参数的时候使用注解。
  • Java注解可以支持任意类型。
  • 类型注解只是语法而不是语义,并不会影响java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。


2)新增ElementType.TYPE_USEElementType.TYPE_PARAMETER(在Target上

新增的两个注释的程序元素类型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER 用来描述注解的新场合
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(eg:声明语句、泛型和强制转换语句中的类型)。

eg:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}


3)类型注解的作用

类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework注:此插件so easy,这里不介绍了,可以在编译的时候检测出runtime erroreg:UnsupportedOperationException; NumberFormatException;NullPointerException异常等都是runtime error以提高代码质量。这就是类型注解的作用。

Note:

使用Checker Framework可以找到类型注解出现的地方并检查。

eg:

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        @NonNull Object my = new Object();
    }
}

使用javac编译上面的类:(当然若下载了Checker Framework插件就不需要这么麻烦了)

javac -processor checkers.nullness.NullnessChecker TestDemo.java


上面编译是通过的,但若修改代码:

@NonNull Object my = null;

若不想使用类型注解检测出来错误,则不需要processor,正常javac TestDemo.java是可以通过编译的,但是运行时会报 NullPointerException 异常。


为了能在编译期间就自动检查出这类异常,可以通过类型注解结合 Checker Framework 提前排查出来错误异常。

注意java 5,6,7版本是不支持注解@NonNull,但checker framework 有个向下兼容的解决方案,就是将类型注解@NonNull 用/**/注释起来。

import checkers.nullness.quals.*;
public class TestDemo{
    void sample() {
        /*@NonNull*/ Object my = null;
    }
}

这样javac编译器就会忽略掉注释块,但用checker framework里面的javac编译器同样能够检测出@NonNull错误。
通过 类型注解 + checker framework 可以在编译时就找到runtime error


2.重复注解

允许在同一声明类型(类,属性,或方法)上多次使用同一个注解。

Java8以前的版本使用注解有一个限制是相同的注解在同一位置只能使用一次,不能使用多次

Java 8 引入了重复注解机制,这样相同的注解可以在同一地方使用多次。重复注解机制本身必须用 @Repeatable 注解。

实际上,重复注解不是一个语言上的改变,只是编译器层面的改动,技术层面仍然是一样的。

eg:

1)自定义一个包装类Hints注解用来放置一组具体的Hint注解

@interface MyHints {
    Hint[] value();
}
 
@Repeatable(MyHints.class)
@interface Hint {
    String value();
}
使用包装类当容器来存多个注解(旧版本方法)

@MyHints({@Hint("hint1"), @Hint("hint2")})
class Person {}
使用多重注解(新方法)

@Hint("hint1")
@Hint("hint2")
class Person {}

2)

public class RepeatingAnnotations {
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Filters {
        Filter[] value();
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Filters.class)
    public @interface Filter {
        String value();
    }
    @Filter("filter1")
    @Filter("filter2")
    public interface Filterable {
    }
    public static void main(String[] args) {
        for (Filter filter : Filterable.class.getAnnotationsByType(Filter.class)) {
            System.out.println(filter.value());
        }
    }
}
输出结果:

filter1
filter2

分析:

注释Filter被@Repeatable( Filters.class )注释。Filters 只是一个容器,它持有Filter, 编译器尽力向程序员隐藏它的存在。通过这样的方式,Filterable接口可以被Filter注释两次。
另外,反射的API提供一个新方法getAnnotationsByType() 来返回重复注释的类型(注意Filterable.class.getAnnotation( Filters.class )将会返回编译器注入的Filters实例)。

3)java 8之前也有重复使用注解的解决方案,但可读性不好。

public @interface MyAnnotation {  
     String role();  
}  
 
public @interface Annotations {  
    MyAnnotation[] value();  
}  
 
public class RepeatAnnotationUseOldVersion {  
      
    @Annotations({@MyAnnotation(role="Admin"),@MyAnnotation(role="Manager")})  
    public void doSomeThing(){  
    }  
}

Java8的实现方式(由另一个注解来存储重复注解,在使用时候,用存储注解Authorities来扩展重复注解),可读性更强。

@Repeatable(Annotations.class) 
public @interface MyAnnotation {  
     String role();  
}  
 
public @interface Annotations {  
    MyAnnotation[] value();  
}  
 
public class RepeatAnnotationUseOldVersion {  
	@MyAnnotation(role="Admin")  
    @MyAnnotation(role="Manager")
    public void doSomeThing(){  
    }  
} 



©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页