【转】JAVA反射与注解

转载自:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/

前言

如今在咱们构建本身或公司的项目中,或多或少都会依赖几个流行比较屌的第三方库,好比:Butter KnifeRetrofit 2Dagger 2GreenDao等,若是你没用过,那你须要找时间补一下啦;有时在使用后咱们会好奇他们究竟是怎么作到这种简洁、高效、松耦合等诸多优势的,固然这里我不探讨它们具体怎么实现的 (能够看看我以前写的几篇文章) ,而关心的是它们都用到一样的技术那就是本篇所讲的反射注解,并实现的依赖注入。html

阅读本篇文章有助于你更好的理解这些大形框架的原理和复习Java的知识点。为何要把反射放在前面讲呢,其实是由于咱们学习注解的时候须要用到反射机制,因此,先学习反射有助于理解后面的知识。java

JAVA反射

主要是指程序能够访问,检测和修改它自己状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。面试

反射机制是什么

面试有可能会问到,这句话无论你能不能理解,可是你只要记住就能够了数组

反射机制就是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。app

用一句话总结就是反射能够实如今运行时能够知道任意一个类属性和方法框架

反射机制能作什么

反射机制主要提供了如下功能:ide

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具备的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理(ps:这个知识点也很重要,后续会为你们讲到)

Java 反射机制的应用场景

  • 逆向代码 ,例如反编译
  • 与注解相结合的框架 例如Retrofit
  • 单纯的反射机制应用框架 例如EventBus
  • 动态生成类框架 例如Gson

反射机制的优势与缺点

为何要用反射机制?直接建立对象不就能够了吗,这就涉及到了动态与静态的概念函数

  • 静态编译:在编译时肯定类型,绑定对象,即经过。工具

  • 动态编译:运行时肯定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以下降类之间的藕合性。post

优势

  • 能够实现动态建立对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。好比,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现须要更新某些功能时,咱们不可能要用户把之前的卸载,再从新安装新的版本,假如这样的话,这个软件确定是没有多少人用的。采用静态的话,须要把整个程序从新编译一次才能够实现功能的更新,而采用反射机制的话,它就能够不用卸载,只须要在运行时才动态的建立和编译,就能够实现该功能。

缺点

  • 对性能有影响。使用反射基本上是一种解释操做,咱们能够告诉JVM,咱们但愿作什么而且它知足咱们的要求。这类操做老是慢于只直接执行相同的操做。

理解Class类和类类型

想要了解反射首先理解一下Class类,它是反射实现的基础。
类是java.lang.Class类的实例对象,而Class是全部类的类(There is a class named Class)
对于普通的对象,咱们通常都会这样建立和表示:

1
Code code1 = new Code();

上面说了,全部的类都是Class的对象,那么如何表示呢,可不能够经过以下方式呢:

1
Class c = new Class();

可是咱们查看Class的源码时,是这样写的:

1
2
3
private Class(ClassLoader loader) { 
classLoader = loader;
}

能够看到构造器是私有的,只有JVM能够建立Class的对象,所以不能够像普通类同样new一个Class对象,虽然咱们不能new一个Class对象,可是却能够经过已有的类获得一个Class对象,共有三种方式,以下:

1
2
3
Class c1 = Code.class; 这说明任何一个类都有一个隐含的静态成员变量class,这种方式是经过获取类的静态成员变量class获得的
Class c2 = code1.getClass(); code1是Code的一个对象,这种方式是经过一个类的对象的getClass()方法得到的
Class c3 = Class.forName("com.trigl.reflect.Code"); 这种方法是Class类调用forName方法,经过一个类的全量限定名得到

这里,c一、c二、c3都是Class的对象,他们是彻底同样的,并且有个学名,叫作Code的类类型(class type)。
这里就让人奇怪了,前面不是说Code是Class的对象吗,而c一、c二、c3也是Class的对象,那么Code和c一、c二、c3不就同样了吗?为何还叫Code什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就行了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,因此咱们能够经过类类型知道一个类的属性和方法,而且能够调用一个类的属性和方法,这就是反射的基础。

举个简单例子代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//第一种:Class c1 = Code.class;
Class class1=ReflectDemo.class;
System.out.println(class1.getName());

//第二种:Class c2 = code1.getClass();
ReflectDemo demo2= new ReflectDemo();
Class c2 = demo2.getClass();
System.out.println(c2.getName());

//第三种:Class c3 = Class.forName("com.trigl.reflect.Code");
Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
System.out.println(class3.getName());
}
}

