使用StringBuilder覆盖普通POJO的toString()方法时如何避免内存浪费

5

我正在开发一个项目,所有的POJO(普通Java对象)必须重写从Object类继承而来的toString()方法。

考虑下面这个不可变类:

public final class SomeActivity {
    private final int id;
    private final String name;
    private final String description;
    private final DateTime startDate;
    private final DateTime endDate;
    private final String note;

    // Constructors and getters

    // My future implementation of toString
}

当我重写toString()方法时,我的目标是实现并输出类似于下面的输出(使用所有SomeActivity类字段的测试值):

[Id: 1, Name: Read a book, Description: Trying to discover how to build a plane, StartDate: 17/10/2013, EndDate: 15/11/2013, Note: I really need this]

因此,我有两个解决方案:

1 - 字符串连接

据我所知,String是一个不可变的类。(请参阅javadoc),因此,如果我实现一个接收这样的输出的方法,由于我的连接操作,可能会创建许多对象:

@Override
public String toString() {
    String s = "[Id: " + id + 
            ", Name: " + name + 
            ", Description: " + description + 
            ", StartDate: " + startDate + 
            ", EndDate: " + endDate + 
            ", Note: " + note + 
            "]";
}

2 - 使用StringBuilder

使用StringBuilder方法,理论上,我将少实例化一些对象而不是“连接方法”。但请注意下面代码中的new StringBuildertoString()调用:

@Override
public String toString() {
    StringBuilder builder = new StringBuilder();
        builder.append("[Id: ").append(id)
                .append(", Name: ").append(name)
                .append(", Description: ").append(description)
                .append(", StartDate: ").append(startDate)
                .append(", EndDate: ").append(endDate)
                .append(", Note: ").append(note)
                .append("]");

        return builder.toString();
}

这第二种选择,真的是最好的选择吗?还是有其他方法我应该采用?考虑那些从循环语句中调用的toString方法。
不幸的是,我对内存测试不是很熟悉,如果可能的话,我很乐意了解如何编写这方面的测试。
提前致谢。

3
请问您能否具体说明您的代码中哪里可能会出现内存泄漏问题?总体来看,您的代码看起来没有问题。请详细描述可能存在的问题。 - Denys Séguret
你们说得对,我已经编辑了标题以更准确地定义问题。 - Bruno Gasparotto
正如我在Jon的回答中所评论的那样,了解到我误解了Java如何处理代码的连接,第一种方法现在看起来并不那么危险,但是,我会采用你们建议的第二种方法。 此外,即使我写成了“内存泄漏”而不是正确的标题“低效”,很高兴知道两者都不存在于我的代码中。 非常感谢您的评论。谢谢。 - Bruno Gasparotto
2
@DevBlanked:没错,你可以说你更喜欢其中一个版本而不是另一个版本——但声称它明确地是“最佳实践”表明两者之间存在显著的具体差异。(我还要指出,如果你重新格式化代码,每行只有一个标签/字段,实际上并不会更加笨拙——事实上,没有所有的append废话,它可能更容易阅读。) - Jon Skeet
1
@DevBlanked: 不,单个 语句 和单个 是有区别的。您可以将单个语句分成几行而不会出现问题。 - Jon Skeet
显示剩余12条评论
2个回答

8
据我所知,String是一个不可变的类,(请参考javadoc),所以,如果我实现一个方法来接收这样的输出,由于我的连接操作,可能会创建很多对象:
不是的。由于你在单个大表达式中执行所有的连接操作,编译器基本上会为你创建等价于StringBuilder的代码。
但注意下面代码中的new StringBuilder和toString()调用:
嗯,是的 - 那又怎样呢?创建一个使用各种不同字段值但没有创建字符串的toString方法是不寻常的。
这里没有内存泄漏。是的,你会创建一个StringBuilder和一个String,但它们将被适当地垃圾回收。
随意使用这两种形式 - 或者使用String.format,这取决于你发现哪个最简单易懂。如果你使用第一种形式,我会将它重新格式化为每行一个描述和一个字段的值。
return "["
    + "Id: " + id 
    + ", Name: " + name 
    + ", Description: " + description
    + ", StartDate: " + startDate 
    + ", EndDate: " + endDate 
    + ", Note: " + note
    + "]";

目前看来,就可读性和添加/删除字段的便捷性而言,它更像第二种形式。


你的第一句话让我感到惊讶,我一直以为每个表达式元素都会创建一个新的字符串。 除此之外,很高兴知道没有内存泄漏和内存使用效率低下的问题,我最近在读《Effective Java》(Joshua Bloch)时也有些担心这个问题。 感谢你的回答,Jon。 - Bruno Gasparotto
1
如果你使用单独的行并继续进行 string=string+"newInfo"; string=string+"newInfo2";,等等操作,那么新的字符串将被创建。 @BrunoGasparotto - Richard Tingle
2
@RichardTingle:需要注意的是,这里并不是“分开的行”,而是“分开的语句”很重要。你可以将单个语句分布在多行上。 - Jon Skeet

0

apache commons中的ToStringBuilder提供了一种非常简洁的生成toString实现的方式。

   public class Person {
    String name;
    int age;
    boolean smoker;
   } 


   public String toString() {
     return new ToStringBuilder(this).
       append("name", name).
       append("age", age).
       append("smoker", smoker).
       toString();
   }
 }

这将生成以下格式的 toString:

Person@7f54[name=Stephen,age=29,smoker=false]


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