通过反射查看类信息
Java程序中的许多对象在运行时都会出现两种类型:编译时类型和运行时类型,例如:Person p = new Student(); 该变量的编译时类型为Person,运行时类型为Student;除此之外,还有更加极端的情形,程序在运行时接受到一个外部传来的一个对象,该对象在编译时类型时Object,但是程序又需要调用该对象运行时的类型的方法。
为了解决这一问题,程序需要在运行时发现对象和类的真实信息。解决该问题有以下两种方法:
1.第一种做法是假设在编译时和运行时都完全知道对象和类的真实信息。这种情况下,可以使用instanceof运算符进行判断,在利用强制类型转换将其转换成为运行时类型的变量即可。
2.第二种做法是根本不知道该对象和类是属于真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
获取Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。在java程序中获得这个Class对象通常有如下三种方式:
1.使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该参数的值是某个类的全限定类名(必须添加完整包名)
2.调用某个类的class属性来获取该类对应的Class对象。例如,Person.class将会返回Person类对应的Class对象
3.调用某个对象的getClass()方法。
下面程序示范了如何通过该Class对象来获取对应类的详细信息
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@Deprecated
public class ClassTest {
//为该类定义一个私有构造器
private ClassTest(){
}
// 定义一个有参数构造器
public ClassTest(String name){
System.out.println("执行有参数构造器");
}
//定义一个无参数info方法
public void info(){
System.out.println("执行一个无参数的info方法");
}
//定义一个有参数的info方法
public void info(String str){
System.out.println("执行一个有参数的info方法" + "其参数为" + str);
}
//定义一个测试用的内部类
class Inner{
}
public static void main(String[] args) throws ClassNotFoundException {
//下面代码可以获取ClassTest对应的Class
Class<ClassTest> clazz = ClassTest.class;
//获取该Class对象的所对应类的全部构造器
Constructor<?>[] ctors = clazz.getDeclaredConstructors();
System.out.println("ClassTest的全部构造器如下:");
for (Constructor c: ctors
) {
System.out.println(c);
}
//获取该Class对象所对应类的全部public构造器
Constructor<?>[] publicCtors = clazz.getConstructors();
System.out.println("ClassTest的全部public构造器如下:");
for (Constructor c:publicCtors
) {
System.out.println(c);
}
//获取该Class对象所对应类的全部public方法
Method[] mtds = clazz.getMethods();
System.out.println("ClassTest的全部public方法如下:");
for (Method md:mtds
) {
System.out.println(md);
}
Annotation[] anns = clazz.getAnnotations();
System.out.println("ClassTest的全部Annotation如下:");
for (Annotation an:anns
) {
System.out.println(an);
};
//获取该Class对象所对应类的全部内部类
Class<?>[] inners = clazz.getDeclaredClasses();
System.out.println("ClassTest的全部内部类如下:");
for (Class c:inners
) {
System.out.println(c);
}
//使用Class.forName()方法加载ClassTest的Inner内部类
Class inClazz = Class.forName("ClassTest$Inner");
//通过getDeclaringClass()访问该类所在的外部类
System.out.println("inClazz对应类的外部类为:" + inClazz.getDeclaringClass());
System.out.println("ClassTest的包为:" + clazz.getPackage());
System.out.println("ClassTest的父类为:" + clazz.getSuperclass());
}
}
其结果如下:
很显然通过ClassTest.class可以获得ClassTest的构造器、方法、注解、内部类、包、父类,等信息。
在java8中,新增了Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor Method两个子类。
Executable基类提供了两个方法
int getParameterCount(): 获取该构造器或方法的形参个数
Parameter[] getParameters() :获取该构造器或方法的所有形参
Parameter 对象代表方法或构造器的一个参数;
Parameter也提供了一些常用的方法来获取参数信息。
getModifiers(): 获取修饰该形参的修饰符
String getName() 获取形参名
Type getParameterizedType(): 获取带泛型的形参类型
Class getType(): 获取形参类型
boolean isNamePresent() 返回该类的class文件中是否包含了方法的形参名信息
boolean isVarArgs(): 该方法用于判断该参数是否为个数可变的形参
注意:javac 命令编译java源文件时,默认生成的class文件不包含方法的形参名信息。如果希望javac命令编译java源文件时保留形参信息,需要为该命令指定-parameters.
由此,测试就不测试了
Class对象可以获得该类里的方法(Method),构造器(Constructor),成员变量(Filed)。
程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,通过Field对象直接访问并修改对象的成员变量值。
创建对象
通过反射来创建对象要先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。
在很多JavaEE中都需要根据配置文件来创建对象,从配置文件中读取的只是某个类的字符串名,程序需要根据该字符串来创建对应的实例,就必须使用反射。
下面程序就是实现一个简单的对象池,该对象池会根据配置文件读取key-value对,然后创建这些对象,并将这些对象放入到一个HashMap中。
代码如下:
package com.example.iot_xiafa_message.classStudy;
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class ObjectPoolFactory {
//定义一个对象池,前面是对象名,后面是实际对象
private Map<String,Object> objectPool = new HashMap<>();
//定义一个创建对象的方法
//该方法只要传入一个字符串类名,程序就可以根据该类名生成Java对象
private Object createObject(String clazzName) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);
//使用clazz对应类的默认构造器来创建实例
return clazz.getConstructor().newInstance();
}
/**
* 该方法会根据指定文件来初始化对象池
* 它会根据配置文件来创建对象
*/
public void initPool(String fileName){
try {
FileInputStream fis = new FileInputStream(fileName);
Properties props = new Properties();
props.load(fis);
for (String name:props.stringPropertyNames()
) {
//每取出一对key-value对,就根据value创建一个对象
objectPool.put(name,createObject(props.getProperty(name)));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public Object getObject(String name){
//从ObjectPool中取出指定name对应的对象
return objectPool.get(name);
}
public static void main(String[] args) {
ObjectPoolFactory pf = new ObjectPoolFactory();
pf.initPool("E:\\idea-study\\iot_xiafa_message\\src\\main\\java\\com\\example\\iot_xiafa_message\\classStudy\\obj.txt");
String b = (String)pf.getObject("b");
b = "ss";
System.out.println(b);
}
}
如上代码:可以把txt文本中的key-value值进行创建对象。当以后碰到此类需求的时候,可以直接进行使用。这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,Spring框架就是使用这种方式大大简化了javaEE的开发。当然,Spring用的是XML配置文件。
有时候我们不想通过默认构造器来创建java对象,而想利用指定的构造器来创建java对象。
其代码如下所示:
package com.example.iot_xiafa_message.classStudy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class CreateJFrame {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> jframeClazz = Class.forName("java.lang.String");
//获取指定参数的构造器
Constructor ctor = jframeClazz.getConstructor(String.class);
//传入参数,创建对象
Object obj = ctor.newInstance("测试");
System.out.println(obj);
}
}
注意:如上面的程序所示:当已知java.lang.String时,通常是没必要使用反射来创建对象的。毕竟使用反射来创建对象其性能会低一些。实际上只有程序需要动态创建某个类时,才会考虑使用反射。
调用方法
当获得某个类对应的class对象以后,就可以通过该class对象getMethods()方法或者getMethod()方法来获得全部方法或指定方法。
每个method对象都对应一个方法,获得Method方法以后,程序就可以通过该Method来调用它对应的方法。在Method里包含一个invoke()方法。
这个invoke方法中,第一个参数代表的是谁去调用它,第二个参数代表的是,调用该方法是传入的参数
测试代码如下:
访问成员变量值
通过Class对象的getFields() 或 getField()方法可以获取该类所包括的全部成员变量或者指定成员变量。
package com.example.iot_xiafa_message.classStudy;
import java.lang.reflect.Field;
class Person{
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class FieldTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Person p = new Person();
Class<Person> personClass = Person.class;
/**
* 获取Person名为name的成员变量
* 使用getDeclaredField()方法表明可获取各种访问控制符的成员变量 private也可以获取到
*/
Field nameField = personClass.getDeclaredField("name");
//设置反射访问该成员变量时,取消访问权限检查
nameField.setAccessible(true);
//调用set()方法为p对象的name成员变量设置值
nameField.set(p,"xiaoming");
//获取Person类名为age的成员变量
Field ageField = personClass.getDeclaredField("age");
ageField.setAccessible(true);
ageField.setInt(p,30);
System.out.println(p);
}
}