Java反射总结

概述

通常在Java代码中使用一个类,需要在编译时知道类的位置。但是在某些时候,需要使用的类在编译时是未知的,相关信息需要在运行时确定。此时就可以通过反射获得使用只有在运行时才能确定名称的类,完成创建类的对象,读写/成员,调用函数等操作。本文通过一系列的例子讲述反射常用的方法的使用,用于测试的类如下:

package com.minghui.model;
class Engineer extends Employee implements Comparable {
private String mType;
private int mTypeCode;
String mSkill;
private Engineer(String name, String tittle) {
this(name, tittle, 0);
}
protected Engineer(String name) {
this(name, "Junior", 0);
}
public Engineer(String name, String tittle, int typeCode) {
super(name, tittle);
setType(typeCode);
setSkill(typeCode);
mTypeCode = typeCode;
}
private String setType(int typeCode) {
mType = (typeCode == 0) ? "Software Engineer" : " Test Engineer";
return mType;
}
void setSkill(int typeCode) {
mSkill = (typeCode == 0) ? "Code" : " Test";
}
public String getType() {
return mType;
}
public int compareTo(Engineer otherEngineer) {
return mTypeCode - otherEngineer.mTypeCode;
}
@Override
public String toString() {
return "Name:" + mName + " Tittle:" + mTittle + " Type: " + mType + " Skill: " + mSkill;
}
public static String getSkillByCode(int typeCode) {
return (typeCode == 0) ? "Code" : " Test";
}
}

它的父类如下:

获取类对象以及父类

可以通过Class.forName方法获得类对象,相关代码如下:

