Java:自定义toString以适合任何类

时间:2017-07-04 21:13:37

标签: java reflection

我想创建一个适用于任何类的toString()方法,打印所有属性,就好像它的工作原理如下:

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

但是假设所有属性都有toString()方法,那么这就行了,所以让我们保留这个假设。

我想这个问题包含了反思的概念,我对这个概念知之甚少,也不知道如何在Java中应用它。

4 个答案:

答案 0 :(得分:1)

我认为这是最简单的方法 - 它是lombok的注释@ToString,它默认会生成包含所有字段的toString()方法,但当然你可以排除其中的几个,或者只描述必需的字段。

https://projectlombok.org/features/ToString

看起来像

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());
    }
}

和输出

Example(name=visa, amount=1000.0, currency=USD)

答案 1 :(得分:1)

你可以通过反射来做到这一点,或者你可以使用像snakeyaml或gson或jackson这样的库。

你实际上可以序列化几个简单的对象,但是有些对象无法序列化为字符串,而且带有循环引用的对象也很难 stringify

你可以在java IDE中点击几下来做到这一点,例如在eclipse中有一个非常简单的向导,可以为任何对象执行此操作。

最后但并非最不重要的是,它不能很好地与java多态性一起使用,不建议让许多类继承共同的祖先只是为了覆盖toString()。

我个人坚持使用IDE简单的解决方案,这种解决方案既实用又不使用库,也不会对类别施加任何约束。

例如在eclipse中,您可以通过选择Object obj

在源上下文菜单中实现此目的
Source > Generate toString ...

答案 2 :(得分:1)

是的,你是对的,这可以通过反思来实现。它已经在ReflectionToStringBuilder

中的Apache Commons中实现

来自文档:

  

此方法的典型调用如下所示:

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

但请注意潜在的性能问题 - 反思总会引入一些开销。

答案 3 :(得分:0)

这是一个相当复杂的事情,因为您需要处理的事情之一是循环引用,如果您不小心,将导致无限递归,从而导致StackOverflow异常。

这些是完成您想要的一系列方法:

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;
}

ArrayList<Object> visited收集所有访问过的对象,以避免重新访问它们。我不记得为什么它是一个ArrayList而不是Set。

StringBuilder是收集字符串的地方。调用toString()的{​​{1}}方法以reflectionToString()结尾。

return stringBuilder.toString();只是一个包含boolean[] first的单元素数组,这是一种通过引用传递单个布尔值的hacky方法。它只是用于确定是否应该发出逗号。

boolean是要发出的对象的类。

Class<?> objectClass是要发出的对象。