通过Java的反射式编程机制,我们可以通过Field、Constructor以及Method类来获取一个类的域成员、构造函数以及方法的具体信息,以此我们可以编写一个通用的toString()方法,所谓通用,即输入的参数可以接受任何类型的变量,返回描述该对象的所有域成员的字符串。
不过在编程开始之前,我们需要解决几个问题:
- 反射机制的默认行为受限于Java的访问控制,为了能够访问一个类的所有域成员,我们需要使用到AccessibleObject类的setAccessible方法来覆盖访问控制。
- 如果给定的类对象存在着继承链,我们需要把继承链上的所有类都进行处理。
- 一个域成员的数据类型可能已经自定义了toString()的规则,此时我们应该遵循它定义的规则而非强制让它遵循我们的规则,这样增强了程序的鲁棒性,避免出现意想不到的bug。
- 如果域成员为数组类型,我们因该依次遍历数据元素,根据元素类型合理选择构造toString()字符串的方法(代码中有相应的注解)。另外,遍历时涉及toString()方法的递归调用,因此可能导致无限递归,需要设置一个标志数组记录已经处理过的对象避免无限递归。
以下将给出代码,代码自带测试主函数:
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
/**
* 一个包含了通用的{@code toString()}方法类.
*
* @author szu_dugf
* @version 1.0 2019-07-09
*/
public class ObjectAnalyzer {
private ArrayList<Object> visited = new ArrayList<>();
/**
* 对于给定的任意对象,将返回一个描述该对象的字符串,该字符串含有该对象所有的域成员.
* @param obj 给定的一个对象
* @return 描述该对象的字符串,该字符串包含了对象所属的类名及成员变量的描述
*/
private String toString(Object obj) {
if (obj == null)
return "null";
if (visited.contains(obj))
return "...";
//避免陷入无限循环,确保当前处理的对象不曾加入到处理栈当中
visited.add(obj);
Class cl = obj.getClass();
//如果是字符串,则直接返回
if (cl == String.class)
return (String) obj;
//首先判断传入的对象是否为数组类型
if (cl.isArray()) {
StringBuilder r = new StringBuilder(cl.getComponentType() + "[]{");
for (int i = 0; i < Array.getLength(obj); ++i) {
if (i > 0)
r.append(",");
//获得数组当中i位置的具体对象元素
Object val = Array.get(obj, i);
//getComponentType()能确定数组的类型
//如果元素类型为基本数据类型,则直接将它添加到字符串描述当中
//否则调用该对象所属的类,由该类自定义的toString()规则来处理
if (cl.getComponentType().isPrimitive())
r.append(val);
else
r.append(toString(val));
}
return r.append("}").toString();
}
//如果不是数组类型,先获得该类的名称
StringBuilder r = new StringBuilder(cl.getName());
//考虑继承关系,持续调用循环体内容,直到继承链的类已被处理完毕
do {
r.append("[");
Field[] fields = cl.getDeclaredFields();
//反射机制默认受限于Java访问控制,通过setAccessible()方法覆盖访问控制
AccessibleObject.setAccessible(fields, true);
for (Field field : fields) {
//toString()方法不考虑该类的静态成员变量,因为该变量不属于任何一个具体的实例化对象的域成员
if (!Modifier.isStatic(field.getModifiers())) {
if (!r.toString().endsWith("["))
r.append(",");
r.append(field.getName()).append("=");
try {
Class t = field.getType();
Object val = field.get(obj);
if (t.isPrimitive())
r.append(val);
else
r.append(toString(val));
} catch (Exception e) {
e.printStackTrace();
}
}
}
r.append("]");
cl = cl.getSuperclass();
} while (cl != null);
return r.toString();
}
public static void main(String[] args) {
ArrayList<Integer> squares = new ArrayList<>();
for (int i = 1; i <= 5; ++i) {
squares.add(i * i);
}
System.out.println(new ObjectAnalyzer().toString(squares));
}
}