执行结果:

1
2
3
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

Java反射相关操做

在这里先看一下sun为咱们提供了那些反射机制中的类:
java.lang.Class;
java.lang.reflect.Constructor; java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;

前面咱们知道了怎么获取Class,那么咱们能够经过这个Class干什么呢?
总结以下:

  • 获取成员方法Method
  • 获取成员变量Field
  • 获取构造函数Constructor

下面来具体介绍

  1. 获取成员方法信息

    两个参数分别是方法名和方法参数类的类类型列表。

1
2
3
4
5
6
7
8
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 获得该类全部的方法,不包括父类的 
public Method getMethod(String name, Class<?>... parameterTypes) // 获得该类全部的public方法,包括父类的

//具体使用
Method[] methods = class1.getDeclaredMethods();//获取class对象的全部声明方法
Method[] allMethods = class1.getMethods();//获取class对象的全部public方法 包括父类的方法
Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法

举个例子:

例如类A有以下一个方法:

1
2
3
public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"岁");
}

如今知道A有一个对象a,那么就能够经过:

1
2
3
4
Class c = Class.forName("com.tengj.reflect.Person"); //先生成class
Object o = c.newInstance(); //newInstance能够初始化一个实例
Method method = c.getMethod("fun", String.class, int.class);//获取方法
method.invoke(o, "tengj", 10); //经过invoke调用该方法,参数第一个为实例对象,后面为具体参数值

完整代码以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Person {
private String name;
private int age;
private String msg="hello wrold";
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Person() {
}

private Person(String name) {
this.name = name;
System.out.println(name);
}

public void fun() {
System.out.println("fun");
}

public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"岁");
}
}

