疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 打磨着九阳神功和独孤九剑,欢迎围观
QQ群链接:疯狂创客圈QQ群
什么是反射?
在Java 编程时,习惯的方式是通过Class类型,创建对象。
比如,创建一个宠物狗对象的代码为:
Pet pet=new Dog();
如果反过来:已知一个对象,如何知道其Class类型信息呢?
这就要用到反射。
反射的基础
在没有加载到内存之前,每一个类,都作为 .class文件保存在磁盘中,是一个二进制文件。一旦加载入JVM内存,就会在JVM的方法区,创建一个描述java.lang.Class对象,保存这个类的所有信息:名称、属性、方法、构造器等等。
这个java.lang.Class对象,就是反射的基础。下面以宠物狗Dog的类型作为演示类型。Dog类型的代码如下:
package com.crazymakercircle.common.pet;
public class Dog extends Pet {
private static int dogNo; //小狗编号
public Dog() {
dogNo++;
name = "小狗-" + dogNo;
}
}
反射的第一步,就是取得这个Class对象。
第一种方法:Class.forName
Class aClass=Class.forName("com.crazymakercircle.common.pet.Dog");
Logger.debug(aClass);
第二种方法:类型.class
aClass=Dog.class;
Logger.debug(aClass);
第三种方法:对象.getclass()
Dog dog = new Dog();
aClass=dog.getClass();
Logger.debug(aClass);
以上三种方法,都是取得同一个Class对象。运行程序,输出的结果为:
class com.crazymakercircle.common.pet.Dog
class com.crazymakercircle.common.pet.Dog
class com.crazymakercircle.common.pet.Dog
三种方法的输出结果,是一模一样的,就看看使用的时候,哪个更加方便。
通过反射,创建对象
通过反射创建对象,需要调用到Class对象的newInstance 方法。
public static void builderInstance() throws Exception {
//正常的创建
Pet pet = new Dog();
Logger.debug(pet);
//通过反射创建对象
Pet pet1 = Dog.class.newInstance();
Logger.debug(pet1);
}
输出的结果为:
宠物{名称=小狗-1, 年龄=7}
宠物{名称=小狗-2, 年龄=9}
通过结果可以看到:通过构造函数创建对象,和通过反射创建对象,结果是一样的。
通过反射, 访问属性
public static void showField() throws Exception {
//取得属性
Field field = Pet.class.getDeclaredField("name");
Logger.debug(field);
field.setAccessible(true);
Logger.debug(field);
Pet pet =new Dog();
Logger.debug(pet);
//取得对象的属性值
String oldValue= (String) field.get(pet);
Logger.debug("oldvalue:="+oldValue);
//设置对象的属性值
field.set(pet, "未知宠物");
Logger.debug(pet);
}
输出的结果如下:
protected java.lang.String com.crazymakercircle.common.pet.Pet.name
protected java.lang.String com.crazymakercircle.common.pet.Pet.name
宠物{名称=小狗-3, 年龄=2}
oldvalue:=小狗-3
宠物{名称=未知宠物, 年龄=2}
通过上边的代码可以看出:每一个类的属性,在JVM方法区中,有一个java.lang.reflect.Field 对象进行保存。
通过java.lang.reflect.Class对象,可以获取到 java.lang.reflect.Field 对象。
通过Field属性对象,可以取得对象的属性值,也可以设置对象的属性值。分别调用set、get方法即可。
如果Field属性对象不是公有权限,还可以强行设置为公有权限,通过调用Field对象的setAccessible(true),即可。
通过反射, 调用方法
public static void invokeMethod() throws Exception {
//取得sayHello方法
Method method = Pet.class.getDeclaredMethod("sayHello");
Logger.debug(method);
//设置为公有权限
method.setAccessible(false);
Logger.debug(method);
Pet pet =new Dog();
Logger.debug(pet);
//反射调用 sayhello
method.invoke(pet);
//直接调用 sayhell
pet.sayHello();
}
输出的结果如下:
public com.crazymakercircle.common.pet.Pet com.crazymakercircle.common.pet.Pet.sayHello()
public com.crazymakercircle.common.pet.Pet com.crazymakercircle.common.pet.Pet.sayHello()
宠物{名称=小狗-4, 年龄=6}
sayHello:嗨,大家好!我是小狗-4
sayHello:嗨,大家好!我是小狗-4
通过上边的代码可以看出:每一个类的方法,在JVM方法区中,有一个java.lang.reflect.Method 对象进行保存。
通过java.lang.reflect.Class对象,可以获取到 java.lang.reflect.Method 对象。
通过Method 属性对象的invoke方法, 可以直接调用object对象 的对应的Method 方法,与通过object对象直接调用Method 方法,结果是一样的。
上文中,直接调用pet.sayHello() 和反射调用 method.invoke(pet) 的结果,是完全一致。
如果Method 方法对象不是公有权限,还可以强行设置为公有权限,通过调用Method 对象的setAccessible(true),即可。这个与Field 类似。
通过反射,调用基类的方法
public static void invokeParentMethod() throws Exception {
Method method = Cat.class.getMethod("sayHello");
Pet pet =new Cat();
Logger.debug(pet);
method.invoke(pet);
pet.sayHello();
}
输出的结果如下:
宠物{名称=小猫-1, 年龄=15}
sayHello:嗨,大家好!我是小猫-1
sayHello:嗨,大家好!我是小猫-1
Class对象的getDeclaredMethod ,只能返回在当前类中定义的方法,不能返回在基类中定义的方法。
使用Class对象的getMethod 方法,可以返回在任何基类中定义的方法。
通过反射,取得注解标记
public static void checkAnno() throws Exception {
Field field = Pet.class.getDeclaredField("age");
AgeRange annotation=field.getAnnotation(AgeRange.class);
Pet pet =new Dog();
Logger.debug(pet);
if(pet.getAge()>annotation.max()){
Logger.debug("age is too big:="+pet.getAge());
} else if(pet.getAge()<annotation.min()){
Logger.debug("age is too small:="+pet.getAge());
}else {
Logger.debug("age is valid:="+pet.getAge());
}
}
输出的结果如下:
宠物{名称=小狗-5, 年龄=18}
age is too big:=18
通过 field.getAnnotation(AgeRange.class) 的方式,可以去取得定义在属性前面的注解标记:
@AgeRange(max = 15,min = 2)
protected int age;
这个注解包含了两个方法:max,min如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AgeRange {
int min();
int max();
}
取得标记对象后,可以直接访问标记的方法,去取得配置的值。 根据配置值进行相对应的业务逻辑的校验:
if(pet.getAge()>annotation.max()){
Logger.debug("age is too big:="+pet.getAge());
} else if(pet.getAge()<annotation.min()){
Logger.debug("age is too small:="+pet.getAge());
}else {
Logger.debug("age is valid:="+pet.getAge());
}
理解反射,使用反射,对于java编程来说,是一个非常重要的基础。
这一小节,只是简单的提到了Java注解。关于Java注解的深入的介绍,另一篇文章。源码在疯狂创客圈QQ群下载。