反射简介
Java的反射( reflection )机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。(摘抄自百度百科)
咱们先弄个实体类用作测试
public class DemoModel {
private Integer age;
private String name;
private Date time;
private String[] friends;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name + " Chen";
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String[] getFriends() {
return friends;
}
public void setFriends(String[] friends) {
this.friends = friends;
}
}
暴力法设值(Field)
public static void main(String[] args) throws Exception{
DemoModel object = new DemoModel();
// 获取私有变量
try {
field = object.getClass().getDeclaredField("name");
} catch (NoSuchFieldException ex) {
// 子类不存在该变量那么尝试去父类获取变量
field=object.getClass().getSuperclass().getDeclaredField("name");
}
// 强行授权访问私有成员变量
f.setAccessible(true);
f.set(object, "Jack");
System.out.println(object.getName());// 输出 Jack
}
Field简介
- Field 是一个类,位于 java.lang.reflect 包下。在Java反射中Field类描述的是类属性信息。
- 主要用于获取成员变量的类型、对成员变量赋值
获取Field的4种方法
- Class.getFields() 获取类中所有public修饰的属性,含父类属性
- Class.getDeclaredFields() 获取类中所有的属性(public、protected、default、private),不含父类属性
- Class.getField(String name) 获取类特定的public修饰的属性,name参数指定了属性的名称
- Class.getDeclaredField(String name) 获取类特定的属性,name参数指定了属性的名称,不可获得父类属性
Field的常用方法
- getName() 获得该成员变量的名称
- getType() 获得表示该成员变量类型的Class对象
- get(Object obj) 得指定对象obj中成员变量的值,返回值为Object型
- set(Object obj, Object value) 将指定对象obj中成员变量的值设置为value
- setAccessible(boolean flag) 继承自AccessibleObject类,设值是否允许通过反射访问该字段。所以它的作用就是让我们在用反射时访问私有变量。
但是这种暴力访问会破坏对象的封装性,例如上面案例中 setName 方法会将 name加上后缀 " Chen”,但是因为我们绕开了set方法,所以输出的name = “Jack”。
通过属性描述器设值(PropertyDescriptor)
public static void main(String[] args) throws Exception{
DemoModel object = new DemoModel();
// 获得属性描述器 如属性在父类:object.getClass().getSuperclass()
PropertyDescriptor propertyDescriptor = new PropertyDescriptor("name", object.getClass());
// 获得set方法
Method setMethod = propertyDescriptor.getWriteMethod();
// 调用指定对象set方法
setMethod.invoke(object, "Jack");
System.out.println(object.getName());// 输出Jack Chen
}
进阶版补充
但是有些时候我们甚至不知道需要赋值的属性类型,那怎么办呢?
看如下代码:
public static void main(String[] args) throws Exception{
Map<String,Object> map = new HashMap<>();
map.put("time","2020-01-01 00:00:00");
map.put("age",18);
map.put("name","Jack");
map.put("friends","Jay Chou,Stephen Chow,Andy Lau,Chow Yun Fat");
DemoModel object = new DemoModel();
for(Map.Entry<String, Object> entry : map.entrySet()){
String mapKey = entry.getKey();
Object mapValue = entry.getValue();
// 这里使用暴力访问方式也行
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(mapKey, object.getClass());
Method setMethod = propertyDescriptor.getWriteMethod();
// 已知咱们都是调用set方法 且都只有一个参数 所以获取set方法的第一个参数的参数类型
setMethod.invoke(object, getValue(mapValue,setMethod.getParameterTypes()[0]));
}
System.out.println(JSON.toJSONString(object)); // 输出 {"age":18,"friends":["Jay Chou","Stephen Chow","Andy Lau","Chow Yun Fat"],"name":"Jack Chen","time":1577808000000}
}
private static Object getValue(Object value,Class type){
if (value != null){
if (type.isAssignableFrom(String[].class))
return toStringArray(value);
if (type.isAssignableFrom(Integer[].class))
return toIntegerArray(value);
else if (type.isAssignableFrom(Integer.class) || type.isAssignableFrom(int.class) )
return toInteger(value);
if (type.isAssignableFrom(Double.class) || type.isAssignableFrom(double.class))
return toDouble(value);
else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class))
return toBoolean(value);
else if (type.isAssignableFrom(String.class))
return toString(value);
else if (type.isAssignableFrom(Date.class))
return toDate(value);
}
return null;
}
private static String[] toStringArray(Object value){
return value.toString().split(",");
}
private static Integer[] toIntegerArray(Object value){
String[] stringArray = toStringArray(value);
Integer[] intArray = new Integer[stringArray.length];
for (int i = 0; i < stringArray.length; i++){
intArray[i] = Integer.parseInt(stringArray[i]);
}
return intArray;
}
private static Integer toInteger(Object value){
return Integer.parseInt(value.toString());
}
private static Double toDouble(Object value){
return Double.parseDouble(value.toString());
}
private static String toString(Object value){
return value.toString();
}
private static Date toDate(Object value){
return DateUtils.parseDate(value.toString(),DateUtils.DATE_FORMAT);
}
private static Boolean toBoolean(Object value){
return Boolean.parseBoolean(value.toString());
}