public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Object o = c.newInstance();
Method method = c.getMethod("fun", String.class, int.class);
method.invoke(o, "tengj", 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果:

我叫tengj,今年10岁

怎样,是否是感受很厉害,咱们只要知道这个类的路径全称就能玩弄它于鼓掌之间。

有时候咱们想获取类中全部成员方法的信息,要怎么办。能够经过如下几步来实现:

1.获取全部方法的数组:

1
2
3
4
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 获得该类全部的方法,不包括父类的
或者:
Method[] methods = c.getMethods();// 获得该类全部的public方法,包括父类的

2.而后循环这个数组就获得每一个方法了:

1
for (Method method : methods)

完整代码以下:
person类跟上面同样,这里以及后面就不贴出来了,只贴关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods();
for(Method m:methods){
String methodName= m.getName();
System.out.println(methodName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果:

getName
setName
setAge
fun
fun
getAge

这里若是把c.getDeclaredMethods();改为c.getMethods();执行结果以下,多了不少方法,觉得把Object里面的方法也打印出来了,由于Object是全部类的父类:

getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
  1. 获取成员变量信息

想想成员变量中都包括什么:成员变量类型+成员变量名

类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,因此咱们经过java.lang.reflect.Field里面封装的方法来获取这些信息。

单独获取某个成员变量,经过Class类的如下方法实现:

参数是成员变量的名字

1
2
3
4
5
6
7
8
public Field getDeclaredField(String name) // 得到该类自身声明的全部变量,不包括其父类的变量
public Field getField(String name) // 得到该类自全部的public成员变量,包括其父类变量

//具体实现
Field[] allFields = class1.getDeclaredFields();//获取class对象的全部属性
Field[] publicFields = class1.getFields();//获取class对象的public属性
Field ageField = class1.getDeclaredField("age");//获取class指定属性
Field desField = class1.getField("des");//获取class指定的public属性

举个例子:

例如一个类A有以下成员变量:

1
private int n;

若是A有一个对象a,那么就能够这样获得其成员变量:

1
2
Class c = a.getClass();
Field field = c.getDeclaredField("n");

完整代码以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
//获取成员变量
Field field = c.getDeclaredField("msg"); //由于msg变量是private的,因此不能用getField方法
Object o = c.newInstance();
field.setAccessible(true);//设置是否容许访问,由于该变量是private的,因此要手动设置容许访问,若是msg是public的就不须要这行了。
Object msg = field.get(o);
System.out.println(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果:

hello wrold

一样,若是想要获取全部成员变量的信息,能够经过如下几步

1.获取全部成员变量的数组:

1
Field[] fields = c.getDeclaredFields();

2.遍历变量数组,得到某个成员变量field

1
for (Field field : fields)

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Field[] fields = c.getDeclaredFields();
for(Field field :fields){
System.out.println(field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果:

name
age
msg
  1. 获取构造函数

最后再想想构造函数中都包括什么:构造函数参数
同上,类的成构造函数也是一个对象,它是java.lang.reflect.Constructor的一个对象,因此咱们经过java.lang.reflect.Constructor里面封装的方法来获取这些信息。

单独获取某个构造函数,经过Class类的如下方法实现:

这个参数为构造函数参数类的类类型列表

1
2
3
4
5
6
7
8
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 得到该类全部的构造器,不包括其父类的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 得到该类因此public构造器,包括父类

//具体
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的全部声明构造函数
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

举个例子:

例如类A有以下一个构造函数:

1
2
3
public A(String a, int b) {
// code body
}

那么就能够经过:

1
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

来获取这个构造函数。

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
//获取构造函数
Constructor constructor = c.getDeclaredConstructor(String.class);
constructor.setAccessible(true);//设置是否容许访问,由于该构造器是private的,因此要手动设置容许访问,若是构造器是public的就不须要这行了。
constructor.newInstance("tengj");
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果:

tengj

注意:Class的newInstance方法,只能建立只包含无参数的构造函数的类,若是某类只有带参数的构造函数,那么就要使用另一种方式:

1
fromClass.getDeclaredConstructor(String.class).newInstance("tengj");

获取全部的构造函数,能够经过如下步骤实现:

1.获取该类的全部构造函数,放在一个数组中:

1
Constructor[] constructors = c.getDeclaredConstructors();

2.遍历构造函数数组,得到某个构造函数constructor:

1
for (Constructor constructor : constructors)

完整代码:

1
2
3
4
5
6
7
8
9
10
11
public class ReflectDemo {
public static void main(String[] args){
Constructor[] constructors = c.getDeclaredConstructors();
for(Constructor constructor:constructors){
System.out.println(constructor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

执行结果:

public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)
  1. 其余方法

注解须要用到的

1
2
3
4
Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的全部注解 
Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解
Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的
Type Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的全部接口的type集合

获取class对象的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
boolean isPrimitive = class1.isPrimitive();//判断是不是基础类型 
boolean isArray = class1.isArray();//判断是不是集合类
boolean isAnnotation = class1.isAnnotation();//判断是不是注解类
boolean isInterface = class1.isInterface();//判断是不是接口类
boolean isEnum = class1.isEnum();//判断是不是枚举类
boolean isAnonymousClass = class1.isAnonymousClass();//判断是不是匿名内部类
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
String className = class1.getName();//获取class名字 包含包名路径
Package aPackage = class1.getPackage();//获取class的包信息
String simpleName = class1.getSimpleName();//获取class类名
int modifiers = class1.getModifiers();//获取class访问权限
Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
Class<?> declaringClass = class1.getDeclaringClass();//外部类

getSuperclass():获取某类的父类
getInterfaces():获取某类实现的接口

经过反射了解集合泛型的本质

扩展的知识点,了解就能够了。后续会为你们写一篇关于泛型的文章。

首先下结论:

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。

下面经过一个实例来验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 集合泛型的本质
*/
public class GenericEssence {
public static void main(String[] args) {
List list1 = new ArrayList(); // 没有泛型
List<String> list2 = new ArrayList<String>(); // 有泛型


/*
* 1.首先观察正常添加元素方式,在编译器检查泛型,
* 这个时候若是list2添加int类型会报错
*/
list2.add("hello");
// list2.add(20); // 报错!list2有泛型限制,只能添加String,添加int报错
System.out.println("list2的长度是:" + list2.size()); // 此时list2长度为1


/*
* 2.而后经过反射添加元素方式,在运行期动态加载类,首先获得list1和list2
* 的类类型相同,而后再经过方法反射绕过编译器来调用add方法,看可否插入int
* 型的元素
*/
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 结果:true,说明类类型彻底相同

// 验证:咱们能够经过方法的反射来给list2添加元素,这样能够绕过编译检查
try {
Method m = c2.getMethod("add", Object.class); // 经过方法反射获得add方法
m.invoke(list2, 20); // 给list2添加一个int型的,上面显示在编译器是会报错的
System.out.println("list2的长度是:" + list2.size()); // 结果:2,说明list2长度增长了,并无泛型检查
} catch (Exception e) {
e.printStackTrace();
}

/*
* 综上能够看出,在编译器的时候,泛型会限制集合内元素类型保持一致,可是编译器结束进入
* 运行期之后,泛型就再也不起做用了,即便是不一样类型的元素也能够插入集合。
*/
}
}

执行结果:

list2的长度是:1
true
list2的长度是:2

思惟导图

有助于理解上述所讲的知识点

拓展阅读
Java反射机制深刻详解 - 火星十一郎 - 博客园
Java反射入门 - Trigl的博客 - CSDN博客
Java反射机制 - ①块腹肌 - 博客园
Java 反射机制浅析 - 孤旅者 - 博客园
反射机制的理解及其用途 - 天天进步一点点! - ITeye博客
Java动态代理与反射详解 - 浩大王 - 博客园

JAVA注解

概念及做用

  1. 概念
  • 注解即元数据,就是源代码的元数据
  • 注解在代码中添加信息提供了一种形式化的方法,能够在后续中更方便的 使用这些数据
  • Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。
  1. 做用
  • 生成文档
  • 跟踪代码依赖性,实现替代配置文件功能,减小配置。如Spring中的一些注解
  • 在编译时进行格式检查,如@Override等
  • 每当你建立描述符性质的类或者接口时,一旦其中包含重复性的工做,就能够考虑使用注解来简化与自动化该过程。

什么是java注解?

在java语法中,使用@符号做为开头,并在@后面紧跟注解名。被运用于类,接口,方法和字段之上,例如:

1
2
3
4
@Override
void myMethod() {
......
}

这其中@Override就是注解。这个注解的做用也就是告诉编译器,myMethod()方法覆写了父类中的myMethod()方法。

java中内置的注解

java中有三个内置的注解:

  • @Override:表示当前的方法定义将覆盖超类中的方法,若是出现错误,编译器就会报错。
  • @Deprecated:若是使用此注解,编译器会出现警告信息。
  • @SuppressWarnings:忽略编译器的警告信息。

本文不在阐述三种内置注解的使用情节和方法,感兴趣的请看这里

元注解

自定义注解的时候用到的,也就是自定义注解的注解;(这句话我本身说的,不知道对不对)

元注解的做用就是负责注解其余注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型做说明。

Java5.0定义的4个元注解:

  1. @Target

  2. @Retention

  3. @Documented

  4. @Inherited

java8加了两个新注解,后续我会讲到。

这些类型和它们所支持的类在java.lang.annotation包中能够找到。

@Target

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

做用:用于描述注解的使用范围(即:被描述的注解能够用在什么地方)

取值(ElementType)有:

类型 用途
CONSTRUCTOR 用于描述构造器
FIELD 用于描述域
LOCAL_VARIABLE 用于描述局部变量
METHOD 用于描述方法
PACKAGE 用于描述包
PARAMETER 用于描述参数
TYPE 用于描述类、接口(包括注解类型) 或enum声明

好比说这个注解表示只能在方法中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.METHOD})
public @interface MyCustomAnnotation {

}

//使用
public class MyClass {
@MyCustomAnnotation
public void myMethod()
{
......
}
}

@Retention

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出如今源代码中,而被编译器丢弃;而另外一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另外一些在class被装载时将被读取(请注意并不影响class的执行,由于Annotation与class在使用上是被分离的)。使用这个meta-Annotation能够对 Annotation的“生命周期”限制。

做用:表示须要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:

类型 用途 说明
SOURCE 在源文件中有效(即源文件保留) 仅出如今源代码中,而被编译器丢弃
CLASS 在class文件中有效(即class保留) 被编译在class文件中
RUNTIME 在运行时有效(即运行时保留) 编译在class文件中

使用示例:

1
2
3
4
5
6
7
8
/***
* 字段注解接口
*/
@Target(value = {ElementType.FIELD})//注解能够被添加在属性上
@Retention(value = RetentionPolicy.RUNTIME)//注解保存在JVM运行时刻,可以在运行时刻经过反射API来获取到注解的信息
public @interface Column {
String name();//注解的name属性
}

@Documented

@Documented用于描述其它类型的annotation应该被做为被标注的程序成员的公共API,所以能够被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

做用:将注解包含在javadoc中

示例:

1
2
3
java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation { //Annotation body}

@Inherited

  • 是一个标记注解
  • 阐述了某个被标注的类型是被继承的
  • 使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类
    @Inherited annotation类型是被标注过的class的子类所继承。类并不从实现的接口继承annotation,方法不从它所重载的方法继承annotation
  • 当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API加强了这种继承性。若是咱们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工做:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

做用:容许子类继承父类中的注解

示例,这里的MyParentClass 使用的注解标注了@Inherited,因此子类能够继承这个注解信息:

1
2
3
4
java.lang.annotation.Inherited
@Inherited
public @interface MyCustomAnnotation {
}
1
2
3
4
@MyCustomAnnotation
public class MyParentClass {
...
}
1
2
3
public class MyChildClass extends MyParentClass { 
...
}

自定义注解

格式

1
2
3
public @interface 注解名{
定义体
}

注解参数的可支持数据类型:

  • 全部基本数据类型(int,float,double,boolean,byte,char,long,short)
  • String 类型
  • Class类型
  • enum类型
  • Annotation类型
  • 以上全部类型的数组

规则

  • 修饰符只能是public 或默认(default)
  • 参数成员只能用基本类型byte,short,int,long,float,double,boolean八种基本类型和String,Enum,Class,annotations及这些类型的数组
  • 若是只有一个参数成员,最好将名称设为”value”
  • 注解元素必须有肯定的值,能够在注解中定义默认值,也可使用注解时指定,非基本类型的值不可为null,常使用空字符串或0做默认值
  • 在表现一个元素存在或缺失的状态时,定义一下特殊值来表示,如空字符串或负值

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* test注解
* @author ddk
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
/**
* id
* @return
*/
public int id() default -1;
/**
* name
* @return
*/
public String name() default "";
}

注解处理器类库

java.lang.reflect.AnnotatedElement

Java使用Annotation接口来表明程序元素前面的注解,该接口是全部Annotation类型的父接口。除此以外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口表明程序中能够接受注解的程序元素,该接口主要有以下几个实现类:

  •  Class:类定义
  •  Constructor:构造器定义
  •  Field:累的成员变量定义
  •  Method:类的方法定义
  •  Package:类的包定义

java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包全部提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement 接口是全部程序元素(Class、Method和Constructor)的父接口,因此程序经过反射获取了某个类的AnnotatedElement对象以后,程序就能够调用该对象的以下四个个方法来访问Annotation信息:

  • 方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,若是该类型注解不存在,则返回null。
  • 方法2:Annotation[] getAnnotations():返回该程序元素上存在的全部注解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,不然返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的全部注释。与此接口中的其余方法不一样,该方法将忽略继承的注释。(若是没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者能够随意修改返回的数组;这不会对其余调用者返回的数组产生任何影响。

注解处理器示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/***********注解声明***************/

/**
* 水果名称注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}

/**
* 水果颜色注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};

/**
* 颜色属性
* @return
*/
Color fruitColor() default Color.GREEN;

}

/**
* 水果供应者注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
* @return
*/
public int id() default -1;

/**
* 供应商名称
* @return
*/
public String name() default "";

/**
* 供应商地址
* @return
*/
public String address() default "";
}

/***********注解使用***************/

public class Apple {

@FruitName("Apple")
private String appleName;

@FruitColor(fruitColor=Color.RED)
private String appleColor;

@FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
private String appleProvider;

public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}

public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}

public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}

public void displayName(){
System.out.println("水果的名字是:苹果");
}
}

/***********注解处理器***************/
//实际上是用的反射


public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){

String strFruitName=" 水果名称:";
String strFruitColor=" 水果颜色:";
String strFruitProvicer="供应商信息:";

Field[] fields = clazz.getDeclaredFields();

for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}

/***********输出结果***************/
public class FruitRun {

/**
* @param args
*/
public static void main(String[] args) {

FruitInfoUtil.getFruitInfo(Apple.class);

}

}

====================================
水果名称:Apple
水果颜色:RED
供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦

Java 8 中注解新特性

  • @Repeatable 元注解,表示被修饰的注解能够用在同一个声明式或者类型加上多个相同的注解(包含不一样的属性值)
  • @Native 元注解,本地方法
  • java8 中Annotation 能够被用在任何使用 Type 的地方
1
2
3
4
5
6
7
8
9
10
11
12
 //初始化对象时
String myString = new @NotNull String();
//对象类型转化时
myString = (@NonNull String) str;
//使用 implements 表达式时
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表达式时
public void validateValues() throws @Critical ValidationFailedException{
...
}

思惟导图

拓展阅读

深刻理解Java:注解 - 牛奶、不加糖 - 博客园
Java 注解基础知识 - 简书
【译】从java注解分析ButterKnife工做流程 - 简书

相关文章
相关标签/搜索