从JDK1.5开始,Java引入了一种新的注释机制-Annotation,中文名称一般叫注解,它一般作为说明信息,与程序的业务逻辑无关。
既然注解仅仅是一种说明信息,为什么我们还要了解它呢?因为它还广泛地应用于一些工具或框架中。从官方的定义来看,Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法,当然我们都知道官方的定义往往都是看不懂的,因此我们需要尽量用通俗一点的东西去理解它。鉴于大家对Java本身的Annotation都不怎么熟悉,因此本文先介绍Java中的注解,再介绍Kotlin的版本。
Java Annotation组成部分
在源码(java.lang.annotation)中,有几个比较重要的类:
(1)Annotation.java
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
从代码来看并无特别之处,唯一值得注意的是 annotationType()这个方法的返回值,这里暂时不讲
(2)ElementType.java
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
ElementType是一个枚举类,它用来指定Annotation的类型,表明Annotation可以用在什么地方
例如,TYPE表示这个Annotation可以用在类/接口/Annotation或者枚举上,CONSTRUCTOR表示它可以用于构造器上,METHOD表示可以用来修饰方法
需要注意的一点是,一个Annotation可以与多个ElementType关联
TYPE_PARAMETER/TYPE_USE是JDK1.8的新特性,它代表此Annotation可以用于泛型
(3)RetentionPolicy.java
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
RetentionPolicy正如它名字所示,它代表了Annotation“保留”的策略
SOURCE代表这个注解在编译器处理完之后就被抛弃(好狠),这意味着它的信息信息仅存在于编译器处理期间。
CLASS代表编译器将注解存储于类对应的.class文件中,但是在运行时不能通过JVM读取,在Java中这是Annotation的默认行为(在Kotlin中默认行为是RUNTIME)
RUNTIME则代表编译器将注解存储于.class文件中,并且可由反射获取,由于Kotlin的语言特性,它在是Kotlin中的默认策略。
很显然,一个Annotation只能和一个RetentionPolicy关联
总结:一个完整的Annotation与多个ElementType和一个RetentionPolicy相关联
在Java中定义Annotation
理解了以上的三个类,掌握定义Annotation的方式就很容易了:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
}
以上代码定义了一个名字叫MyAnnotation的注解,它可以用来修饰类/接口/Annotation或者枚举,并且将注解存储于类对应的.class文件中。现在,我们可以在代码里通过@MyAnnotation来使用它了:
@MyAnnotation
class water{
@MyAnnotation //My Annotation不能作用于方法
public void flow()
{
}
}
在定义注解时需要注意以下几点:
(1)定义注解时,必须通过@interface方式,而不是通过implement Annotation的方式,因为Annotation接口的实现细节都是由编译器完成,而不是通过我们的代码去完成的。通过@interface定义注解后,该注解不能继承其他的注解或接口。
(2)@Target(ElementType.TYPE) 的意思就是指定该注解的类型是ElementType.TYPE,当然,你可以定义多个ElementType:
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD})
@interface YourAnnotation{
}
当然,不指定也是行的,这时候此注解可以用在任何地方
(3)@Retention(RetentionPolicy.CLASS)表示这个注解的保留策略为CLASS(将注解存储于.class文件中,但是不能被JVM访问),在Java中如果不指定的话默认保留策略为RetentionPolicy.CLASS
因此,在定义一个注解需要三个步骤- (1)实现接口 (2)定义类型 (3)定义保留策略
Java中的常见注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Documented标记表示被注解的内容会被收录入javadoc中,只能用在其他的注解上。喜感的是,它在自己的定义中使用了它自己。
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}
@Deprecated 表示所标注的内容不再被建议使用,它可以用在任何地方
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Inherited 表示它所标注的Annotation将具有继承性,这意味着它所标注的类的子类也将拥有这个标注(子类默认是不继承注解的)。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@SuppressWarnings可以让编译器忽略掉对它所标注的内容的警告
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
重复注释允许相同注释在声明使用的时候重复使用超过一次,这是Java 8中的新特性,不过目前来看并没有什么卵用。
让Java程序认识注解
如果注解仅仅是用于给程序员描述信息的话,那实际上注解是没有什么实质性作用的。为了编写更为灵活和智能的程序,我们需要在程序中识别注解。
public interface AnnotatedElement {
default boolean isAnnotationPresent(Class<? extends Annotation> var1) {
return this.getAnnotation(var1) != null;
}
<T extends Annotation> T getAnnotation(Class<T> var1);
Annotation[] getAnnotations();
default <T extends Annotation> T[] getAnnotationsByType(Class<T> var1) {
Annotation[] var2 = this.getDeclaredAnnotationsByType(var1);
if(var2.length == 0 && this instanceof Class && AnnotationType.getInstance(var1).isInherited()) {
Class var3 = ((Class)this).getSuperclass();
if(var3 != null) {
var2 = var3.getAnnotationsByType(var1);
}
}
return var2;
}
default <T extends Annotation> T getDeclaredAnnotation(Class<T> var1) {
Objects.requireNonNull(var1);
Annotation[] var2 = this.getDeclaredAnnotations();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Annotation var5 = var2[var4];
if(var1.equals(var5.annotationType())) {
return (Annotation)var1.cast(var5);
}
}
return null;
}
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> var1) {
Objects.requireNonNull(var1);
return AnnotationSupport.getDirectlyAndIndirectlyPresent((Map)Arrays.stream(this.getDeclaredAnnotations()).collect(Collectors.toMap(Annotation::annotationType, Function.identity(), (var0, var1) -> {
return var0;
}, LinkedHashMap::<init>)), var1);
}
Annotation[] getDeclaredAnnotations();
}
AnnotatedElement接口定义了一些判断注解以及获取注解的方法。isAnnotationPresent方法可以判断该元素是否拥有指定注解类的注解,getAnnotation(Class<T> var1) 则返回指定注解类的注解,getAnnotations()则会返回所有的注解。
实现了这个接口的类有: Class,Constructor,Executable,Field,Method,Package,Parameter,AccessibleObject
这些类都位于java.lang.reflect包,这意味着我们的程序需要通过反射来识别注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface MyAnnotation3{
}
@MyAnnotation
class Country{
@MyAnnotation
int pop = 0;
int coin = 0;
String name = "";
public Country(String name,int pop,int coin){
this.name = name;
this.pop = pop;
this.coin = coin;
}
@MyAnnotation2
public String describe()
{
return name+" pop: "+pop+" coin: "+coin;
}
@MyAnnotation2
public int doublepop()
{
return pop*2;
}
@MyAnnotation3
public int averageCoin()
{
if(pop == 0) return Integer.MAX_VALUE;
else return coin/pop;
}
}
public class Anotest {
public static void main(String[] args) {
Country sr = new Country("Ravenland",200,10000);
Class<Country> c = Country.class;
if(c.isAnnotationPresent(MyAnnotation.class))
{
System.out.println("Class Country has MyAnnotation");
}
Method[] methods = c.getMethods();
for(Method method : methods)
{
Annotation[] aList = method.getAnnotations();
if(aList!=null && aList.length > 0)
{
for(Annotation b : aList)
{
System.out.println(method.getName()+"Has Annotation: "+b);
}
}
}
}
}
输出结果:
Class Country has MyAnnotation
doublepopHas Annotation: @test.MyAnnotation2()
describeHas Annotation: @test.MyAnnotation2()
以上的例子深刻地表明只有RetentionPolicy.RUNTIME的注解能够通过反射访问。
注解广泛地使用在各种工具(例如Retrofit,GSON等)和框架(JUnit,DataBinding)中。
如果你对反射还不太熟悉也没太大问题,不久之后我也会介绍反射的一些概念。
在Kotlin中使用注解
同Java一样,Kotlin中也提供了对注解的支持,大部分相关的类位于包kotlin.annotation中。
(1)AnnotationTarget.kt
public enum class AnnotationTarget {
/** Class, interface or object, annotation class is also included */
CLASS,
/** Annotation class only */
ANNOTATION_CLASS,
/** Generic type parameter (unsupported yet) */
TYPE_PARAMETER,
/** Property */
PROPERTY,
/** Field, including property's backing field */
FIELD,
/** Local variable */
LOCAL_VARIABLE,
/** Value parameter of a function or a constructor */
VALUE_PARAMETER,
/** Constructor only (primary or secondary) */
CONSTRUCTOR,
/** Function (constructors are not included) */
FUNCTION,
/** Property getter only */
PROPERTY_GETTER,
/** Property setter only */
PROPERTY_SETTER,
/** Type usage */
TYPE,
/** Any expression */
EXPRESSION,
/** File */
FILE,
/** Type alias */
@SinceKotlin("1.1")
TYPEALIAS
}
在Kotlin中,AnnotationTarget是一个枚举类,它的作用和ElementType类似,表示注解能应用到哪些地方。
其中需要特别注意的是FIELD和PROPERTY,Kotlin的类不能有字段(FIELD)而只有属性(PROPERTY),只有在使用自定义访问器时才可能需要有一个后备字段。另外,由于在Kotlin中的单个申明往往对应了多个Java声明(例如var属性),因此在标注中也特意进行了PROPERTY_GETTER和PROPERTY_SETTER来特意区分它们。
在Kotlin中也可以对文件(FILE)和别名(TYPEALIAS)进行注解。
如果在声明注解时没有特意指明AnnotationTarget,说明这个注解可以用到任何地方(除了泛型、表达式和文件)
(2)AnnotationRetention.kt
public enum class AnnotationRetention {
/** Annotation isn't stored in binary output */
SOURCE,
/** Annotation is stored in binary output, but invisible for reflection */
BINARY,
/** Annotation is stored in binary output and visible for reflection (default retention) */
RUNTIME
}
AnnotationRetention和Java中的RetentionPolicy相对应,表示注解的保留策略。在Kotlin中的BINARY和Java中的CLASS对应,都代表无法通过反射访问,但是会被保留在class文件中。
在Kotlin中,默认的保留策略为RUNTIME,这表明我们可以通过反射在程序中获取它。
(3)在Kotlin中定义注解
在Kotlin中声明注解和声明普通的类很相似,区别在于在class前面需要加上annotation关键字:
/*不接收参数的注解*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class MyAnnotation
/*接受参数的注解*/
annotation class MyAnnotation2(val name : String)
/*接受多个参数的注解*/
annotation class MyAnnotation3(vararg val name : String)
Kotlin中的注解目标
由于在Kotlin中的单个申明往往对应了多个Java声明,例如,一个Property对应了一个Field和Getter和Setter,为了使标注更为精确,Kotlin中还允许使用点目标。
点目标的语法为 @目标:注解名 例如:@get:MyAnnotation
class Country{
/*对属性的getter使用注解*/
@get:MyAnnotation2("Editable")
var name : String = ""
/*对属性使用注解*/
@MyAnnotation2("Editable")
var pop : Int = 0
/*对属性的Setter使用注解*/
@MyAnnotation3("Editable","Can be below 0")
@set:MyAnnotation3
var coin : Int = 0
/*对生成的Field使用注解*/
/*具有setter的属性一般会自动生成backing field(后备字段)*/
@field:MyAnnotation2("")
var army : Int = 1000
}
在Kotlin中支持以下点目标:
property:代表kotlin中的属性,不能被Java的注解所应用
field:为属性生成的字段(包括后备字段)
get:属性的getter
set:属性的setter
receiver:扩展函数/属性的接收者
param:构造函数的参数
setparam:属性setter的参数
delegate:委托属性存储委托实例的字段
file:在文件中声明的顶层函数与类
如果你希望你的注解使用类作为类型参数的话,你可以定义为:
annotation class MyClassAnnotation(val clazz : KClass<out Any>)
注意必须out Any,否则泛型参数无法协变导致你只能使用Any类作为参数
@MyClassAnnotation(NewsAdapter::class)
fun nothingleft()
{
}
KClass是Kotlin中与Java中Class类对应的类,用于Kotlin中的反射。
如果要使用泛型类作为注解参数,则需要通过星形投影
annotation class ListAnnotation(val clazz : KClass<out List<*>>)
/*把List作为注解参数*/
@ListAnnotation(List::class)
fun sum()
{
}
星形投影表示你不知道关于泛型实参的任何信息,比如在上面的例子中,你只知道List本身,而不知道它具体的泛型实参
由此可见,在Kotlin中应用注解的语法和Java很相似,但Kotlin中你注解的目标要比Java更广。
当然,还是那句话,注解给人看是没多大意义的,还是需要在程序里处理,这就需要掌握反射,不过,在介绍反射之前,我们需要对Java虚拟机中的内存模型进行一点简单的了解......
反射参考://www.greatytc.com/p/5adf5f1c49e8