目录
一、通过反射查看类信息
1.获得Class对象
2.获取Class中的信息
二、使用反射生成并操作对象
1.创建对象
2.调用方法
3.访问Field
Java程序中的许多对象在运行时都会出现两种类型:编译时类型和运行时类型,如Person p = new Student(),p变量编译时类型为Person,而运行时类型为Student。我们常常需要在程序运行时获取对象和类的真实信息,这就有两种可能:
一是在编译和运行时完全知道类型的具体信息,可以直接使用instanceof运算符进行判断。instanceof运算符的前一个操作数通常是一个引用型变量,后一个操作数可以是一个类或接口,它用于判断前面的对象是否是后面的类或者其子类、实现类的实例。如:
public class InstanceofTest {
public static void main(String[] args) {
// 声明hello时使用Object类,则hello的编译类型是Object,但hello变量的实际类型是String
Object hello = "Hello";
// 以下三条语句的返回结果均为true
// String是Object类的子类,可以进行instanceof运算。
System.out.println("字符串是否是Object类的实例:" + (hello instanceof Object));
System.out.println("字符串是否是String类的实例:" + (hello instanceof String));
// String实现了Comparable接口,所以返回true。
System.out.println("字符串是否是Comparable接口的实例:" + (hello instanceof Comparable));
}
}
每个类在被加载之后系统都会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。获得Class对象通常有3种方式:
使用 static Class> forName(String className) throws ClassNotFoundException返回与带有给定字符串名的类或接口相关联的 Class 对象。其中,className必须是某个类的全限定类名(即需添加完整包名)。
调用某个类的class属性来获取该类对应的Class对象。如:Person.class。
调用某个对象的getClass()方法。该方法是java.lang.Object包中的一个方法,所有对象均可调用,该方法会返回该对象所属类对应的Class对象。
package reflect;
public class TestGetClass {
public static void main(String[] args) throws ClassNotFoundException {
Person p = new Person();
System.out.println(Class.forName("reflect.Person").getName());
System.out.println(Person.class.getName());
System.out.println(p.getClass().getName());
}
}
class Person {
}
运行结果:
其中,b.方法由于在编译阶段就可以检查需要访问的Class对象是否存在,且无需调用方法,所以更加安全,性能更好。
Class类中提供了大量的实例方法来获得该Class对象的所对应类的详细信息,包括:获取Class对应类所包含的构造方法、获取Class对应类所包含的方法、获取Class对应类所包含的Field、获取Class对应类所包含的Annotation、获取Class对应类所包含的内部类、获取Class对应类所继承的父类和所实现的接口、获取Class对应类的修饰符、所在包以及类名等基本信息,除此之外,还有方法可以判断该类是否为接口、枚举、注释类型等。
import java.lang.reflect.Constructor;
public class GetClassInfo {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Class c = Student.class;
// 返回对应形参为String类型的public构造方法
System.out.println(c.getConstructor(String.class));
// 返回无形参的构造方法,无论其访问权限为何
System.out.println(c.getDeclaredConstructor());
// 返回所有的构造方法,无论其访问权限为何
Constructor[] csts = c.getDeclaredConstructors();
for (Constructor cst : csts) {
System.out.println(cst);
}
}
}
class Student {
private String id;
private Student() {
}
public Student(String id) {
this.id = id;
}
}
运行结果:
java.lang.reflect包java 1.8之前及java 1.8的改变:
在java.lang.reflect包中包含了Method类、Constructor类和Field类,这三个类都实现了java.lang.reflect.Member接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造方法创建实例,通过Field对象直接访问并修改对象的属性值。
通过反射创建对象有两种方式:
直接使用Class对象的newInstance()方法。该方式需要Class对象对应的类有默认的构造方法。
先使用Class对象通过getConstructor()方法获取指定的Constructor对象,再调用Constructor对象的newInstance()方法创建Java对象。通过这种方法可以选择指定的构造方法来创建实例。
import java.util.*;
import java.io.*;
import java.lang.reflect.Constructor;
/**
* @Title ObjectPoolFactory.java
* @Description TODO
* @Author 15643
* @Time 2018年8月28日 上午10:25:24
* @Other obj.txt中的内容:a=java.util.Date
*/
public class ObjectPoolFactory {
// 定义一个对象池,前面是对象名,后面是实际对象
private Map objectPool = new HashMap<>();
// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private static Object createObject(String clazzName)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
// 根据字符串来获取对应的Class对象
Class> clazz = Class.forName(clazzName);
// 使用clazz对应类的默认构造器创建实例
return clazz.newInstance();
}
// 该方法根据指定文件来初始化对象池,并根据配置文件来创建对象
public void initPool(String fileName)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try (FileInputStream fis = new FileInputStream(fileName)) {
Properties props = new Properties();
props.load(fis);
for (String name : props.stringPropertyNames()) {
// 根据键值对的值创建一个对象,并将对象添加到对象池中
objectPool.put(name, createObject(props.getProperty(name)));
}
} catch (IOException ex) {
System.out.println("读取" + fileName + "异常");
}
}
public Object getObject(String name) {
// 从objectPool中取出指定name对应的对象。
return objectPool.get(name);
}
public static void main(String[] args) throws Exception {
ObjectPoolFactory pf = new ObjectPoolFactory();
pf.initPool("obj.txt");
System.out.println(pf.getObject("a"));
// 直接在这里示范了,使用指定的构造方法创建对象
Constructor> c = pf.getObject("a").getClass().getConstructor(long.class);
System.out.println(c.newInstance(1000));
}
}
运行结果:
在Class类里面有四个用来获取方法的方法,getMethod()、getDeclaredMethod()、getDeclaredMethods()、getMethods(),前两个返回Method对象,后两个返回Method对象数组。
Method类的常用方法:
Object invoke(Object obj, Object... args) |
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。obj - 从中调用底层方法的对象,args - 执行方法时传入的实参 |
boolean equals(Object obj) | 将此 Method 与指定对象进行比较。 |
String getName() | 以 String 形式返回此 Method 对象表示的方法名称。 |
Class>[] getParameterTypes() | 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。 |
Class> getReturnType() | 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。 |
import java.lang.reflect.*;
public class TestMethod {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InvocationTargetException,
InstantiationException, IllegalAccessException {
String field = "id";
Class> cl = Employee.class;
Object obj = cl.newInstance();
// 使用get+首字母大写的Field来匹配方法
Method getMt = cl.getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1));
Method setMt = cl.getMethod("set" + field.substring(0, 1).toUpperCase() + field.substring(1), String.class);
// 使用invoke()调用Method对象对应表示的方法
setMt.invoke(obj, "100001");
System.out.println("StudentID is " + getMt.invoke(obj));
}
}
class Employee {
private String id;
public Employee() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
若程序需要调用某个对象的private方法,则可以先用Method对象调用setAccessible(boolean flag),当flag为true时,则指示反射的对象在使用时应该取消 Java 语言访问权限检查。值为 false 则指示反射的对象应该实施 Java 语言访问权限检查。 (setAccessible()方法是属于AccessibleObject类的,由于它是Method、Constructor、Field的父类,所以它们都可以调用该方法从而通过反射来调用私有的方法、属性等。)
Field类提供了两组方法来访问、设置Field的值:
getXxx(Object obj) | 获取obj对象的该Field的属性值。Xxx对应8个基本类型,若为引用类型,则为get(Object obj)方法 |
setXxx(Object obj,Xxx value) | 将obj对象的该Field设置成value值。Xxx对应8个基本类型,若为引用类型,则为set(Object obj,Object value)方法 |
import java.lang.reflect.*;
public class FieldTest {
public static void main(String[] args) throws Exception {
Class cl = Employee.class;
Object p = cl.newInstance();
// 获取Person的名为id的Field,使用getDeclaredField无视访问控制符获取field
Field idField = cl.getDeclaredField("id");
// 设置通过反射访问该Field时取消访问权限检查,否则IllegalAccessException
idField.setAccessible(true);
idField.set(p, "1000001");
System.out.println(p);
}
}
class Employee {
private String id;
public Employee() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "StudentID is "+id;
}
}
参考资料:《Java疯狂讲义》