反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的方法,属性,构造方法等成员。
反射又称之为应用程序的自省:也就是获取和操作自身的所有的属性和行为。
必须先要获取到该类的字节码文件对象,即Class类型对象。关于Class描述字节码文件如下图所示:
说明:
1)Java中使用Class类表示某个class文件.
2)任何一个class文件都是Class这个类的一个实例对象.
public class Student {
static {
System.out.println("静态代码块");
}
{
System.out.println("构造代码块");
}
}
public class Demo01 {
public static void main(String[] args) {
// 获得Student的Class的对象
Class c = Student.class;
// 打印输出:class com.itheima.reflect.Student
System.out.println(c);
}
}
public class Demo01 {
public static void main(String[] args) {
// 创建学生对象
Student stu = new Student();
// 获得学生类的Class对象
Class c = stu.getClass();
// 打印输出:class com.itheima.reflect.Student
System.out.println(c);
}
}
public class Demo01 {
public static void main(String[] args) throws Exception {
// 获得字符串的Class对象
Class c = Class.forName("java.lang.String");
// 打印输出:class java.lang.String
System.out.println(c);
}
}
知道怎么获取Class对象之后,接下来就介绍几个Class类中常用的方法了。
String getSimpleName(); 获得简单类名,只是类名,没有包
String getName(); 获取完整类名,包含包名+类名
T newInstance() ;创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法
public class Demo02 {
public static void main(String[] args) throws Exception {
// 获得字符串的Class对象
Class c = Class.forName("java.lang.String");
// 获得简单类名
String name = c.getSimpleName();
// 打印输入:name = String
System.out.println("name = " + name);
}
}
public class Demo02 {
public static void main(String[] args) throws Exception {
// 获得字符串的Class对象
Class c = Class.forName("java.lang.String");
// 获得完整类名(包含包名和类名)
String name = c.getName();
// 打印输入:name = java.lang.String
System.out.println("name = " + name);
}
}
public class Demo02 {
public static void main(String[] args) throws Exception {
// 获得字符串的Class对象
Class c = Class.forName("java.lang.String");
// 创建字符串对象
String str = (String) c.newInstance();
// 输出str:空字符串 ""
System.out.println(str);
}
}
一开始在阐述反射概念的时候,我们说到利用反射可以在程序运行过程中对类进行解剖并操作里面的成员。而一般常操作的成员有构造方法,成员方法,成员变量等等,那么接下来就来看看怎么利用反射来操作这些成员以及操作这些成员能干什么,先来看看怎么操作构造方法。而要通过反射操作类的构造方法,我们需要先知道一个Constructor类。
Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对象。
1. Constructor getConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,包括private修饰的构造方法。
如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
3. Constructor[] getConstructors()
获取所有的public修饰的构造方法
4. Constructor[] getDeclaredConstructors()
获取所有构造方法,包括privat修饰的
1. T newInstance(Object... initargs)
根据指定参数创建对象。
2. void setAccessible(true)
暴力反射,设置为可以直接访问私有类型的构造方法。
/**
* @author pkxing
* @version 1.0
* @description com.itheima
* @date 2018/1/25
*/
public class Student {
// 姓名
private String name;
// 性别
public String gender;
// 年龄
private int age;
// public 有参构造方法
public Student(String name, String gender, int age) {
System.out.println("public 修饰有参数构造方法");
this.name = name;
this.gender = gender;
this.age = age;
}
// public 无参构造方法
public Student() {
System.out.println("public 修饰无参数构造方法");
}
// private 有参构造方法
private Student(String name,String gender){
System.out.println("private 修饰构造方法");
this.name = name;
this.gender = gender;
}
// getter & setter 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 普通方法
public void sleep(){
System.out.println("睡觉");
}
public void sleep(int hour){
System.out.println("public修饰---sleep---睡" + hour + "小时");
}
private void eat(){
System.out.println("private修饰---eat方法---吃饭");
}
// 静态方法
public static void study(){
System.out.println("静态方法---study方法---好好学习Java");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
/**
* @author pkxing
* @version 1.0
* @description 获取Class对象的Constructor信息
* @date 2018/1/26
*/
public class Demo03 {
public static void main(String[] args)throws Exception{
test01();
test02();
test03();
test04();
}
/**
4. Constructor[] getDeclaredConstructors()
获取所有构造方法,包括privat修饰的
*/
public static void test04() throws Exception{
System.out.println("----------- test04() -----------");
// 获取Student类的Class对象
Class c = Student.class;
// 获取所有的public修饰的构造方法
Constructor[] cons = c.getDeclaredConstructors();
// 遍历构造方法数组
for(Constructor con:cons) {
// 输出con
System.out.println(con);
}
}
/**
3. Constructor[] getConstructors()
获取所有的public修饰的构造方法
*/
public static void test03() throws Exception{
System.out.println("----------- test03() -----------");
// 获取Student类的Class对象
Class c = Student.class;
// 获取所有的public修饰的构造方法
Constructor[] cons = c.getConstructors();
// 遍历构造方法数组
for(Constructor con:cons) {
// 输出con
System.out.println(con);
}
}
/**
2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,包括private修饰的构造方法。
如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
*/
public static void test02() throws Exception{
System.out.println("----------- test02() -----------");
// 获取Student类的Class对象
Class c = Student.class;
// 根据参数获取对应的private修饰构造方法对象
Constructor cons = c.getDeclaredConstructor(String.class,String.class);
// 注意:private的构造方法不能直接调用newInstance创建对象,需要暴力反射才可以
// 设置取消权限检查(暴力反射)
cons.setAccessible(true);
// 调用Constructor方法创建学生对象
Student stu = (Student) cons.newInstance("林青霞","女");
// 输出stu
System.out.println(stu);
}
/**
1. Constructor getConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
*/
public static void test01() throws Exception{
System.out.println("----------- test01() -----------");
// 获取Student类的Class对象
Class c = Student.class;
// 根据参数获取对应的构造方法对象
Constructor cons = c.getConstructor(String.class,String.class,int.class);
// 调用Constructor方法创建学生对象
Student stu = (Student) cons.newInstance("张曼玉","女",28);
// 输出stu
System.out.println(stu);
}
}
操作完构造方法之后,就来看看反射怎么操作成员方法了。同样的在操作成员方法之前我们需要学习一个类:Method类。
Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用方法。
1. Method getMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
2. Method getDeclaredMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,包括private修饰的
3. Method[] getMethods()
获取所有的public修饰的成员方法,包括父类中。
4. Method[] getDeclaredMethods()
获取当前类中所有的方法,包含私有的,不包括父类中。
1. Object invoke(Object obj, Object... args)
根据参数args调用对象obj的该成员方法
如果obj=null,则表示该方法是静态方法
2. void setAccessible(boolean flag)
暴力反射,设置为可以直接调用私有修饰的成员方法
/**
* @author pkxing
* @version 1.0
* @description 获取Class对象的Method信息
* @date 2018/1/26
*/
public class Demo04 {
public static void main(String[] args)throws Exception{
// 获得Class对象
Class c = Student.class;
// 快速创建一个学生对象
Student stu = (Student ) c.newInstance();
// 获得public修饰的方法对象
Method m1 = c.getMethod("sleep",int.class);
// 调用方法m1
m1.invoke(stu,8);
// 获得private修饰的方法对象
Method m2 = c.getDeclaredMethod("eat");
// 注意:private的成员方法不能直接调用,需要暴力反射才可以
// 设置取消权限检查(暴力反射)
m2.setAccessible(true);
// 调用方法m2
m2.invoke(stu);
// 获得静态方法对象
Method m3 = c.getDeclaredMethod("study");
// 调用方法m3
// 注意:调用静态方法时,obj可以为null
m3.invoke(null);
System.out.println("------------获得所有public的方法,不包括private,包括父类的---------------");
// 获得所有public的方法,包括父类的
Method[] ms = c.getMethods();
// 遍历方法数组
for(Method m : ms) {
System.out.println(m);
}
System.out.println("-----------获得所有方法,包括private,不包括父类-----------------------");
// 获得所有方法,包括private,不包括父
Method[] ms2 = c.getDeclaredMethods();
// 遍历方法数组
for(Method m : ms2) {
System.out.println(m);
}
}
}
Field是属性类,类中的每一个属性(成员变量)都是Field的对象,通过Field对象可以给对应的成员变量赋值和取值。
1. Field getDeclaredField(String name)
根据属性名获得属性对象,包括private修饰的
2. Field getField(String name)
根据属性名获得属性对象,只能获取public修饰的
3. Field[] getFields()
获取所有的public修饰的属性对象,返回数组。
4. Field[] getDeclaredFields()
获取所有的属性对象,包括private修饰的,返回数组。
void set(Object obj, Object value)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)
Object get(Object obj)
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。
/**
* @author pkxing
* @version 1.0
* @description 获取Class对象的Field信息
* @date 2018/1/26
*/
public class Demo05 {
public static void main(String[] args)throws Exception{
// 获得Class对象
Class c = Student.class;
// 快速创建一个学生对象
Student stu = (Student ) c.newInstance();
// 获得public修饰Field对象
Field f1 = c.getField("gender");
// 通过f1对象给对象stu的gender属性赋值
f1.set(stu,"风清扬");
// 通过f1对象获取对象stu的gender属性值
String gender = (String) f1.get(stu);
System.out.println("性别:" + gender);
// 获得private修饰Field对象
Field f2 = c.getDeclaredField("age");
// 注意:private的属性不能直接访问,需要暴力反射才可以
// 设置取消权限检查(暴力反射)
f2.setAccessible(true);
// 通过f1对象给对象stu的age属性赋值
f2.setInt(stu,30);
// 通过f2对象获取对象stu的age属性值
int age = f2.getInt(stu);
System.out.println("年龄:" + age);
System.out.println("-------获得所有public修饰的属性--------");
// 获得所有public修饰的属性
Field[] fs1 = c.getFields();
// 遍历数组
for(Field f : fs1) {
System.out.println(f);
}
System.out.println("-------获得所有的属性,包括private修饰--------");
// 获得所有的属性,包括private修饰
Field[] fs2 = c.getDeclaredFields();
// 遍历数组
for(Field f : fs2) {
System.out.println(f);
}
}
}
编写一个工厂方法可以根据配置文件产任意类型的对象。
例如有配置文件stu.properties,存储在项目的src文件夹下,内容如下:
class=com.itheima.reflect.Student
name=rose
gender=女
age=18
根据配置文件信息创建一个学生对象。
/**
* @author pkxing
* @version 1.0
* @description com.itheima.reflect
* @date 2018/1/26
*/
public class Demo06 {
public static void main(String[] args){
// 获取对象
Student stu = (Student) createObject();
// 输出对象
System.out.println(stu);
}
/**
* 根据配置文件创建对象
*/
public static Object createObject(){
try {
// 创建属性集合
Properties pro = new Properties();
// 从文件中加载内容到集合中
pro.load(Demo06.class.getResourceAsStream("/stu.properties"));
// 从集合中获得类名
String className = pro.getProperty("class");
// 通过反射获得Class对象
Class c = Class.forName(className);
// 快速创建对象
Object obj = c.newInstance();
// 遍历集合
Set<String> names = pro.stringPropertyNames();
for (String name : names) {
// 判断name是否class
if (name.equals("class")) continue;
// 获得值
String value = pro.getProperty(name);
// name:成员变量名
// 根据成员变量名获得对应的Field对象
Field f = c.getDeclaredField(name);
// 暴力反射
f.setAccessible(true);
// 获得成员变量的类型
Class typeClass = f.getType();
if(typeClass == int.class){ // 判断成员变量的数据类型是否是int类型
f.setInt(obj, Integer.parseInt(value));
} else {
// 给f对象的赋值
f.set(obj, value);
}
}
// 返回对象
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
该反射案例的目的是让同学们感受反射机制的强大之处,在后面即将学习的Spring框架中就会有大量根据配置文件信息创建对象的过程,其内部的原理和我们这个案例的原理是一样,有这个案例做基础,以后学到spring框架时就会容易理解了。
可能有同学有这样的想法,反射机制确实是强大,但是如果从每次配置文件中读取信息给对象属性赋值时都需要写这么复杂的代码,做这么多条件判断的话,那就会严重影响工作效率,有没有更好的方式来更方便,更有效率的解决这个问题?答案就是:有,如果想使用更方便的方式给对象封装数据的话,我们可以使用一个非常方便的第三方工具:BeanUtils。接下来就来看看BeanUtils是什么以及怎么使用。
BeanUtils是Apache commons组件的成员之一,主要用于简化JavaBean封装数据的操作。常用的操作有以下三个:
JavaBean就是一个类,但该类需要满足以下三个条件:
类必须使用public修饰。
提供无参数的构造器。
提供getter和setter方法访问属性。
JavaBean的两个重要概念
字段:就是成员变量,字段名就是成员变量名。
属性:属性名通过setter/getter方法去掉set/get前缀,首字母小写获得。
字段名和属性名不一致的情况
// 成员变量
private String description;
// getter & setter 方法
public String getDesc(){
return this.description;
}
public void setDesc(String desc) {
this.descripiton = desc;
}
commons-beanutils-1.9.3.jar // 工具核心包
commons-logging-1.2.jar // 日志记录包
commons-collections-3.2.2.jar // 增强的集合包
public static void setProperty(Object bean, String name, Object value)
给指定对象bean的指定name属性赋值为指定值value。
如果指定的属性不存在,则什么也不发生。
public static String getProperty(Object bean, String name)
获取指定对象bean指定name属性的值。
如果指定的属性不存在,则会抛出异常。
注意;当属性的类型是数组类型时,获取到的值数组中的第一个值。
public static void copyProperties(Object dest, Object orig)
将对象orig的属性值赋值给对象dest对象对应的属性
注意:只有属性名名相同且类型一致的才会赋值成功。
public static void populate(Object bean, Map<String, ? extends Object> properties)
将一个Map集合中的数据封装到指定对象bean中
注意:对象bean的属性名和Map集合中键要相同。
/**
* @author pkxing
* @version 1.0
* @description com.itheima
* @date 2018/1/25
*/
public class Student {
// 姓名
private String name;
// 性别
private String gender;
// 年龄
private int age;
// 爱好
private String[] hobbies;
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
", hobbies=" + Arrays.toString(hobbies) +
'}';
}
}
/**
* @author pkxing
* @version 1.0
* @description 对JavaBean的属性进行赋值和取值。
* @date 2018/1/25
*/
public class BeanUtilsDemo01 {
/**
public static void setProperty(Object bean, String name, Object value)
给指定对象bean的指定name属性赋值为指定值value。
如果指定的属性不存在,则什么也不发生。
public static String getProperty(Object bean, String name)
获取指定对象bean指定name属性的值。
如果指定的属性不存在,则会抛出异常。
注意;当属性的类型是数组类型时,获取到的值数组中的第一个值。
*/
public static void main(String[] args) throws Exception {
// 创建学生对象
Student stu = new Student();
// 调用BeanUtils工具类的方法给对象属性赋值
BeanUtils.setProperty(stu,"name","风清扬");
BeanUtils.setProperty(stu,"gender","男");
BeanUtils.setProperty(stu,"age",40);
BeanUtils.setProperty(stu,"hobbies",new String[]{"敲代码","打篮球"});
// 输出对象到控制台
System.out.println(stu);
// 调用BeanUtils工具类的方法获取对象属性值
String name = BeanUtils.getProperty(stu,"name");
String gender = BeanUtils.getProperty(stu,"gender");
String age = BeanUtils.getProperty(stu,"age");
String hobbies = BeanUtils.getProperty(stu,"hobbies");
System.out.println("姓名:"+ name);
System.out.println("性别:"+ gender);
System.out.println("年龄:" + age);
System.out.println("爱好:" + hobbies);
}
}
/**
* @author pkxing
* @version 1.0
* @description 将一个JavaBean对象的属性赋值给另一个JavaBean对象。
* @date 2018/1/25
*/
public class BeanUtilsDemo02 {
/**
public static void copyProperties(Object dest, Object orig)
将对象orig的属性值赋值给对象dest对象对应的属性
注意:只有属性名名相同且类型一致的才会赋值成功。
* @throws Exception
*/
public static void main(String[] args)throws Exception{
// 创建学生对象
Student stu = new Student();
// 调用BeanUtils工具类的方法给对象属性赋值
BeanUtils.setProperty(stu,"name","风清扬");
BeanUtils.setProperty(stu,"gender","男");
BeanUtils.setProperty(stu,"age",40);
BeanUtils.setProperty(stu,"hobbies",new String[]{"敲代码","打篮球"});
// 再创建一个学生对象
Student newStu = new Student();
// 调用BeanUtils工具类的方法将stu对象的属性值赋值给newStu对象
BeanUtils.copyProperties(newStu,stu);
// 输出stu和newStu对象
System.out.println(stu);
System.out.println(newStu);
}
}
public class User {
// 姓名
private String name;
// 性别
private String gender;
// 地址
private String address;
// 年龄
private String age;
public User() {
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", address='" + address + '\'' +
", age='" + age + '\'' +
'}';
}
}
/**
* @author pkxing
* @version 1.0
* @description 将一个JavaBean对象的属性赋值给另一个JavaBean对象。
* @date 2018/1/25
*/
public class BeanUtilsDemo03 {
/**
public static void copyProperties(Object dest, Object orig)
将对象orig的属性值赋值给对象dest对象对应的属性
注意:只有属性名名相同且类型一致的才会赋值成功。
*/
public static void main(String[] args)throws Exception {
// 创建学生对象
Student stu = new Student();
// 调用BeanUtils工具类的方法给对象属性赋值
BeanUtils.setProperty(stu,"name","风清扬");
BeanUtils.setProperty(stu,"gender","男");
BeanUtils.setProperty(stu,"age",40);
BeanUtils.setProperty(stu,"aaa","xxx");
BeanUtils.setProperty(stu,"hobbies",new String[]{"敲代码","打篮球"});
// 创建用户对象
User user = new User();
// 调用BeanUtils工具类的方法将stu对象的属性值赋值给user对象
BeanUtils.copyProperties(user,stu);
// 输出stu和user对象
System.out.println(user);
System.out.println(stu);
}
}
/**
* @author pkxing
* @version 1.0
* @description 将一个Map集合的数据封装到一个JavaBean对象中。
* @date 2018/1/25
*/
public class BeanUtilsDemo04 {
/**
*
public static void populate(Object bean, Map properties)
将一个Map集合中的数据封装到指定对象bean中
注意:对象bean的属性名和Map集合中键要相同。
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 创建map集合
Map<String,Object> map = new HashMap<String, Object>();
map.put("name","林青霞");
map.put("gender","女");
map.put("age","38");
map.put("hobbies",new String[]{"唱歌","跳舞"});
// 创建学生对象
Student stu = new Student();
System.out.println("封装前:" + stu);
// 调用BeanUtils工具类的方法将map数据封装到stu中
BeanUtils.populate(stu,map);
// 输出对象stu
System.out.println("封装后:" + stu);
}
}
注解是JDK1.5的新特性。
注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
注解的作用就是给程序带入参数。
以下几个常用操作中都使用到了注解:
生成帮助文档:@author和@version
@author:用来标识作者姓名。
@version:用于标识对象的版本号,适用范围:文件、类、方法。
编译检查:@Override
@Override:用来修饰方法声明。
框架的配置(框架=代码+配置)
作为标记,就比如说Junit单元测试中:@Test
public @interface 注解名{
}
如:定义一个名为Student的注解
public @interface Student {
}
属性的作用
属性的格式
属性定义示例
public @interface Student {
String name(); // 姓名
int age() default 18; // 年龄
String gender() default "男"; // 性别
}
// 该注解就有了三个属性:name,age,gender
属性适用的数据类型
定义一个注解:Book
代码实现
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
定义类在成员方法上使用Book注解
/**
* @author pkxing
* @version 1.0
* @description 书架类
* @date 2018/1/26
*/
public class BookShelf {
@Book(value = "西游记",price = 998,authors = {"吴承恩","白求恩"})
public void showBook(){
}
}
使用注意事项
// 定义注解Book
public @interface Book {
// 书名
String value();
}
// 使用注解Book
public class BookShelf {
@Book("西游记")
public void showBook(){
}
}
或
public class BookShelf {
@Book(value="西游记")
public void showBook(){
}
}
// 定义注解Book
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
// 使用Book注解:正确方式
@Book(value="红楼梦",authors = "曹雪芹")
public class BookShelf {
// 使用Book注解:正确方式
@Book(value="西游记",authors = {"吴承恩","白求恩"})
public void showBook(){
}
}
// 使用Book注解:错误方式
public class BookShelf {
@Book("西游记",authors = {"吴承恩","白求恩"})
public void showBook(){
}
}
// 此时value属性名不能省略了。
现在我们已经学会了如何定义注解以及如何使用注解了,可能细心的同学会发现一个问题:我们定义的注解是可以使用在任何成员上的,比如刚刚Book注解的使用:
// 定义注解Book
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 多位作者
String[] authors();
}
// 使用Book注解:正确方式
@Book(value="红楼梦",authors = "曹雪芹")
public class BookShelf {
// 使用Book注解:正确方式
@Book(value="西游记",authors = {"吴承恩","白求恩"})
public void showBook(){
}
}
可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
作用:定义该注解的生命周期(有效范围)。
可选的参数值在枚举类型RetentionPolicy中包括
SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。
@Target({ElementType.METHOD,ElementType.TYPE})
@interface Stu{
String name();
}
// 类
@Stu(name="jack")
public class AnnotationDemo02 {
// 成员变量
@Stu(name = "lily") // 编译失败
private String gender;
// 成员方法
@Stu(name="rose")
public void test(){
}
// 构造方法
@Stu(name="lucy") // 编译失败
public AnnotationDemo02(){}
}
通过Java技术获取注解数据的过程则称为注解解析。
Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个:
boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回true,否则返回false。
T getAnnotation(Class<T> annotationClass); 获得当前对象上指定的注解对象。
Annotation[] getAnnotations(); 获得当前对象及其从父类上继承的所有的注解对象。
Annotation[] getDeclaredAnnotations();获得当前对象上所有的注解对象,不包括父类的。
注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。
// 得到方法对象
Method method = clazz.getDeclaredMethod("方法名");
// 根据注解名得到方法上的注解对象
Book book = method.getAnnotation(Book.class);
如注解作用在类上,就通过Class对象得到它的注解
// 获得Class对象
Class c = 类名.class;
// 根据注解的Class获得使用在类上的注解对象
Book book = c.getAnnotation(Book.class);
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
// 书名
String value();
// 价格
double price() default 100;
// 作者
String[] authors();
}
@Book(value = "红楼梦",authors = "曹雪芹",,price = 998)
public class BookStore {
@Book(value = "西游记",authors = "吴承恩")
public void buyBook(){
}
}
/**
* @author pkxing
* @version 1.0
* @description com.itheima.annotation
* @date 2018/1/26
*/
public class TestAnnotation {
public static void main(String[] args) throws Exception{
System.out.println("---------获取类上注解的数据----------");
test01();
System.out.println("---------获取成员方法上注解的数据----------");
test02();
}
/**
* 获取BookStore类上使用的Book注解数据
*/
public static void test01(){
// 获得BookStore类对应的Class对象
Class c = BookStore.class;
// 根据注解Class对象获取注解对象
Book book = (Book) c.getAnnotation(Book.class);
// 输出book注解属性值
System.out.println("书名:" + book.value());
System.out.println("价格:" + book.price());
System.out.println("作者:" + Arrays.toString(book.authors()));
}
/**
* 获取BookStore类成员方法buyBook使用的Book注解数据
*/
public static void test02() throws Exception{
// 获得BookStore类对应的Class对象
Class c = BookStore.class;
// 获得成员方法buyBook对应的Method对象
Method m = c.getMethod("buyBook");
// 根据注解Class对象获取注解对象
Book book = (Book) m.getAnnotation(Book.class);
// 输出book注解属性值
System.out.println("书名:" + book.value());
System.out.println("价格:" + book.price());
System.out.println("作者:" + Arrays.toString(book.authors()));
}
}
TestAnnotation类在获取注解数据时处理得不够严谨,假如出现下面的其中一种情况:
再运行TestAnnotation类代码则会出现空指针异常,如下图所示:
解决方案
public class TestAnnotation {
public static void main(String[] args) throws Exception{
System.out.println("---------获取类上注解的数据----------");
test01();
System.out.println("---------获取成员方法上注解的数据----------");
test02();
}
/**
* 获取BookStore类上使用的Book注解数据
*/
public static void test01(){
// 获得BookStore类对应的Class对象
Class c = BookStore.class;
// 判断BookStore类是否使用了Book注解
if(c.isAnnotationPresent(Book.class)) {
// 根据注解Class对象获取注解对象
Book book = (Book) c.getAnnotation(Book.class);
// 输出book注解属性值
System.out.println("书名:" + book.value());
System.out.println("价格:" + book.price());
System.out.println("作者:" + Arrays.toString(book.authors()));
}
}
/**
* 获取BookStore类成员方法buyBook使用的Book注解数据
*/
public static void test02() throws Exception{
// 获得BookStore类对应的Class对象
Class c = BookStore.class;
// 获得成员方法buyBook对应的Method对象
Method m = c.getMethod("buyBook");
// 判断成员方法buyBook上是否使用了Book注解
if(m.isAnnotationPresent(Book.class)) {
// 根据注解Class对象获取注解对象
Book book = (Book) m.getAnnotation(Book.class);
// 输出book注解属性值
System.out.println("书名:" + book.value());
System.out.println("价格:" + book.price());
System.out.println("作者:" + Arrays.toString(book.authors()));
}
}
}
模拟Junit测试的@Test
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
public class MyTestDemo {
@MyTest
public void test01(){
System.out.println("test01");
}
public void test02(){
System.out.println("test02");
}
@MyTest
public void test03(){
System.out.println("test03");
}
}
public class TestMyTest {
public static void main(String[] args) throws Exception{
// 获得MyTestDemo类Class对象
Class c = MyTestDemo.class;
// 获得所有的成员方法对象
Method[] methods = c.getMethods();
// 创建MyTestDemo类对象
Object obj = c.newInstance();
// 遍历数组
for (Method m:methods) {
// 判断方法m上是否使用注解MyTest
if(m.isAnnotationPresent(MyTest.class)){
// 执行方法m
m.invoke(obj);
}
}
}
}