Java: Getting the properties of a class to constru

2019-03-12 04:14发布

问题:

Let's say I have a class like this (and also further assume that all the private variables:

public class Item {
    private String _id = null;
    private String _name = null;
    private String _description = null;

        ...
}

Now, if I want to build a toString() representation of this class, I would do something like this inside the Item class:

@Override
public String toString() {
    return (_id + " " + _name + " " + _description);
}

But what if I have say 15 private variables inside the class? Do I have to write the name of each and every variable like this?

Ideally, I would like to get over with the task by iterating through the list of private variables of this class and construct the string representation:

@Override
public String toString() {
    ArrayList<String> members = getClass().getMembers(); //Some method like this
    String string = "";
    for(...)
        string += members[i] + " ";
}

Or perhaps a toJSON method, I would still need access to the names of these variables. Any suggestions?

回答1:

You could do:

@Override
public String toString() {
  StringBuilder sb = new StringBuilder();
  sb.append(getClass().getName());
  sb.append(": ");
  for (Field f : getClass().getDeclaredFields()) {
    sb.append(f.getName());
    sb.append("=");
    sb.append(f.get(this));
    sb.append(", ");
  }
  return sb.toString();
}

Don't use string concatenation to construct an end result from 15 data members, particularly if the toString() will be called a lot. The memory fragmentation and overhead could be really high. Use StringBuilder for constructing large dynamic strings.

I usually get my IDE (IntelliJ) to simply generate toString() methods for me rather than using reflection for this.

Another interesting approach is to use the @ToString annotation from Project Lombok:

import lombok.ToString;

@ToString(excludes="id")
public class ToStringExample {
  private static final int STATIC_VAR = 10;
  private String name;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;

  @ToString(callSuper=true, includeFieldNames=true)
  public static class Square extends Shape {
    private final int width, height;

    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}

I find this much more preferable to, say, Jakarta Commons toString builders because this approach is far more configurable and it's also built at compile-time not run-time.



回答2:

Check this API org.apache.commons.lang.builder.ToStringBuilder, it provides multiple ways to create toString usinf reflection or without reflection. Take a look at other subclasses as well.



回答3:

There is such an api, and it is called Java Reflection

To accomplish what you are requesting, you can simply do something like:

 Class<?> cls = this.getClass();
 Field fieldlist[] = cls.getDeclaredFields();
 for (Field aFieldlist : fieldlist) {
   // build toString output with StringBuilder()
 }


回答4:

This should be exactly what you are looking for

    public String toString() {
    StringBuilder sb = new StringBuilder();
    try {
        Class c = Class.forName(this.getClass().getName());
        Method m[] = c.getDeclaredMethods();
        Object oo;

        for (int i = 0; i < m.length; i++)
            if (m[i].getName().startsWith("get")) {
                oo = m[i].invoke(this, null);
                sb.append(m[i].getName().substring(3) + ":"
                        + String.valueOf(oo) + "\n");
            }
    } catch (Throwable e) {
        System.err.println(e);
    }
    return sb.toString();
}


回答5:

Most IDEs provide a way to create a toString method in a given class.

Given an Item class with multiple fields:

class Item {
    int i;
    int j;
    int k;
    int l;
    int m;
    int n;
    int o;  
}

For example, in Eclipse, performing "Generate toString()" feature on the Item class above will create the following:

@Override
public String toString() {
    return "Item [i=" + i + ", j=" + j + ", k=" + k + ", l=" + l + ", m="
            + m + ", n=" + n + ", o=" + o + "]";
}

Using reflection would allow a programmatic way to observe one's own fields, but reflection itself is a fairly expensive (read: slow) process, so unless it is truly required, using a fixed toStringmethod written at runtime is probably going to be more desirable.



回答6:

You may run into the problem of speed. If you are using reflection, it can be really slow, in comparison to running native code. If you are going to use reflection, then you should probably have an in memory cache of the variables you are goaing to iterate over.



回答7:

Starting from the good medopal answer, you can use this code to have a string representation of all fields of an object, even if you are outside its class (obviously it's only for fields with a getter method):

/**Gives a string representation of the fields of the object
 * @param obj the object
 * @return
 */
private static String objectToString(Object obj) {
    StringBuilder sb = new StringBuilder();
    try {
        Class c = Class.forName(obj.getClass().getName());
        Method m[] = c.getDeclaredMethods();

        Object oo;
        for (int i = 0; i < m.length; i++)
            if (m[i].getName().startsWith("get")) {
                oo = m[i].invoke(obj, null);
                sb.append(m[i].getName().substring(3) + "="
                        + String.valueOf(oo) + "\n");
            }
    } catch (Throwable e) {
        System.err.println(e);
    }
    return sb.toString();
}