如果您重新定义了toString方法,如何打印对象的地址

53

我是Java的新手,现在正在学习equals和==以及equals和toString的重新定义。

我想同时使用我已经重新定义的toString方法和从Object类继承的默认方法。

我试图使用super修饰符来调用那个方法,但失败了。

这只是为了教育目的。如果您看一下我的代码中的注释,就能更清楚地了解我想要的内容。

你能帮我吗?

我的代码如下:

public class EqualTest{
    public static void main(String[] args){ 
        Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
            //System.out.super.println(alice1);

        Employee alice2 = alice1;
            //System.out.super.println(alice2);

        Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);
            //System.out.super.println(alice3);

        System.out.println("alice1==alice2: " + (alice1==alice2));
        System.out.println("alice1 == alice3: " + (alice1==alice3));
        System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));
    }
}

class Employee{
...
    public String toString(){
        return getClass().getName() + "[name = " + name + 
            ", salary=" + salary + ", hireDay=" + hireDay + "]";
    }

}

6
首先,这并不被称为重新定义,而是被称为覆盖。 - Aniket Thakur
3
但默认实现并不返回对象的地址,它返回类名和该对象的 hashCode。根据 java.lang.Object 文档所述:“getClass().getName() + '@' + Integer.toHexString(hashCode())”。 - Matthias
2
不完全是这样。它可以是地址,但没有保证。因此,您从一开始就无法可靠地打印地址,无论是否覆盖了toString()。但我没有看到任何要求实际上获取地址的要求,只需使用super.toString()可用的toString()的原始结果即可,尽管您未指定的“失败”。您实际的问题仍然不清楚。 - user207421
为什么不呢?它就是地址。在给定的时间内,它是以int表示的地址。由于我们正在谈论更或多或少静态的环境,所以地址不会改变。我的意思是,在这样一个简单的程序中,垃圾收集器没有触及堆。那么,在这种情况下,为什么不是地址呢? - Kifsif
3
@Kifsif说“这就是地址”是错误的。它可能是地址(在大多数情况下,但不是所有情况),如果你依赖hashCode()来获取对象的地址,在某些情况下它可能有效,但在其他情况下可能会失败。例如,根据规格说明,某些JVM可能会将完全随机的数字存储为每个对象的hashcode值。实际上,这可能不是真实情况,因为这会导致不必要的存储开销。无论如何,在Java中,通常不需要关注对象位于哪个内存地址。Java不是C语言。 - Alderath
显示剩余2条评论
5个回答

89
严格来说,在纯Java中你不能打印对象的地址。由Object.toString()产生的字符串中看起来像对象地址的数字是对象的“标识哈希码”,它可能与对象的当前地址有关,也可能无关:
  • 规范不说明如何计算标识哈希码。它被故意留空。

  • 由于这个数字是哈希码,所以它不会改变。因此,即使它(通常)与对象地址有关,那将是在第一次访问哈希码时对象的地址。这可能与它的当前地址不同,如果GC自从第一次观察到对象的标识哈希码以来移动了对象,那它将不同。

  • 在64位JVM上(使用足够大的堆大小/不使用压缩oops),地址将不适合作为一个返回为int的标识哈希码。

无论如何,获取这个数字的方法是调用System.identityHashCode(obj)


如果你真的想要一个对象的当前地址,你可以使用JNI和本地方法(以及一些抽象破坏),或者使用Unsafe类中的方法(参见如何在Java中获取对象的内存位置?)。但是要注意,这两种方法都不可移植。此外,它们给出的对象地址容易在GC移动对象时“破裂”,这对于许多(可能是大多数)潜在用例来说都是有问题的。


对于怀疑者,这是Java 10 javadocs在“哈希码!=地址”点上所说的:

"(hashCode可能会或不会在某个时间点上实现为对象内存地址的某些函数。)"

特别强调,自从Java 7以来,使用最新JVM的默认算法并不从内存地址派生hashCode。

您可以通过将-XX:+PrintFlagsFinal包含在命令行选项中来确认这一点,并查看OpenJDK源代码以了解其含义。(该代码在某些版本中的“vm/runtime/synchronizer.cpp”文件中,但YMMV。)


22
如果您想要实现类似于默认的toString()行为,您可以使用System.identityHashCode()方法。默认的toString()将会是这样的:
public String toString(Object o) {
    return o.getClass().getName() + "@" + 
           Integer.toHexString(System.identityHashCode(o));
}

1
你可以调用super()方法来执行对应的父类方法。
class Employee{
...
    public String toString(){
         String s = super.toString();
        return getClass().getName() + "[name = " + name + 
            ", salary=" + salary + ", hireDay=" + hireDay + "]" + s;
    }

Object类中的toString()方法如下:
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

如果这个类扩展了一个覆盖了 toString() 方法的父类,那么这种方法将无法正常工作。而且如果你在继承层次结构中更高层次地调用了 toString() 方法,也同样会出现问题。还有更好的解决方法。 - Stephen C

0
你可以在Employee类中创建另一个方法来使用超级toString方法。
例如:
public class Employee {
    public String toString() {
        return "MyToStringMethod";
    }

    public String superToString() {
        return super.toString();
    }

    public static void main(String[] args) {
        Employee b = new Employee();
        System.out.println(b);
        System.out.println(b.superToString());
    }
}

或者将两者结合在一个方法中:

public class Employee {
    public String toString() {
        return super.toString() + " MyToStringMethod";
    }

    public static void main(String[] args) {
        Employee b = new Employee();
        System.out.println(b);
    }
}

0

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