Java反射机制应该算是Java中必学的一种东西了吧,不过我第一次学Java的时候应该是一看到这个就跳过了,因为感觉这种东西那会用不上。
直到今天我看完了这个特性,才发现如果有些情况下使用了反射机制,能减少很多没必要的机械式的代码量。
首先看一下通过反射机制能做到什么:
class类与Java反射
获取Class类型的对象
Class textFiledC=textFiled.getClass();
该对象可以取得
- 包路径
- 类名称
- 继承类
- 实现接口
- 构造方法
- 方法
- 成员变量
- 内部类
- 内部类的声明类
1.访问构造方法
返回Constructor类型的对象或者数组。
objectClass.getDeclaredConstructor(String.class,int.class)
objectClass.getDeclaredConstructor(new Class[]{String.class,int.class})
Constructor中的常见方法
- 判断是否允许带有可变数量的参数
- 获取构造方法的各个参数类型
- 获取异常类型的Class数组
- 通过构造方法利用指定参数创建一个该类的对象
- 如果构造方法的权限为private,则需要先用setAccessible方法设置为true,再创建
- 解析出构造方法所采用修饰符的整数,再利用Modifier类的isPublic等方法进行解析。
2.访问成员变量
返回Field类型的对象或者数组。
object.getDeclaredField("birthday");
Field中常见方法
- 获取名称
- 获取类型的Class对象
- 获取对象成员变量的值
- 将指定成员的值设置为value
- 获取int、float、boolean等类型的值
- 设置是否忽略权限直接访问private等私有权限的成员变量
- 获得可以解析出该成员变量所采用修饰符的整数
Field field=example.getDeclaredField("i");
field.setInt(example01,10);
3.访问方法
返回Method类型的对象或者数组。
objectClass.getDecaredMethod("print",String.class,int.class);
objectClass.getDecaredMethod("print",new Class[]{String.class,int.class});
Method中常见方法
- 获取名称
- 以Class数组获取方法各个参数的类型
- 获取返回值类型
- 以Class数组形式获得该方法可能抛出的异常类型
- 利用指定参数args执行指定对象中的该方法,返回值为Object型
- 查看构造方法是否允许可变参数
- 解析出修饰符的整数
Method print=example.getDeclaredMethod("print");
print.invoke(example01);
实例
这是我目前能想到的一个最实用的实例,对实体类的序列化和反序列化,通常情况下应该是序列化为JSON比较好,这里我举的例子比较简单(其实是没建Maven工程而且懒的导包),所以就序列化为一个字符串了。
1.编写实体类
编写一个用户实体类UserEntity,包含id,用户名、密码、性别,并实现一个自定义的toString方法来进行序列化
比如一个id为2,用户名为test,密码为123456,性别为1的用户就会序列化为"2|test|123456|1"。
import java.lang.reflect.Field;
/**
* Create by zekdot on 19-2-24.
*/
public class UserEntity {
int id;
String username;
String password;
int gender;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String toString(){
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append(id).append("|");
stringBuffer.append(username).append("|");
stringBuffer.append(password).append("|");
stringBuffer.append(gender);
return stringBuffer.toString();
}
}
2.工具类的编写
序列化的时候,可以从实体类中改写toString方法来实现,但是反序列化的时候,就要通过一个一个的setXXX方法来实现,如果成员变量比较多的话这种方式就会显得非常繁琐,但是如果利用反射机制可以比较容易的实现这种方法并且可以多次复用。
下列的代码就可以通过实体类的一个序列化字符串来重建实体类对象:
import java.lang.reflect.Field;
/**
* Create by zekdot on 19-2-24.
*/
public class EntityUtil {
/**
* 根据序列化字符串获取实体
* @param cla 实体的Class
* @param str 实体字符串
* @return 实体
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static Object getEntity(Class cla,String str) throws IllegalAccessException, InstantiationException {
Object object=cla.newInstance(); //实体类不需要构造参数
Field fields[]=cla.getDeclaredFields(); //获取所有成员变量
/*以下代码仅用于测试,通常情况应该是一个JSON序列*/
String strs[]=str.split("\\|");
/*测试结束*/
for(int i=0;i<strs.length;i++){
Field field=fields[i];
if(field.getType().equals(int.class)){ //如果是int
field.setInt(object,Integer.valueOf(strs[i]));
}else if(field.getType().equals(String.class)){ //如果是String
field.set(object,strs[i]);
}
// else{ //如果是自定义类
// field.set(object,strs[i]); //只要符合格式就能递归构建
// }
}
return object;
}
public static void main(String args[]){
try {
UserEntity userEntity= (UserEntity) getEntity(UserEntity.class,"2|test|123456|1");
System.out.println(userEntity.getPassword());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
此段代码运行结果的输出是"123456",说明实体类重建成功。
3.增加序列化方法
按照刚才的套路,其实可以通过对工具类的修改,来增加一个通过实体类对象获得其对应的字符串的方法,然后实体类调用工具类的序列化方法,这样就可以避免写很多setXXX方法,并且可以在多个实体类中复用。
首先利用反射机制在实体类中增加一个获取字符串的方法,这里打印了一些无关信息,真正的使用中可以注释掉这些语句:
/**
* 根据实体获取序列化的字符串
* @param cla 实体Class
* @param entity 实体
* @return 序列化后的字符串
* @throws IllegalAccessException
*/
public static String getString(Class cla,Object entity) throws IllegalAccessException {
Field fields[]=cla.getDeclaredFields(); //获取Field列表
StringBuffer stringBuffer=new StringBuffer();
for(int i=0;i<fields.length;i++){
Field field=fields[i];
System.out.println("追加"+field.getType()+"类型的"+field.getName());
if(field.getType().equals(int.class)){
stringBuffer.append(field.getInt(entity));
}else if(field.getType().equals(String.class)){
stringBuffer.append(field.get(entity));
}
if(i!=fields.length-1){
stringBuffer.append("|");
}
}
return stringBuffer.toString();
}
编写完之后,就可以在实体类中修改toString方法,调用工具类的序列化方法:
public String toString(){
try {
return EntityUtil.getString(UserEntity.class,this);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
最后在EntityUtil中的main方法来测试一下该方法。
public static void main(String args[]){
try {
UserEntity userEntity= (UserEntity) getEntity(UserEntity.class,"2|test|123456|1");
System.out.println(userEntity.toString());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
运行后的打印结果为:
"
追加int类型的id
追加class java.lang.String类型的username
追加class java.lang.String类型的password
追加int类型的gender
2|test|123456|1
"
说明序列化的功能也完成了。
这样做还有一个好处,就是在增加实体类的成员及其set/get方法之后,不需要修改序列化方法,因为使用的是反射机制,是直接对类所具有的信息进行读取的。
比如给实体类中增加一个成员变量address:
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
String address;
然后再修改一下工具类的main方法:
public static void main(String args[]){
try {
UserEntity userEntity= (UserEntity) getEntity(UserEntity.class,"2|test|123456|1|测试");
System.out.println(userEntity.toString());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
运行的打印结果为:
"
追加int类型的id
追加class java.lang.String类型的username
追加class java.lang.String类型的password
追加int类型的gender
追加class java.lang.String类型的address
2|test|123456|1|测试
"
可以证明增加成员变量后原有功能并没有收到影响。