与equals方法相关的Java代码


75

我正在为考试练习,发现了一个我不理解的示例问题。

对于以下代码,找到输出是什么:

public class Test {

    private static int count = 0;

    public boolean equals(Test testje) {
        System.out.println("count = " + count);
        return false;
    }

    public static void main(String [] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        ++count; t1.equals(t2);
        ++count; t1.equals(t3);
        ++count; t3.equals(o1);
        ++count; t3.equals(t3);
        ++count; t3.equals(t2);
    }
}

这段代码的输出是count = 4,但我不明白为什么。谁能帮我?


32
正确的答案应该是“让聪明的程序员被解雇”。
蓬松的

我对现代Java不十分熟悉,您能指出@fluffy的聪明之处吗?
丹尼尔(Daniel)

@Daniel看到eran的答案-由于有目的的方法重载,它基于方法参数的调用位置类型具有截然不同的行为。
蓬松的

1
我还将注意到,在代码中使用荷兰语变量名的做法被广泛反对。
埃尔瓦,2016年

1
我希望自己可以“资助”自己。但我很伤心:)
丹尼尔(Daniel)

Answers:


112

你应该注意到的第一件事情是,public boolean equals(Test testje) 没有覆盖Objectequals,因为参数是Test不是Object,所以签名不匹配。

因此,该main方法equals(Test testje)仅在执行时调用一次,t3.equals(t3);因为这是唯一一种实例equals同时针对其实例执行的静态类型和参数类型均为Test该类的情况。

t3.equals(t3);是第四个equals语句(在静态count变量的4个增量之后出现),因此将输出4。

其他所有equals语句都执行Objectequals,因此不打印任何内容。

更详细的解释:

t1.equals()呼叫Objectequals不管参数的类型的,因为静态(编译时间)型的t1Object,和Test类不覆盖该方法。所述Object类不具有equals与单个方法Test参数,所以equals(Test testje)不能称为,无论动态(运行时类型)的t1

t3.equals()可以执行ObjectequalsTest's等于,因为在编译时间类型t3Test,并且Test类有两个equals方法(一个来自继承Object类,并在定义的其它Test类)。

被选择的方法取决于所述参数的编译时间类型:1.当参数是Object(如在t3.equals(o1);t3.equals(t2);Objectequals被称为并打印什么。2.当参数为时Test,如中的t3.equals(t3);,两个版本都equals匹配该参数,但由于方法重载的规则,equals(Test testje)选择了参数最具体的方法--并count打印了变量。


19
Geeeez,这就是为什么我用@Override批注
皮埃尔Arlaud的

11

Test中的equals方法采用Test的实例。

以前所有尝试都是使用Object实例进行的,该实例采用Object类的继承方法:

public boolean equals(Object o){
  return this == o;
}

由于其中没有打印内容,因此不会打印任何值。

++count;将增加count的值,因此当您实际呼叫

public boolean equals(Test testje){...

方法,它会打印该值,count的值为4。


7

t3.equals(t3)是唯一具有与方法签名匹配的正确参数的行,public boolean equals (Test testje)因此它是程序中实际调用该print语句的唯一行。该问题旨在教您一些知识。

  • 所有类都隐式扩展Object
  • Object.java包含一个采用Object类型的equals方法
  • 如果它们具有不同的参数,则可以存在多个具有相同名称的方法-这称为方法重载
  • 在运行时签名匹配参数的方法方法重载是被调用的方法。

本质上,这里的技巧是Test像所有java类一样隐式扩展Object。Object包含一个采用Object类型的equals方法。键入t1和t2,以便在运行时参数永远不匹配Test中定义的equals的方法签名。相反,它总是调用Object.java中的equals方法,因为基本类型是Object,在这种情况下,您只能访问Object.java中定义的方法,或者派生类型是Object,在这种情况下

public boolean equals(Test testje)

无法输入,因为在这种情况下,运行时参数为Object类型,它是Test的超类,而不是子类。因此,它改为查看Test.java的隐式类型化超类Object.java中的equals方法,该类还包含一个equals方法,该方法恰好具有方法签名为

public boolean equals (Object o)

在这种情况下,它在运行时与我们的参数匹配,因此此equals方法是执行的方法。

注意t3.equals(t3),t3的基本类型和派生类型均为Test。

Test t3 = new Test ();

这意味着在运行时,您正在Test.java中调用equals方法,而传入的参数实际上是Test类型的,因此方法签名匹配,并且Test.java中的代码得以执行。在这一点上count == 4

为您提供丰富的知识:

@Override 

您可能已经在几个地方看到的注释显式地指示编译器在超级类中某处找不到具有完全相同签名的方法时失败。这对于了解您是否确实打算覆盖某个方法,并且要绝对确保自己确实在覆盖该方法,并且您没有意外更改父类或子类中的方法,而并非两者都更改,并且引入了运行时错误非常有用调用该方法的错误实现会导致不良行为。


4

您应该知道两件事。

  • 重写的方法必须具有其超类所具有的准确签名。(在您的示例中,此条件不满足。)

  • 在Java中,对于对象,我们有两种类型:编译类型和运行时类型。在以下示例中,的编译类型为myobjObject但其运行时类型为Car

    public class Car{
          @Override
          public boolean equals(Object o){
                System.out.println("something");
                return false;
          }
    }
    

    Object myobj = new Car();

    您还应注意,这会myobj.equals(...)导致在something控制台中进行打印。


1
@Override不需要,在1.5之前根本不存在
洗脸

@plugwash是正确的。您不需要@Override。实际上,在这种情况下,如果添加@Override,则代码可能会停止编译,因为equals超类中没有方法具有相同的方法签名。@Override注解是告诉编译器“在该超类中应该有这个确切的方法签名的东西-请抱怨是否不存在,因为该方法实际上是有意故意覆盖某些东西”
james_s_tayler

是的,这就是为什么使用@Override的原因。因此,当您打算覆盖某些内容但弄乱细节时,编译器会对您大喊大叫,而不是默默地重载。
plugwash

@plugwash谢谢您的提示。
frogatto
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.