目录
5. 获取 Class 的逻辑属性对象
在获取了 Class 对象之后,若要具体对其中的成员属性、成员方法以及构造器进行操作,就需要先获取它们对应的封装对象。这三个对象对应的类均在 java.lang 的子包 reflect 包中,并且继承了同在子包中的 AccessibleObject 类,其中的暴力反射方法就来自于此类。
(1)获取 Field 对象:操作成员属性
Field | getField(String name) 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。 |
Field[] | getFields() 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。 |
以上两个方法所获取的均为 public 修饰的成员属性,若要想获取所有的成员属性,则要用下面的方法。
Field | getDeclaredField(String name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。 |
Field[] | getDeclaredFields() 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。 |
下面我们来使用一下这几个方法,首先是获取成员属性数组。
public static void main(String[] args) throws NoSuchFieldException {
Person person = new Person("张三",12);
Class<?> aClass = person.getClass();
Field<?>[] fields1 = aClass.getFields();//获取所有public成员变量
Field<?>[] fields2 = aClass.getDeclaredFields();//获取所有成员变量
//遍历fields1
for (Field field:fields1) {
System.out.println(field);
}
System.out.println("----------------分割线----------------");
//遍历fields2
for (Field field:fields2) {
System.out.println(field);
}
}
结果:
public java.lang.Integer com.java.day01.Person.age
----------------分割线----------------
private java.lang.String com.java.day01.Person.name
public java.lang.Integer com.java.day01.Person.age
我们再来看获取指定成员属性的两个方法。
//name是私有的,getField()获取不到,会报错
Field<?> field1 = aClass.getField("name");//报错:java.lang.NoSuchFieldException: name
Field<?> field2 = aClass.getField("age");
Field<?> field3 = aClass.getDeclaredField("name");
Field<?> field4 = aClass.getDeclaredField("age");
那对于获取到的私有属性,我们可以随意操作吗?答案肯定是“不可以”!
Person person = new Person("张三",12);
Class<?> aClass = person.getClass();
Field<?> field = aClass.getDeclaredField("name");//私有属性
Object o = field.get(person);//报错:java.lang.IllegalAccessException
System.out.println(o);
如果我们非要操作私有属性该怎么办呢?此时我们就需要通过 setAccessible ( ) 方法告诉 Field 我们要“暴力反射”来操作私有属性,只需把该方法的参数写为 true ,就可以进行“暴力反射”。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Person person = new Person("张三", 12);
Class<?> aClass = person.getClass();
Field<?> field = aClass.getDeclaredField("name");//私有属性
field.setAccessible(true);//告诉field忽略访问修饰符的限制
field.set(person, "李四");//修改person的name
Object o = field.get(person);
System.out.println(o);
}
结果:李四
我们发现,即使随意修改 Person 的私有属性 name ,依然不会报错。但这同时也破坏了封装,使得封装失去了它的意义。
(2)获取 Constructor 对象:操作构造方法
Constructor<T> | getConstructor(Class<?>... parameterTypes) 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 |
Constructor<?>[] | getConstructors() 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。 |
Constructor<T> | getDeclaredConstructor(Class<?>... parameterTypes) 返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。 |
Constructor<?>[] | getDeclaredConstructors() 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。 |
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
Person person = new Person("张三", 12);
Class<?> aClass = person.getClass();
//获得Constructor对象,参数为构造方法参数的Class对象
Constructor<?> constructor = aClass.getConstructor(String.class, Integer.class);
//若为私有构造方法,则同样要使用"暴力反射"
//constructor.setAccessible(true);
//通过Constructor对象实例化一个新的Object对象
Object object = constructor.newInstance("王五", 34);
//向下转型
Person person1 = (Person) object;
//判断和原对象是否是同一对象:地址是否相等
System.out.println(person == person1);//false(地址不相等,是新对象)
//打印新对象信息
System.out.println(person1.getName() + " " + person1.age);
}
结果:
false
王五 34
从上面的例子中可以看出,即使是私有的构造方法,我们依然可以通过反射机制使其实例化一个对象。
(3)获取 Method 对象:操作成员方法
Method | getMethod(String name, Class<?>... parameterTypes) 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
Method[] | getMethods() 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
Method | getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
Method[] | getDeclaredMethods() 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
Person person = new Person("张三", 12);
Class<?> aClass = person.getClass();
//获取method对象时,不仅要指明方法名,还有指明参数的 Class 对象
Method method = aClass.getMethod("method", String.class);
//私有方法同样暴力反射
//method.setAccessible(true);
//添加指定person对象执行Method对象的方法
method.invoke(person,"方法的参数");
}
结果:
参数:方法的参数 方法执行了
6.Java反射机制的简单应用
在第 2 小节中我们曾给出过一个问题,就是在不改变 Factory 代码的基础上如何实例化任意的 Animal 子类对象,下面我们用 Java 反射机制来解决此问题。
public class Factory {
//实例化对象
public static Animal getInstance(String className) {
Animal animal = null;
try {
Class<?> aClass = Class.forName(className);
animal = (Animal) aClass.newInstance();
} catch (ClassNotFoundException e) {
System.out.println("找不到该类");
} catch (IllegalAccessException e) {
System.out.println("该类无参构造方法是私有的");
} catch (InstantiationException e) {
System.out.println("该类没有默认的构造方法");
} catch (ClassCastException e) {
System.out.println("该类不是动物");
}
return animal;
}
}
可以看出,此时无论声明多少个 Animal 的子类,只要传入的类名格式正确,就可以通过此类名实例化一个对象而无需修改 Factory 代码。
public static void main(String[] args){
Animal animal = null;
//父类不是动物的人
animal = Factory.getInstance("com.java.day01.Person");
//类名不合法
animal = Factory.getInstance("Dog");
//正确的:实例化一只猫
animal = Factory.getInstance("com.java.day02.Cat");
animal.eat();
}
结果:
该类不是动物
找不到该类
猫吃鱼
7.Java反射机制的优点与缺点
(1)优点
可以实现动态创建对象和编译,提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类;反射是其它一些常用语言,如C、C++、Fortran 或者 Pascal 等都不具备的。反射机制通常的使用场景有:
① 各种框架的开发以及工厂模式:在声明一个新的类之后无需修改 Factory 代码。
② 数据库 JDBC:通过 Class.forName(Driver) 来获得数据库连接驱动。
③ 访问私有成员,分析类文件结构与功能。
(2)缺点
① 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。
② 使用反射会模糊程序内部逻辑:程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题;通常反射代码比相应的直接代码更复杂。