用一个词就可以描写叙述注解。那就是元数据,即一种描写叙述数据的数据。因此,可以说注解就是源码的元数据。php
比方。如下这段代码:java
@Override
publicString toString() {
return"This is String Representation of current object.";
}
上面的代码中。我重写了toString()方法并使用了@Override注解。但是,即便我不使用@Override注解标记代码。程序也可以正常执行。那么。该注解表示什么?这么写有什么优势吗?其实。@Override告诉编译器这种方法是一个重写方法(描写叙述方法的元数据),假设父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。数组
假设我不当心拼写错误。好比将toString()写成了toStrring(){double r},并且我也没有使用@Override注解。那程序依旧能编译执行。ruby
但执行结果会和我指望的大不一样样。现在咱们了解了什么是注解,并且使用注解有助于阅读程序。markdown
Annotation是一种应用于类、方法、參数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描写叙述元数据的一种工具。架构
使用Annotation以前(甚至在使用以后),XML被普遍的应用于描写叙述元数据。不知什么时候開始一些应用开发者和架构师发现XML的维护愈来愈糟糕了。他们但愿使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些状况下甚至是全然分离的)代码描写叙述。app
假设你在Google中搜索“XML vs. annotations”,会看到不少关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很是疑惑,二者观点彷佛构成了一种循环。但各有利弊。如下咱们经过一个样例来理解这二者的差异。
假如你想为应用设置很是多的常量或參数,这样的状况下。XML是一个很是好的选择,因为它不会同特定的代码相连。假设你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这样的状况下需要注解和方法紧密耦合起来,开发者也必须认识到这点。
还有一个很是重要的因素是Annotation定义了一种标准的描写叙述元数据的方式。框架
在这以前,开发者一般使用他们本身的方式定义元数据。ide
好比。使用标记interfaces,凝视。transient关键字等等。每个程序猿依照本身的方式定义元数据。而不像Annotation这样的标准的方式。
眼下。不少框架将XML和Annotation两种方式结合使用,平衡二者之间的利弊。函数
注解 | 说明 |
---|---|
@Override | 当咱们想要复写父类中的方法时,咱们需要使用该注解去告知编译器咱们想要复写这种方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。 |
@Deprecated | 当咱们但愿编译器知道某一方法不建议使用时。咱们应该使用这个注解。 Java在javadoc 中推荐使用该注解,咱们应该提供为何该方法不推荐使用以及替代的方法。 |
@SuppressWarnings | 这个仅仅是告诉编译器忽略特定的警告信息,好比在泛型中使用原生数据类型。 它的保留策略是SOURCE(译者注:在源文件里有效)并且被编译器丢弃。 |
@SafeVarargs | 修饰”堆污染”警告 |
@FunctionalInterface | Java8特有的函数式接口 |
注意:
1. value特权:假设使用注解时仅仅需要为value成员变量指定值, 则使用注解时可以直接在该注解的括号里指定value值, 而无需使用name=value的形式. 如@SuppressWarnings(“unchecked”)
2. 请坚持使用@Override注解: 假设在每个方法中使用Override注解来声明要覆盖父类声明, 编译器就可以替你防止大量的错误.
元Annotation用于修饰其它的Annotation定义.
元注解 | 释义 |
---|---|
@Retention | 指明了该Annotation被保留的时间长短。取值(RetentionPoicy)有:1. SOURCE:在源文件里有效(即源文件保留);2. CLASS:在class文件里有效(即class保留);3. RUNTIME:在执行时有效(即执行时保留) |
@Target | 指明该类型的注解可以注解的程序元素的范围。 假设Target元注解没有出现,那么定义的注解可以应用于程序的不论什么元素。取值(ElementType)有:1. CONSTRUCTOR:用于描写叙述构造器;2. FIELD:用于描写叙述域; 3. LOCAL_VARIABLE:用于描写叙述局部变量; 4. METHOD:用于描写叙述方法; 5. PACKAGE:用于描写叙述包; 6. PARAMETER:用于描写叙述參数; 7. TYPE:用于描写叙述类、接口(包括注解类型) 或enum声明 |
@Documented | 指明拥有这个注解的元素可以被javadoc此类的工具文档化。 这样的类型应该用于注解那些影响客户使用带凝视的元素声明的类型。 假设一种声明使用Documented进行注解,这样的类型的注解被做为被标注的程序成员的公共API。 |
@Inherited | 指明该注解类型被本身主动继承。假设用户在当前类中查询这个元注解类型并且当前类的声明中不包括这个元注解类型。那么也将本身主动查询当前类的父类是否存在Inherited元注解,这个动做将被反复执行知道这个标注类型被找到,或者是查询到顶层的父类。 |
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable
{
}
@Testable
class SupperClass
{
}
class SubClass extends SupperClass
{
public SubClass()
{
for(Annotation annotation:SubClass.class.getAnnotations())
{
System.out.println(annotation);
}
for(Annotation annotation:SubClass.class.getDeclaredAnnotations())
{
System.out.println("getDeclaredAnnotations:"+annotation);
}
}
}
package annotation;
import java.lang.annotation.Annotation;
import org.junit.Test;
public class Client {
@Test
public void Client()
{
new SubClass();
}
}
执行结果:@annotation.Testable()
使用@interface本身定义注解时,本身主动继承了java.lang.annotation.Annotation接口,由编译程序本身主动完毕其它细节。在定义注解时,不能继承其它的注解或接口。@interface用来声明一个注解,当中的每个方法其实是声明了一个配置參数。方法的名称就是參数的名称。返回值类型就是參数的类型(返回值类型仅仅能是基本类型、Class、String、enum)。可以经过default来声明參数的默认值。
定义注解格式:
public @interface 注解名 {定义体}
注解參数的可支持数据类型:
1. 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2. String类型
3. Class类型
4. enum类型
5. Annotation类型
6. 以上所有类型的数组
Annotation类型里面的參数该怎么设定:
1. 仅仅能用public或默认(default)这两个訪问权修饰.好比,String value();这里把方法设为defaul默认类型;
2. 參数成员仅仅能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.好比,String value();这里的參数成员就为String;
3. 假设仅仅有一个參数成员,最好把參数名称设为”value”,后加小括号。
依据Annotation是否包括成员变量,可以把Annotation分为两类:
1. 标记Annotation:没有成员变量的Annotation;这样的Annotation仅利用自身的存在与否来提供信息;
2. 元数据Annotation:包括成员变量的Annotation;他们可以接受(和提供)不少其它的元数据。
定义新注解使用@interface关键字。其定义过程与定义接口很是类似(见上面的@Testable), 需要注意的是:Annotation的成员变量在Annotation定义中是以无參的方法形式来声明的, 其方法名和返回值类型定义了该成员变量的名字和类型,并且咱们还可以使用default关键字为这个成员变量设定默认值。
例如如下所看到的。
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Tag
{
String name() default "zzh";
String description() default "excellent!";
}
注解元素必须有肯定的值,要么在定义注解的默认值中指定。要么在使用注解时指定。非基本类型的注解元素的值不可为null。
所以,使用空字符串或0做为默认值是一种常用的作法。
这个约束使得处理器很是难表现一个元素的存在或缺失的状态,因为每个注解的声明中。所有元素都存在,并且都具备对应的值。为了绕开这个约束。咱们仅仅能定义一些特殊的值,好比空字符串或者负数,一次表示某个元素不存在,在定义注解时。这已经成为一个习惯使用方法。
本身定义的Annotation继承了Annotation这个接口,所以本身定义注解中包括了Annotation接口中因此的方法;
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
使用Annotation修饰了类/方法/成员变量等以后,这些Annotation不会本身生效,必须由这些注解的开发者提供对应的工具来提取并处理Annotation信息(固然,仅仅有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,JVM才会在装载class文件时提取保存在class文件里的Annotation,该Annotation才会在执行时可见,这样咱们才干够解析).
Java使用Annotation接口来表明程序元素前面的注解, 用AnnotatedElement接口表明程序中可以接受注解的程序元素.像Class Constructor FieldMethod Package这些类都实现了AnnotatedElement接口.
比方Class的定义:public final class Class extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement。
AnnotatedElement接口的API例如如下:
修饰符与类型 | 方法与描写叙述 |
---|---|
T | ==getAnnotation==(类 annotationClass) Returns this element’s annotation for the specified type if such an annotation is present, else null. |
Annotation[] | ==getAnnotations()== Returns all annotations present on this element. |
Annotation[] | ==getDeclaredAnnotations()== Returns all annotations that are directly present on this element. |
boolean | ==isAnnotationPresent==(类 annotationClass) Returns true if an annotation for the specified type is present on this element, else false. |
package annotation;
import java.lang.annotation.Annotation;
import org.junit.Test;
public class Client {
@Test
public void client() throws NoSuchMethodException, SecurityException
{
Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
for(Annotation annotation : annotations)
{
System.out.println(annotation.annotationType().getName());
}
}
}
执行结果:org.junit.Test
假设需要获取某个注解中的元数据。则需要强转成所需要的注解类型,而后经过注解对象的抽象方法来訪问这些数据。
package annotation;
import java.lang.annotation.Annotation;
import org.junit.Test;
@Tag(name="hiddenzzh")
public class Client {
@Test
public void client() throws NoSuchMethodException, SecurityException
{
Annotation[] annotations = this.getClass().getAnnotations();
for(Annotation annotation : annotations)
{
if(annotation instanceof Tag)
{
Tag tag = (Tag)annotation;
System.out.println("name:"+tag.name());
System.out.println("description:"+tag.description());
}
}
}
}
执行结果:
name:hiddenzzh
description:excellent!
注解对代码的语意没有直接影响, 他们仅仅负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以经过工具对被注解的代码进行特殊处理.
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable
{
}
package annotation;
import java.io.IOException;
public class TestCase
{
@Testable
public void test1()
{
System.out.println("test1");
}
public void test2() throws IOException {
System.out.println("test2");
throw new IOException("我test2出错啦...");
}
@Testable
public void test3() {
System.out.println("test3");
throw new RuntimeException("我test3出错啦...");
}
public void test4() {
System.out.println("test4");
}
@Testable
public void test5() {
System.out.println("test5");
}
@Testable
public void test6() {
System.out.println("test6");
}
}
package annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestableProcessor {
public static void process(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException
{
int passed = 0;
int failed = 0;
Object obj = Class.forName(className).newInstance();
for(Method method:Class.forName(className).getMethods())
{
if(method.isAnnotationPresent(Testable.class))
{
try
{
method.invoke(obj, null);
++passed;
}
catch(IllegalAccessException | InvocationTargetException e)
{
System.out.println("method "+method.getName()+" execute error:< "+e.getCause()+" >");
e.printStackTrace(System.out);
++failed;
}
}
}
System.out.println("共执行 "+(failed+passed)+"个方法。成功:"+passed+"个");
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException
{
process("annotation.TestCase");
}
}
执行结果:
test1
test3
method test3 execute error:< java.lang.RuntimeException: 我test3出错啦... >
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at annotation.TestableProcessor.process(TestableProcessor.java:19)
at annotation.TestableProcessor.main(TestableProcessor.java:36)
Caused by: java.lang.RuntimeException: 我test3出错啦...
at annotation.TestCase.test3(TestCase.java:21)
... 6 more
test5
test6
共执行 4个方法。成功:3个
注意到在TestCase中仅仅有test1,test3,test5以及test6标注了@Testable的注解,经过注解处理器TestableProcessor进行处理,仅仅执行了这个四个标注注解的方法,这个就是经过注解来实现junit功能的一个雏形。
package annotation; 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) public @interface ExceptionTest { Class<? extends Exception> value(); }
package annotation;
public class Sample {
@ExceptionTest(ArithmeticException.class)
public static void m1()
{
int i=0;
i=i/i;
}
@ExceptionTest(ArithmeticException.class)
public static void m2()
{
int [] a = new int[0];
int i=a[1];
}
@ExceptionTest(ArithmeticException.class)
public static void m3(){}
}
package annotation; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ExceptionProcess { public static void main(String[] args) throws ClassNotFoundException { int tests = 0; int passed = 0; Class testClass = Class.forName("annotation.Sample"); for(Method m:testClass.getDeclaredMethods()) { if(m.isAnnotationPresent(ExceptionTest.class)) { tests++; try{ m.invoke(null); System.out.printf("Test %s failed: no exception%n",m); } catch(InvocationTargetException wrappedEx) { Throwable exc = wrappedEx.getCause(); Class<? extends Exception> excType = m.getAnnotation(ExceptionTest.class).value(); if(excType.isInstance(exc)) { passed++; } else { System.out.printf("Test %s failed: expected %s, got %s%n",m,excType.getName(),exc); } } catch(Exception exc) { System.out.println("INVALID @Test: "+m); } } } } }
执行结果:
Test public static void annotation.Sample.m2() failed: expected java.lang.ArithmeticException, got java.lang.ArrayIndexOutOfBoundsException: 1
Test public static void annotation.Sample.m3() failed: no exception
这三段程序类似于上面用来处理Testable注解的代码,但有一处不一样:这段代码提取了注解參数的值,并用它检验改測试抛出的异常是否为正确的类型。没有显示的转换,所以没有出现ClassCastException的危急。编译过的測试程序确保它的注解參数表示的是有效的异常类型,需要提醒一点:有可能注解參数在编译时是有效的,但是表示特定异常类型的类文件在执行时却再也不存在。
在这样的但愿很是少出现的状况下。測试执行类抛出TypeNotPresentException异常。