认识Object中的几个经常需要覆盖的方法——toString方法

学习Java少不了对Object的认知,所有类都会继承它的属性,真正的超类。这一个系列,我会对Object中的几个方法,也就是我们自定义类的时候需要重写的几个方法做一个介绍。下面是这一个系列的主要内容:

本系列内容源于对《Effective Java》中文第二版第8条到第12条的学习记录。所有内容的准确性均以原书为准。

1,引言

toString方法在平时是我们用的相对来说最多的,所以它的意义也更加重要,虽然重写它的时候没有约束,但是我们应该利用它达到我们想要的目的,下面就来看一个官方API对其的介绍和建议:

public String toString()

返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。

Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:

getClass().getName() + '@' + Integer.toHexString(hashCode())
 

返回:

该对象的字符串表示形式。

从官方的介绍我们可以总结出两点:

  • 建议所有子类重写该方法
  • 默认的格式是类名+“@”+哈希码值的无符号16进制表示

2,分析

我们下面就对上述的两条进行验证,看看对于Java平台类库中的类是不是都重写toString方法,如果没有,是不是符合第二条的组成规则。

  • 验证没有重写的类

我在API中随便找了一个类:

public final class Parameter

extends Object

implements IDLEntity

org/omg/Dynamic/Parameter.java。由 "3.2" 版的 IDL-to-Java 编译器(可移植)从 ../../../../src/share/classes/org/omg/PortableInterceptor/Interceptors.idl 生成,生成时间为 2006 年 6 月 30 日,星期五上午 12:40:09 (GMT-08:00)。

它就没有重写toString方法,测试如下:

可见并不是所有java平台类库中的所有类都重写了toSTring,但是不可否认我们经常使用的比如String,文件相关API,集合相关API等等都是重写了的

  • 验证Object中的toString方法默认输出组成

这里为了验证,我创建了两个Object对象,测试结果如下:

于是我们还会有下面两个猜测

  • 如果我们自己重写了hashCode方法还会保持上面的规律吗?
  • 如果想保持一致该肿么办?

我们使用如下的Person类测试第一个猜测:

package hfut.edu;

/**
 * Date:2018年10月1日 上午11:05:45 Author:why
 */

public class Person {

    private int hash=1;
    int age;
    String name;
    String sex;

    public Person(int age, String name, String sex) {
        super();
        this.age = age;
        this.name = name;
        this.sex = sex;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }


    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub

        if (!(obj instanceof Person))
            return false;

        Person p = (Person) obj;
        return this.age == p.age && this.name.equals(p.name) && this.sex.equals(p.sex);

    }

    
    @Override
    public int hashCode() {
        // TODO Auto-generated method stub    
        hash=hash*3+this.age;
        hash=hash*3+this.name.hashCode();
        hash=hash*3+this.sex.hashCode();
        return hash;
    }
}

测试结果如下:

到这里我们就验证了第一条是否定的。但是我觉得还是有点问题,因为我们观察Object类中的toString方法

 public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

我们想了一下,它调用的就是我们的hashCode方法转为16进制字符串显示,为什么我们转回10进制就不对了,这里有一个问题就是我们调用了两次hashCode方法,第一次调用的时候就修改了对象的hash参数的值,第二次是在此基础上进行运算的,所以出现了上述的错误,我在上一篇博客中也出现了这样的错误(现在已修改),所以,Person类的hashCode方法应该这样定义:

@Override
    public int hashCode() {
        // TODO Auto-generated method stub
        int hash=1;
        hash = hash * 3 + this.age;
        hash = hash * 3 + this.name.hashCode();
        hash = hash * 3 + this.sex.hashCode();
        return hash;
    }

同时,删除Person类中的hash成员变量即可,这个时候我们再次测试:

现在就没有任何问题了。

上面也说了,重写toString没有什么约束条约,所以可以随便定义,但是我们必须要明确其用途和意义,这里,我们就可以效仿Java平台类库了,比如File:

 /**
     * Returns the pathname string of this abstract pathname.  This is just the
     * string returned by the <code>{@link #getPath}</code> method.
     *
     * @return  The string form of this abstract pathname
     */
    public String toString() {
        return getPath();
    }

比如Calender:

 /**
     * Return a string representation of this calendar. This method
     * is intended to be used only for debugging purposes, and the
     * format of the returned string may vary between implementations.
     * The returned string may be empty but may not be <code>null</code>.
     *
     * @return  a string representation of this calendar.
     */
    @Override
    public String toString() {
        // NOTE: BuddhistCalendar.toString() interprets the string
        // produced by this method so that the Gregorian year number
        // is substituted by its B.E. year value. It relies on
        // "...,YEAR=<year>,..." or "...,YEAR=?,...".
        StringBuilder buffer = new StringBuilder(800);
        buffer.append(getClass().getName()).append('[');
        appendValue(buffer, "time", isTimeSet, time);
        buffer.append(",areFieldsSet=").append(areFieldsSet);
        buffer.append(",areAllFieldsSet=").append(areAllFieldsSet);
        buffer.append(",lenient=").append(lenient);
        buffer.append(",zone=").append(zone);
        appendValue(buffer, ",firstDayOfWeek", true, (long) firstDayOfWeek);
        appendValue(buffer, ",minimalDaysInFirstWeek", true, (long) minimalDaysInFirstWeek);
        for (int i = 0; i < FIELD_COUNT; ++i) {
            buffer.append(',');
            appendValue(buffer, FIELD_NAME[i], isSet(i), (long) fields[i]);
        }
        buffer.append(']');
        return buffer.toString();
    }

可见只要添加好toString方法备注,无论是继承还是扩展都明白你输出的内容即可。到这里,关于toString的介绍就结束了。

猜你喜欢

转载自blog.csdn.net/hfut_why/article/details/82924200