try {
Class> engineerClass = Class.forName("com.minghui.model.Engineer");
Class> superClass = engineerClass.getSuperclass();
System.out.println("engineerClass:" + engineerClass.getName() + " superClass:"
+ superClass.getName());
Class>[] interfaces = engineerClass.getInterfaces();
for (Class> cls : interfaces) {
System.out.println("engineerClass interface:" + cls.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

上述代码输出如下:

engineerClass:com.minghui.model.Engineer superClass:com.minghui.model.Employee
engineerClass interface:java.lang.Comparable

获得到类的对象后,即可通过该对象完成创建对象,读写成员等各类操作。

创建对象

创建对象首选需要获得构造函数,Class类中有下面几个常用的获得构造函数的方法:

getConstructors - 获得所有公有的函数

getDeclaredConstructors - 获得所有的构造函数

getConstructor - 获得指定签名的构造函数,其参数列表为构造函数参数的类的列表

注意getConstructors 和 getConstructor 会抛出

package com.minghui.generic;
Constructor>[] engineerConstructors = engineerClass.getConstructors();
for (Constructor> con : engineerConstructors) {
System.out.println("engineerConstructor con:" + con.toGenericString());
}
Constructor>[] engineerDeclareConstructors = engineerClass.getDeclaredConstructors();
for (Constructor> con : engineerDeclareConstructors) {
System.out.println("engineerDeclareConstructors con:" + con.toGenericString());
}
Constructor> constructor = engineerClass.getConstructor(String.class, String.class,
int.class);
System.out.println("engineerClass constructor :" + constructor.toGenericString());

上述代码结果为:

engineerConstructor con:public com.minghui.model.Engineer(java.lang.String,java.lang.String,int)
engineerDeclareConstructors con:public com.minghui.model.Engineer(java.lang.String,java.lang.String,int)
engineerDeclareConstructors con:protected com.minghui.model.Engineer(java.lang.String)
engineerDeclareConstructors con:private com.minghui.model.Engineer(java.lang.String,java.lang.String)
engineerClass constructor :public com.minghui.model.Engineer(java.lang.String,java.lang.String,int)


获得构造函数对象后,可以通过newInstance方法创建对象,在之前代码中增加如下几行:

constructor.setAccessible(true);
Object employ = constructor.newInstance("Bill", "Senior", 0);
System.out.println("employ objct is obj :" + employ);

输出如下:

employ objct is obj :Name:Bill Tittle:Senior Type: Software Engineer Skill: Code

注意在调用newInstance之前先调用了

constructor.setAccessible(true)

原因在于Engineer类的可见性为Package,测试类和其不再同一个包中因此无法访问,如果未增加这行代码则会抛出java.lang.IllegalAccessException.这段代码同时也说明反射的另外一个作用: 访问类的不可访问的成员(private,package,protected).

读/写成员

上一节的例子中我们通过反射获创建了Engineer对象。这一节介绍通过反射读写类的对象的成员变量的方法,参考如下代码:

Field nameField = superClass.getDeclaredField("mName");
nameField.setAccessible(true);
Field tittleField = superClass.getDeclaredField("mTittle");
tittleField.setAccessible(true);
Field skillField = engineerClass.getDeclaredField("mSkill");
skillField.setAccessible(true);
String name = (String) nameField.get(employ);
String tittle = (String) tittleField.get(employ);
String skill = (String) skillField.get(employ);
System.out.println("employ objct name is: " + name + " tittle is: " + tittle
+ " skill is: " + skill);
nameField.set(employ, "Tony");
tittleField.set(employ, "Derictor");
skillField.set(employ, "Wash-Cut-Blow");
System.out.println("employ after modifyed by reflect ois: " + employ);

上述代码输出为:

employ objct name is: Bill tittle is: Senior skill is: Code
employ after modifyed by reflect ois: Name:Tony Tittle:Derictor Type: Software Engineer Skill: Wash-Cut-Blow

上面的代码需要注意下面几点:

mName,mTittle定义在父类中,使用父类的class对象获取

mName,mTittle以及mSkill都不是可见性都不是public,在读写之前需要调用setAccessible(true)

调用方法

上一节介绍了通过通过反射读/写成员变量,这一节介绍通过反射调用成员方法,参考如下代码:

Method getSkillByCodeMethod = engineerClass.getMethod("getSkillByCode", int.class);
getSkillByCodeMethod.setAccessible(true);
Method setTypeMethod = engineerClass.getDeclaredMethod("setType", int.class);
setTypeMethod.setAccessible(true);
Method setSkillMethod = engineerClass.getDeclaredMethod("setSkill", int.class);
setSkillMethod.setAccessible(true);
Object skillByCode = getSkillByCodeMethod.invoke(employ, 0);
System.out.println("employ getSkillByCode skillByCode class: " + skillByCode.getClass()
+ " value: " + skillByCode);
Object setTypeRetValue = setTypeMethod.invoke(employ, 0);
System.out.println("employ setType setTypeRetValue class: " + setTypeRetValue.getClass()
+ " value: " + setTypeRetValue);
System.out.println("employ after setType employ : " + employ);
Object setSkillRetValue = setSkillMethod.invoke(employ, 0);
System.out.println("employ setSkill setSkillRetValue class : " + setSkillRetValue);
System.out.println("employ after setSkill employ : " + employ);

输出结果:

employ getSkillByCode skillByCode class: class java.lang.String value: Code
employ setType setTypeRetValue class: class java.lang.String value: Software Engineer
employ after setType employ : Name:Tony Tittle:Derictor Type: Software Engineer Skill: Wash-Cut-Blow
employ setSkill setSkillRetValue class : null
employ after setSkill employ : Name:Tony Tittle:Derictor Type: Software Engineer Skill: Code

上面的代码需要注意的几个点:

getSkillByCodeMethod 方法是public类型需要可以通过getMethod获得,另外2个需要通过getDeclaredMethod获得

由于Engineer类可见性为package,因此三个方法都需要调用setAccessible(true)后才可以调用invoke

读取修饰符

通过反射获得的Class,Field,Method对象都包含一个getModifiers的方法,这个方法返回一个整形,代表了相关对象的修饰符。可以通过Modifier.toString(int)方法将整形的修饰符变成字符串,下面是例子:

int getSkillByCodeModifier = getSkillByCodeMethod.getModifiers();
int setTypeModifier = setTypeMethod.getModifiers();
int setSkillModifier = setSkillMethod.getModifiers();
System.out.println("employ getSkillByCodeModifier is : " + getSkillByCodeModifier + " "
+ Modifier.toString(getSkillByCodeModifier));
System.out.println("employ setTypeModifier is : " + setTypeModifier
+ Modifier.toString(setTypeModifier));
System.out.println("employ setSkillModifier is : " + setSkillModifier
+ Modifier.toString(setTypeModifier));

输出结果如下:

employ getSkillByCodeModifier is : 9 public static
employ setTypeModifier is : 2 private
employ setSkillModifier is : 0
getModifiers返回的整形,不同的位代表了不同的修饰符,定义在Modifier.java中,相关代码如下:
public static final int PUBLIC = 0x00000001;
public static final int PRIVATE = 0x00000002;
public static final int PROTECTED = 0x00000004;
public static final int STATIC = 0x00000008;
public static final int FINAL = 0x00000010;
public static final int SYNCHRONIZED = 0x00000020;
public static final int VOLATILE = 0x00000040;
public static final int TRANSIENT = 0x00000080;
public static final int NATIVE = 0x00000100;
public static final int INTERFACE = 0x00000200;
public static final int ABSTRACT = 0x00000400;
public static final int STRICT = 0x00000800;

总结

Java反射的机制可以用于在运行时获得 class,field,constuctor,method 对象。反射式Java中一个重要的辅助机制实际使用的并不多。使用反射回带来性能上的问题并会破坏面向对象编程中的封装性,实际的编码过程中应慎用反射。