在Java8以前,在某个类或者方法,字段或者参数上标注注解时,同一个注解只能标注一次。可是在Java8中,新增了重复注解和类型注解,也就是说,从Java8开始,支持在某个类或者方法,字段或者参数上标注多个相同的注解。那么,有读者就会问了:如何实现呢?别急,往下看!文中不仅是Java8中的注解。java
注解就至关于一种标记,在程序中加了注解就等于为程序加了某种标记。(JDK1.5新特性)。程序员
告诉javac编译器或者java开发工具……向其传递某种信息,做为一个标记。数组
一个注解就是一个类。bash
标记能够加在包、类、字段、方法,方法参数以及局部变量上。能够同时存在多个注解。微信
每个注解结尾都没有“;”或者其余特别符号。ide
定义注解须要的基础注解信息以下所示。工具
@SuppressWarnings("deprecation") //编译器警告过期(source阶段) @Deprecated //过期(Runtime阶段) @Override //重写(source阶段) @Retention(RetentionPolicy.RUNTIME) //保留注解到程序运行时。(Runtime阶段) @Target({ElementType.METHOD,ElementType.TYPE}) //标记既能定义在方法上,又能定义在类、接口、枚举上等。
注意:学习
1)添加注解须要有注解类。RetentionPolicy是一个枚举类(有三个成员)。开发工具
2)Target中能够存放数组。它的默认值为任何元素。测试
3)ElementType也是枚举类。成员包括:ANNOTATION_TYPE(注解)、CONSTRUCTOR(构造方法)、FIEID(成员变量)、LOCAL_VARIABLE(变量)、METHOD(方法)、PACKAGE(包)、PARAMETER(参数)、TYPE。
java源文件--> class文件 --> 内存中的字节码。
Retention的注解有三种取值:(分别对应注解的三个阶段)
注意:注解的默认阶段是Class。
原始类型(就是八个基本数据类型)、String类型、Class类型、数组类型、枚举类型、注解类型。
value:是一个特殊的属性,若在设置值时只有一个value属性须要设置或者其余属性都采用默认值时 ,那么value=能够省略,直接写所设置的值便可。
例如:@SuppressWarnings("deprecation") 为属性指定缺省值(默认值): 例如:String value() default "blue"; //定义在注解类中 数组类型的属性: 例如:int[] arrayArr() default {3,4,5,5};//定义在注解类中 SunAnnotation(arrayArr={3,9,8}) //设置数组值 注意:若是数组属性中只有一个元素时,属性值部分能够省略大括号。 例如:SunAnnotation(arrayArr=9) 枚举类型的属性: 例如:EnumDemo.TrafficLamp lamp() ////枚举类型属性, 定义在注解类中,这里使用了自定义的枚举类EnumDemo.java并无给出相关代码,这里只是举个例子 default EnumDemo.TrafficLamp.RED; 注解类型的属性: 例如:MetaAnnotation annotationAttr() //定义在一个注解类中,并指定缺省值, //此属性关联到注解类:MetaAnnotation.java, default @MetaAnnotation("lhm"); //设置注解属性值 @SunAnnotation(annotationAttr=@MetaAnnotation("flx"))
对于注解(也被称作元数据),Java 8 主要有两点改进:类型注解和重复注解。
1)Java 8 的类型注解扩展了注解使用的范围。
在java 8以前,注解只能是在声明的地方所使用,java8开始,注解能够应用在任何地方。
例如:
建立类实例
new @Interned MyObject();
类型映射
myString = (@NonNull String) str;
implements 语句中
class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }
throw exception声明
void monitorTemperature() throws@Critical TemperatureException { ... }
注意:
在Java 8里面,当类型转化甚至分配新对象的时候,均可以在声明变量或者参数的时候使用注解。
Java注解能够支持任意类型。
类型注解只是语法而不是语义,并不会影响java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。
2)新增ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER(在Target上)
新增的两个注释的程序元素类型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER用来描述注解的新场合。
例如,下面的示例。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}
3)类型注解的做用
类型注解被用来支持在Java的程序中作强类型检查。配合第三方插件工具Checker Framework(注:此插件so easy,这里不介绍了),能够在编译的时候检测出runtime error(例如:UnsupportedOperationException; NumberFormatException;NullPointerException异常等都是runtime error),以提升代码质量。这就是类型注解的做用。
注意:使用Checker Framework能够找到类型注解出现的地方并检查。
例以下面的代码。
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。
容许在同一声明类型(类,属性,或方法)上屡次使用同一个注解。
Java8之前的版本使用注解有一个限制是相同的注解在同一位置只能使用一次,不能使用屡次。
Java 8 引入了重复注解机制,这样相同的注解能够在同一地方使用屡次。重复注解机制自己必须用 @Repeatable 注解。
实际上,重复注解不是一个语言上的改变,只是编译器层面的改动,技术层面仍然是同样的。
例如,咱们可使用以下示例来具体对比Java8以前的版本和Java8中的注解。
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(){ } }
什么?没看懂?那就再来一波!!!
Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。整体来讲,比较简单,下面,咱们就以实例的形式来讲明Java8中的重复注解和类型注解。
首先,咱们来定义一个注解类BingheAnnotation,以下所示。
package io.mykit.binghe.java8.annotition; import java.lang.annotation.*; /** * @author binghe * @version 1.0.0 * @description 定义注解 */ @Repeatable(BingheAnnotations.class) @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface BingheAnnotation { String value(); }
注意:在BingheAnnotation注解类上比普通的注解多了一个@Repeatable(BingheAnnotations.class)注解,有小伙伴会问:这个是啥啊?这个就是Java8中定义可重复注解的关键,至于BingheAnnotations.class,你们别急,继续往下看就明白了。
接下来,我们定义一个BingheAnnotations注解类,以下所示。
package io.mykit.binghe.java8.annotation; import java.lang.annotation.*; /** * @author binghe * @version 1.0.0 * @description 定义注解 */ @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE,ElementType.TYPE_PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface BingheAnnotations { BingheAnnotation[] value(); }
看到这里,你们明白了吧!!没错,BingheAnnotations也是一个注解类,它相比于BingheAnnotation注解类来讲,少了一个@Repeatable(BingheAnnotations.class)注解,也就是说,BingheAnnotations注解类的定义与普通的注解几乎没啥区别。值得注意的是,咱们在BingheAnnotations注解类中,定义了一个BingheAnnotation注解类的数组,也就是说,在BingheAnnotations注解类中,包含有多个BingheAnnotation注解。因此,在BingheAnnotation注解类上指定@Repeatable(BingheAnnotations.class)来讲明能够在类、字段、方法、参数、构造方法、参数上重复使用BingheAnnotation注解。
接下来,咱们建立一个Binghe类,在Binghe类中定义一个init()方法,在init方法上,重复使用@BingheAnnotation注解指定相应的数据,以下所示。
package io.mykit.binghe.java8.annotation; /** * @author binghe * @version 1.0.0 * @description 测试注解 */ @BingheAnnotation("binghe") @BingheAnnotation("class") public class Binghe { @BingheAnnotation("init") @BingheAnnotation("method") public void init(){ } }
到此,咱们就能够测试重复注解了,建立类BingheAnnotationTest,对重复注解进行测试,以下所示。
package io.mykit.binghe.java8.annotation; import java.lang.reflect.Method; import java.util.Arrays; /** * @author binghe * @version 1.0.0 * @description 测试注解 */ public class BingheAnnotationTest { public static void main(String[] args) throws NoSuchMethodException { Class<Binghe> clazz = Binghe.class; BingheAnnotation[] annotations = clazz.getAnnotationsByType(BingheAnnotation.class); System.out.println("类上的重复注解以下:"); Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " ")); System.out.println(); System.out.println("============================="); Method method = clazz.getMethod("init"); annotations = method.getAnnotationsByType(BingheAnnotation.class); System.out.println("方法上的重复注解以下:"); Arrays.stream(annotations).forEach((a) -> System.out.print(a.value() + " ")); } }
运行main()方法,输出以下的结果信息。
类上的重复注解以下: binghe class ============================= 方法上的重复注解以下: init method
若是以为文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习Java8新特性。
最后,附上Java8新特性核心知识图,祝你们在学习Java8新特性时少走弯路。