Java中使用反射实现toString()方法?

61
我前几天在Java中为一个类编写了toString()方法,通过手动将类的每个元素写成字符串。然后我意识到,使用反射可能可以创建一个通用的toString()方法,可以适用于所有类。也就是说,它将找出字段名称和值,并将它们发送到一个字符串中。
获取字段名称相当简单,以下是我的同事想出来的方法:
public static List initFieldArray(String className) throws ClassNotFoundException {

    Class c = Class.forName(className);
    Field field[] = c.getFields();
    List<String> classFields = new ArrayList(field.length);

    for (int i = 0; i < field.length; i++) {
        String cf = field[i].toString();
        classFields.add(cf.substring(cf.lastIndexOf(".") + 1));
    }

    return classFields;
}

使用工厂,我可以通过仅在第一次调用toString()时存储字段来减少性能开销。但是查找这些值可能会更加昂贵。

由于反射的性能原因,这可能更多是理论上的想法而不是实际应用。但我对反射的概念很感兴趣,以及如何使用它来改进日常编程。


从jdk api中,“结果应该是简明但信息丰富的表示,易于人阅读。” - 自动生成的值是一个不错的近似值,但有时你必须咬紧牙关,自己编写代码。 - wds
请参阅自动生成toString方法 - Vadzim
可能是 java toString for any object 的重复问题。 - msangel
@msangel,这个问题比那个问题早两年。此外,这个问题不同之处在于他并没有特别询问反射的问题。 - James McMahon
更正一下,我没有理解他关于反射的观点。我认为那实际上是这个问题的重复。 - James McMahon
7个回答

108

Apache commons-lang 的 ReflectionToStringBuilder 可以为您完成这项工作。

import org.apache.commons.lang3.builder.ReflectionToStringBuilder

// your code goes here

public String toString() {
   return ReflectionToStringBuilder.toString(this);
}

3
是的,我在发布这个问题后就看到了。我仍然对细节感兴趣。我将深入挖掘他们的代码以及他们如何实现它。 - James McMahon
9
使用此功能时,请注意反射比显式字段访问慢一个数量级。在某些情况下,特别是针对仍在开发和可能发生更改的对象,使用反射是一个好主意。否则,最好使用显式的ToStringBuilder。 - kdgregory

11

如果您对JSON没有问题的话,另一个选择是谷歌的GSON库。

public String toString() {
    return new GsonBuilder().setPrettyPrinting().create().toJson(this);
}

它会为您执行反射操作,生成一个漂亮、易于阅读的JSON文件。当然易于阅读是相对的,非技术人员可能会觉得JSON很复杂。

如果您不想每次都新建GSONBuilder,也可以将其作为成员变量。

如果您有无法打印的数据(例如流)或者您不想打印的数据,只需在要打印的属性上添加@Expose标记,然后使用以下代码即可。

 new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation()
.create()
.toJson(this);

请注意,对于不受 json 支持的值(例如无穷大),该方法并不总是有效。 - Jens Baitinger

6

在使用反射时,我之前并不知道Apache库:

请注意,如果您这样做,您可能需要处理子对象并确保它们正确地打印 - 特别是,数组将不会显示任何有用的信息。

@Override
public String toString()
{
    StringBuilder b = new StringBuilder("[");
    for (Field f : getClass().getFields())
    {
        if (!isStaticField(f))
        {
            try
            {
                b.append(f.getName() + "=" + f.get(this) + " ");
            } catch (IllegalAccessException e)
            {
                // pass, don't print
            }
        }
    }
    b.append(']');
    return b.toString();
}


private boolean isStaticField(Field f)
{
    return Modifier.isStatic(f.getModifiers());
}

5
如果您正在使用Eclipse,您也可以查看JUtils toString generator,它可以静态地执行此操作(在源代码中生成该方法)。

这很有趣,我个人不使用Eclipse,但我相信我的一些同事会对此感兴趣。 - James McMahon

5
您可以使用已经实现的库,如Apache commons-lang中的ReflectionToStringBuilder。就像之前提到的那样。
或者使用反射API自己编写类似的代码。
下面是一个示例:
class UniversalAnalyzer {

   private ArrayList<Object> visited = new ArrayList<Object>();

   /**
    * Converts an object to a string representation that lists all fields.
    * @param obj an object
    * @return a string with the object's class name and all field names and
    * values
    */
   public 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()) {
         String r = cl.getComponentType() + "[]{";
         for (int i = 0; i < Array.getLength(obj); i++) {
            if (i > 0) r += ",";
            Object val = Array.get(obj, i);
            if (cl.getComponentType().isPrimitive()) r += val;
            else r += toString(val);
         }
         return r + "}";
      }

      String r = cl.getName();
      // inspect the fields of this class and all superclasses
      do {
         r += "[";
         Field[] fields = cl.getDeclaredFields();
         AccessibleObject.setAccessible(fields, true);
         // get the names and values of all fields
         for (Field f : fields) {
            if (!Modifier.isStatic(f.getModifiers())) {
               if (!r.endsWith("[")) r += ",";
               r += f.getName() + "=";
               try {
                  Class t = f.getType();
                  Object val = f.get(obj);
                  if (t.isPrimitive()) r += val;
                  else r += toString(val);
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }
         }
         r += "]";
         cl = cl.getSuperclass();
      } while (cl != null);

      return r;
   }    
}

1
很高兴有人提供了一个我可以调整的定制解决方案。没有一个OTB解决方案会打印字段类名或修饰符。 - TastyWheat

4

不是反射,而是我研究了使用字节码操作作为后编译步骤生成toString方法(以及equals/hashCode)。结果参差不齐。


2
看起来非常有趣,我得找时间仔细阅读一下。这似乎是一个非常好的方法。对于我的特定领域——商务应用来说,可能有点过于复杂,但如果有人能够毫无问题地运用您的方法,那将成为一个很好的JSR。 - James McMahon

2
这是Netbeans中等价于Olivier答案的方法:smart-codegen插件。它可以帮助您生成代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接