【Effective Java】Ch3_Methods:Item10_始终重写toString()

来源:互联网 发布:天池数据竞赛 编辑:程序博客网 时间:2024/05/16 01:25

        虽然java.lang.Object类提供了toString方法的一个实现,但是其返回的字符串通常不是类的用户所期望看到的。它包含类的名称,接着是一个@符号,然后是哈希码的无符号十六进制表示,例如“PhoneNumber@163b91”。toString的通用约定指出返回的字符串必须“简洁而信息丰富,并易于阅读”。尽管可能有人说“PhoneNumber@163b91”算得上是简洁和易于阅读,但是与“(707) 867-5309”比起来它可并不信息丰富。toString约定进一步指出:建议所有子类都重写这个方法。这真是一个好建议!

        虽然尊重toString约定并不像遵守equals、hashCode约定那么重要,但是提供好的toString实现可以让你的类用起来更加舒适。当对象被传递给println、printf、字符串连接符、assert或者被调试器打印时,其toString方法会被自动调用。

        如果你为PhoneNumber提供了一个好的toString的话,那么要产生有用的诊断信息就很容易:

System.out.println("Fail to connect: " + phoneNumber);
        无论是否重写了toString,程序员都使用这种方式产生诊断信息,但如果不重写toString的话,产生的信息就不会有用了。提供一个好的toString方法,不仅有益于这个类的实例,也有益于包含这些实例的引用的对象,尤其是集合对象。打印Map时有下面这两条信息:{Jenny=PhoneNumber@163b91}、{Jenny=(707) 867-5309},你更愿意看到哪一个?

        在实际应用中,toString方法应该返回对象中所有的值得关注的信息,正如上述PhoneNumber的例子那样。如果对象太大,或者对象中包含的状态信息难以用字符串表示,这样做就有些不切实际。在这种情况下,toString应该返回一个摘要信息,例如“Manhattan white pages (1487536 listings)”,或者“Thread[main, 5, main]”。理想情况下,字符串应该是自描述的(Thread的例子不满足这个要求)。

       

        在实现toString的时候必须要做一个重要的决定:是否在文档中指定返回值的格式。对于值类(value classes),比如电话号码、矩阵,推荐这么做。指定格式的好处是,返回值可作为对象的标准的、明确的、易于阅读的表示。这种表示可以被用作输入和输出,以及持久化的可阅读的数据对象中,例如XML文档。如果你指定了格式,那么最好提供一个相应的静态工厂方法,或者构造函数,以便程序员能容易地在对象及其字符串表示之间来回转换。【例】JDK中的许多值类都采用了这种方式,包括BigInteger、BigDecimal,以及大多数基本类型包装类。

    /**     * Translates the String representation of a BigInteger in the specified     * radix into a BigInteger.  The String representation consists of an     * optional minus sign followed by a sequence of one or more digits in the     * specified radix.  The character-to-digit mapping is provided by     * {@code Character.digit}.  The String may not contain any extraneous     * characters (whitespace, for example).     *     * @param val String representation of BigInteger.     * @param radix radix to be used in interpreting {@code val}.     * @throws NumberFormatException {@code val} is not a valid representation     *       of a BigInteger in the specified radix, or {@code radix} is     *       outside the range from {@link Character#MIN_RADIX} to     *       {@link Character#MAX_RADIX}, inclusive.     * @see    Character#digit     */    public BigInteger(String val, int radix) {}

    /**     * Returns the decimal String representation of this BigInteger.     * The digit-to-character mapping provided by     * {@code Character.forDigit} is used, and a minus sign is     * prepended if appropriate.  (This representation is compatible     * with the {@link #BigInteger(String) (String)} constructor, and     * allows for String concatenation with Java's + operator.)     *     * @return decimal String representation of this BigInteger.     * @see    Character#forDigit     * @see    #BigInteger(java.lang.String)     */    public String toString() {return toString(10);    }    /**     * Returns the String representation of this BigInteger in the     * given radix.  If the radix is outside the range from {@link     * Character#MIN_RADIX} to {@link Character#MAX_RADIX} inclusive,     * it will default to 10 (as is the case for     * {@code Integer.toString}).  The digit-to-character mapping     * provided by {@code Character.forDigit} is used, and a minus     * sign is prepended if appropriate.  (This representation is     * compatible with the {@link #BigInteger(String, int) (String,     * int)} constructor.)     *     * @param  radix  radix of the String representation.     * @return String representation of this BigInteger in the given radix.     * @see    Integer#toString     * @see    Character#forDigit     * @see    #BigInteger(java.lang.String, int)     */    public String toString(int radix) {}



        指定toString返回值格式的缺点是,如果你的类已被广泛使用,则一旦你指定了格式,就必须永远保持这种格式。程序员会编写代码来解析这种字符串表示,生成这种表示、将其嵌入到持久化数据中。如果你在将来的版本中修改了这个表示,那就会破坏他们的代码和数据,他们就会抱怨了。而如果不指定格式,你就保留了在后续版本中添加信息或优化格式的灵活性。


        无论你是否决定指定格式,你都必须在文档中明确地表明你的意图。如果你指定了格式,就必须严格地这样做,一下是PhoneNumber类的toString方法:

/** * Returns the string representation of this phone number. * The string consists of fourteen characters whose format * is "(XXX) YYY-ZZZZ", where XXX is the area code, YYY is * the prefix, and ZZZZ is the line number.  (Each of the * capital letters represents a single decimal digit.) * * If any of the three parts of this phone number is too small * to fill up its field, the field is padded with leading zeros. * For example, if the value of the line number is 123, the last * four characters of the string representation will be "0123". * * Note that there is a single space separating the closing * parenthesis after the area code from the first digit of the * prefix. */@Override public String toString() {  return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);}
        如果你决定不指定格式,则文档注释应该有类似如下的信息:

/*** Returns a brief description of this potion. The exact details* of the representation are unspecified and subject to change,* but the following may be regarded as typical:** "[Potion #9: type=love, smell=turpentine, look=india ink]"*/@Override public String toString() { ... }
        读了这段注释后,那些想基于返回值格式细节进行编码或持久化数据的程序员就会明白,如果格式改变了,就怨不得别人了。

        无论是否指定格式,都应该为toString返回值中包含的所有信息提供一个编程式的访问途径。例如PhoneNumber类应该包含area code、prefix、line number的访问器。如果没有提供这些方法,那么就会迫使那些需要这些信息的程序员不得不去解析字符串。除了降低程序性能、为程序员带来不必要的工作之外,这个解析过程也容易出错,会导致系统不稳定,如果你改变了格式就会导致系统崩溃。如果没有提供访问方法,即便你已经指明了字符串的格式是可以变化的,这个字符串格式也会成为事实上的API。