Я практикуюсь на экзамене и нашел образец проблемы, который я не понимаю.
Для следующего кода найдите результат:
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
, но я не понимаю, почему. Кто-нибудь может мне помочь?
Ответ 1
В первую очередь следует отметить, что public boolean equals(Test testje)
не переопределяет Object
equals
, так как аргумент Test
вместо Object
, поэтому сигнатуры на ' t.
Поэтому метод main
вызывает equals(Test testje)
ровно один раз - при выполнении t3.equals(t3);
- поскольку это единственный случай, в котором выполняется как статический тип экземпляра equals
, так и тип аргумента Test
.
t3.equals(t3);
- это 4-й оператор equals
(который приходит после 4 шагов статической переменной count
), поэтому 4. печатается 4.
Все остальные операторы equals
выполняют Object
equals
и поэтому ничего не печатают.
Более подробное объяснение:
t1.equals()
вызывает Object
equals
независимо от типа аргумента, поскольку тип статического (время компиляции) t1
равен Object
, а класс Test
не переопределяет этот метод, Класс Object
не имеет метода equals
с единственным аргументом Test
, поэтому equals(Test testje)
не может быть вызван независимо от динамического (тип выполнения) t1
.
t3.equals()
может выполнять либо Object
equals
, либо Test
равно, поскольку тип времени компиляции t3
равен Test
, а класс Test
имеет два метода equals
(один унаследованный из класса Object
и другого, определенного в классе Test
).
Выбранный метод зависит от типа времени компиляции аргумента:
1. Когда аргумент Object
(как в t3.equals(o1);
или t3.equals(t2);
), вызывается Object
equals
и ничего не печатается.
2. Когда аргумент Test
, как в t3.equals(t3);
, обе версии equals
соответствуют этому аргументу, но из-за правил перегрузки метода выбирается метод с наиболее конкретным аргументом - equals(Test testje)
- и изменена переменная count
.
Ответ 2
Метод equals в Test принимает экземпляр Test.
Все предыдущие попытки были сделаны с экземпляром Object, который берет унаследованный метод из класса Object:
public boolean equals(Object o){
return this == o;
}
Поскольку там нет печати, он не будет печатать никаких значений.
Ваш ++count;
будет увеличивать значение count, поэтому момент, когда вы действительно вызываете
public boolean equals(Test testje){...
который печатает это значение, значение count равно 4.
Ответ 3
t3.equals(t3)
- единственная строка, которая имеет правильные аргументы, соответствующие сигнатуре метода public boolean equals (Test testje)
, поэтому это единственная строка в программе, которая фактически вызывает этот оператор печати. Этот вопрос призван научить вас нескольким вещам.
- Все классы неявно расширяют Object
- Object.java содержит метод equals, который принимает тип Object
- несколько методов с тем же именем могут существовать, если они имеют разные аргументы - это называется перегрузкой метода
- метод метода overload, подпись которого соответствует аргументам во время выполнения, - это метод, который вызывается.
По сути, здесь есть трюк, что Test неявно расширяет Object, как и все классы java. Объект содержит метод equals, который принимает тип Object. t1 и t2 набираются так, что во время выполнения аргументы никогда не соответствуют сигнатуре метода равным, которая определена в тесте. Вместо этого он всегда вызывает метод equals в Object.java, потому что либо базовый тип Is Object, и в этом случае единственными методами, к которым у вас есть доступ, являются те, которые определены в Object.java, или производный тип - Object, в этом случае
public boolean equals(Test testje)
Невозможно ввести, потому что в этом случае во время выполнения аргумент имеет тип Object, который является суперклассом теста, а не подклассом. Таким образом, вместо этого он смотрит на метод equals в Test.java неявно типизированном суперклассе Object.java, который также содержит метод equals, который просто имеет подпись метода
public boolean equals (Object o)
которые в этом случае соответствуют нашим аргументам во время выполнения, поэтому этот метод равен методу, который выполняется.
Обратите внимание, что в случае t3.equals(t3)
и базовый тип, и производный тип t3 - это Test.
Test t3 = new Test ();
это означает, что во время выполнения вы вызываете метод equals в Test.java, и аргумент, который вы передаете, фактически имеет тип Test, поэтому соответствие сигнатур метода и код внутри Test.java выполняется. В этот момент count == 4
.
Бонусный бит знаний для вас:
@Override
Аннотации которые вы, возможно, видели в нескольких местах, явно инструктируют компилятор сбой, если он не находит метод с той же самой подписью где-то в суперклассе. Это полезно знать, если вы определенно намереваетесь переопределить метод и хотите быть абсолютно уверенным, что вы действительно переопределяете метод, и вы случайно не изменили метод в суперклассе или подклассе, но не оба, и введена ошибка времени выполнения, когда вызывается неправильная реализация метода, вызывающая нежелательное поведение.
Ответ 4
Есть две ключевые вещи, которые вы должны знать.
-
Переопределенные методы должны иметь точные подписи, так как их суперкласс. (в вашем примере это условие не соответствует.)
-
В Java для объекта мы имеем два типа: тип компиляции и тип выполнения. В следующем примере тип компиляции myobj
равен Object
, но его тип времени выполнения Car
.
public class Car{
@Override
public boolean equals(Object o){
System.out.println("something");
return false;
}
}
Object myobj = new Car();
Также следует отметить, что myobj.equals(...)
приводит к печати something
в консоли.