-2

I want to create a toString() method that works for any class, printing all its attributes as if it worked like this:

String toString(){
    String s = "";
    for(Attribute att: this.Attributes){
        s += (att.name + ": " + att.toString() + "\n");
    }
    return s;
}

But this would work supposing all attributes have toString() method, so let's keep this supposition.

I guess this question includes concepts of reflection, and I don't know much about this concept nor how to apply this in Java.

  • 4
    Do you have some context for doing this? In general, no offense, but that would be a terrible idea, as it flies in the face of _information hiding_. – jub0bs Jul 04 '17 at 21:15
  • Yes, I do. I'm working on a project that has a package full of classes with a lot of big toString()s that print the attributes and their value, and I'd like to do some modifications and tests using AspectJ before I really edit the code. –  Jul 04 '17 at 21:17
  • 3
    Possible duplicate of [How to print values of an object in Java when you do not have the source code for the class?](https://stackoverflow.com/questions/3217603/how-to-print-values-of-an-object-in-java-when-you-do-not-have-the-source-code-fo) and [Printing all variables value from a class](https://stackoverflow.com/questions/1526826/printing-all-variables-value-from-a-class) – d.j.brown Jul 04 '17 at 21:22
  • 2
    Possible duplicate of [How to print values of an object in Java when you do not have the source code for the class?](https://stackoverflow.com/questions/3217603/how-to-print-values-of-an-object-in-java-when-you-do-not-have-the-source-code-fo) – Tom Jul 04 '17 at 21:34
  • 1
    thanks for mentioning, @Jubobs, my mistake. Fixed it. –  Jul 04 '17 at 21:37

4 Answers4

1

I think easiest way to do this - It is lombok's annotation @ToString which by default will generate toString() method with all fields, but of course you will able to exclude couple of them, or describe only required fields.

https://projectlombok.org/features/ToString

It will be look like

package test;

import lombok.AllArgsConstructor;
import lombok.ToString;
import java.util.Currency;

@AllArgsConstructor
@ToString
public class Example {
    private String name;
    private double amount;
    private Currency currency;

    public static void main(String[] args) {
        Example example = new Example("visa", 1000, Currency.getInstance("USD"));
        System.out.println(example.toString());
    }
}

and output

Example(name=visa, amount=1000.0, currency=USD)
Batiaev
  • 1,173
  • 1
  • 14
  • 30
  • I'd prefer not to include a library just to do this if there is a coded way –  Jul 04 '17 at 21:34
  • 2
    you right, but It is very good one on my opinion, because I will able to reduce boilerplate code, getters/setters/equals/hashCode popular constructors, all just with one annotation @Data – Batiaev Jul 04 '17 at 21:40
  • Libraries, good ones at least, are typically a better choice in production code than development of even the best in-house code. – Lew Bloch Jul 04 '17 at 21:58
1

You could do this by reflection or you could use libraries like snakeyaml or gson or jackson.

You could in fact serialize several simple objects, but there are object which could not be serialized to a string, and also objects with circular references could prove difficult to stringify.

You could do this with a couple of clicks in a java IDE, for example in eclipse there is a very simple wizard that does this for any object.

Last but not least it would not play well with java polymorphism, it would not be advisable to make many classes inherit from a common ancestor just to override the toString().

I personally stick with the IDE simple solution which is practical and uses neither libraries nor imposes any constraint on classes with a minimum fuss.

For example in eclipse you could achieve this on the source context menu by selecting Source > Generate toString ... :

public class Foo {
    private String name;
    private Integer age;
    private Date birthday;

    @Override
    public String toString() {
        return "Foo [name=" + name + ", age=" + age + ", birthday=" + birthday + "]";
    }
}
minus
  • 2,646
  • 15
  • 18
0

Yes, you're right that this can be achieved with reflection. It's already implemented in Apache Commons in ReflectionToStringBuilder

From documentation:

A typical invocation for this method would look like:

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

But be aware of potential performance problems - reflection always introduces some overhead.

shmosel
  • 49,289
  • 6
  • 73
  • 138
manicka
  • 204
  • 2
  • 8
-1

This is a rather complicated affair, because one of the things you need to take care of is circular references, which, if you are not careful, will cause infinite recursion resulting in a StackOverflow exception.

These are a set of methods for accomplishing what you want:

public static String reflectionToString( Object obj )
{
    StringBuilder stringBuilder = new StringBuilder();
    reflectionToString( stringBuilder, obj );
    return stringBuilder.toString();
}

public static void reflectionToString( StringBuilder stringBuilder, Object obj )
{
    reflectionToString( new ArrayList<>(), stringBuilder, obj );
}

private static void reflectionToString( ArrayList<Object> visited, 
    StringBuilder stringBuilder, Object obj )
{
    if( obj == null )
    {
        stringBuilder.append( "null" );
        return;
    }

    Class<?> objectClass = obj.getClass();

    if( objectClass == String.class )
    {
        StaticStringHelpers.appendEscapedForJava( stringBuilder, String.valueOf( obj ), '"' );
        return;
    }

    if( objectClass.isPrimitive() )
    {
        stringBuilder.append( obj );
        return;
    }

    if( Collection.class.isAssignableFrom( objectClass ) && ((Collection<?>)obj).isEmpty() )
    {
        stringBuilder.append( "{}" );
        return;
    }

    if( objectClass.isEnum() )
    {
        stringBuilder.append( obj );
        return;
    }

    if( objectClass.isArray() )
    {
        stringBuilder.append( objectClass.getComponentType().getName() ).append( "[]" );
    }
    else
    {
        stringBuilder.append( objectClass.getName() );
    }
    stringBuilder.append( "@" ).append( Integer.toHexString( System.identityHashCode( obj ) ) );

    if( visited.contains( obj ) )
        return;
    visited.add( obj );

    stringBuilder.append( "={" );
    if( objectClass.isArray() )
    {
        for( int i = 0; i < Array.getLength( obj ); i++ )
        {
            if( i > 0 )
                stringBuilder.append( "," );
            Object val = Array.get( obj, i );
            reflectionToString( visited, stringBuilder, val );
        }
    }
    else
    {
        boolean[] first = { true };
        reflectionToString( visited, stringBuilder, first, objectClass, obj );
    }
    stringBuilder.append( "}" );
}

private static void reflectionToString( ArrayList<Object> visited, 
    StringBuilder stringBuilder, boolean[] first, 
    Class<?> objectClass, Object obj )
{
    Class<?> superClass = objectClass.getSuperclass();
    if( superClass != null )
        reflectionToString( visited, stringBuilder, first, superClass, obj );
    Field[] fields = objectClass.getDeclaredFields();
    AccessibleObject.setAccessible( fields, true );
    for( Field field : fields )
    {
        if( Modifier.isStatic( field.getModifiers() ) )
            continue;
        appendNonFirst( stringBuilder, first, "," );
        stringBuilder.append( field.getName() ).append( "=" );
        try
        {
            Object fieldValue = field.get( obj );
            reflectionToString( visited, stringBuilder, fieldValue );
        }
        catch( Exception e )
        {
            assert false : e;
        }
    }
}

private static StringBuilder appendNonFirst( StringBuilder stringBuilder,
    boolean[] first, String text )
{
    if( first[0] )
        first[0] = false;
    else
        stringBuilder.append( text );
    return stringBuilder;
}

The ArrayList<Object> visited collects all visited objects so as to avoid re-visiting them. I do not remember why it is an ArrayList and not a Set.

The StringBuilder is where the string is collected. The toString() method which invokes reflectionToString() ends with a return stringBuilder.toString();.

boolean[] first is just a single-element array containing a boolean, which is a hacky way to pass a single boolean by reference. It is simply used for figuring out whether a comma should be emitted.

Class<?> objectClass is the class of the object being emitted.

Object obj is the object being emitted.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • @minus It is read in the `reflectionToString( visited, stringBuilder, fieldValue )` overload, which I had omitted because posting it opens a whole new can of worms, but I guess I have no option but to include now. Just do not ask me what the other functions are. They are left as an exercise to the reader. – Mike Nakis Jul 04 '17 at 21:41