Java代码相关的equals方法

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
“像这样的代码有什么作用”这个问题的正确答案应该是“让那些太聪明的程序员失业”。 - fluffy
我对现代Java不是特别熟悉,你能指出@fluffy的巧妙之处吗? - Daniel
@Daniel,请查看Eran的答案 - 它具有极其不同的行为,这取决于方法参数的调用类型,这要归功于一个特意混淆的方法重载。谢谢。 - fluffy
1
我还要指出,在代码中使用荷兰变量名是非常不受欢迎的。 - Elva
1
我希望我能够“资助”自己。但是我身无分文 :) - Daniel
显示剩余5条评论
4个回答

113
首先需要注意的是,public boolean equals(Test testje)没有覆盖Object类的equals方法,因为参数是Test而不是Object,所以签名不匹配。
因此main方法只在执行t3.equals(t3);时调用equals(Test testje)一次,这是唯一一个两个实例的静态类型和参数类型都是Test类的情况。 t3.equals(t3);是第四个equals语句(在静态count变量增加了4次后),因此输出4。
所有其他的equals语句都会执行Objectequals,因此不输出任何内容。
更详细的解释: t1.equals()无论参数的类型如何,都会调用Objectequals,因为t1的静态(编译时)类型是Object,而Test类没有覆盖该方法。由于Object类没有一个带有单个Test参数的equals方法,因此无论t1的动态(运行时)类型是什么,都不能调用equals(Test testje)t3.equals()可以执行ObjectequalsTestequals,因为t3的编译时类型是Test,而Test类有两个equals方法(一个继承自Object类,另一个在Test类中定义)。被选择的方法取决于参数的编译时类型: 1. 当参数是Object(例如在t3.equals(o1);t3.equals(t2);中),调用Objectequals方法,不会打印任何内容。 2. 当参数是Test,例如t3.equals(t3);,两个版本的equals方法都匹配该参数,但由于方法重载的规则,选择具有最特定参数 - equals(Test testje)的方法,并打印count变量。

19
天啊,这就是我使用 @Override 注解的原因。 - Pierre Arlaud

11

Test类中的equals方法接受一个Test类实例作为参数。

之前所有的尝试都是用Object类的实例来调用继承自Object类的方法:

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

因为这里没有打印输出,所以不会打印任何值。

你的 ++count; 将增加 count 的值,因此当你实际调用你的

public boolean equals(Test testje){...

这个方法会打印出count的值,而count的值为4。


7

t3.equals(t3) 是唯一符合方法签名 public boolean equals(Test testje) 的正确参数的行,因此它是该程序中唯一调用该打印语句的行。这个问题旨在教你一些东西:

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

实际上,这里的诀窍在于Test类隐式地扩展Object,就像所有Java类一样。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方法与我们在运行时传入的参数相匹配,因此执行该equals方法。

请注意,在t3.equals(t3)的情况下,t3的基类型和派生类型都是Test。

Test t3 = new Test ();

这意味着在运行时,您正在调用Test.java中的equals方法,并且您传递的参数实际上是Test类型,因此方法签名匹配并且Test.java内部的代码执行。此时count == 4
附加知识点:
@Override 

你可能在一些地方看到了明确指示编译器如果在超类中找不到完全相同签名的方法就会失败的注解。这对于你确实打算覆盖一个方法并且你想确保你真的正在覆盖该方法,并且你没有在超类或子类中意外更改了方法,从而引入运行时错误并调用错误的方法实现导致不必要的行为是很有用的。


4

有两件重要的事情需要知道。

在 IT 技术中,这是非常关键的。
  • Overridden methods must have the exact signatures as their superclass have. (in your example this condition doesn't meet.)

  • In Java for an object, we have two types: compile type and runtime type. In the following example compile type of myobj is Object but its runtime type is Car.

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

    Object myobj = new Car();

    Also you should note that myobj.equals(...) results in printing something in the console.


1
@Override是不必要的,事实上直到1.5版本之前它都不存在。 - plugwash
@plugwash 是正确的。实际上,在这种情况下,如果您添加 @Override,则代码可能会停止编译,因为超类中没有具有相同方法签名的 equals 方法。@Override 注释是告诉编译器“超类中应该有一个具有此确切方法签名的东西 - 如果没有,请抱怨,因为此方法旨在实际上覆盖某些内容”。 - james_s_tayler
没错,这就是为什么要使用 @Override。这样当你打算重写某个方法但却搞砸了细节时,编译器会向你发出警告而不是默默地进行重载。 - plugwash
@plugwash 谢谢你的提示。 - frogatto

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