Как переопределить метод equals в интерфейсе?
Я создал интерфейс.
В нём хочу переопределить метод equals():
default boolean equals (OverdueControlActivityProjection other) {
System.out.println("Вызывается метод equals в интерфейсе");
return getControlActivityId().equals(other.getControlActivityId());
}
На затем,
когда я использую стрим, я ожидаю, что метод distinct должен вызывать метод equals():
Stream.of(projection1, projection2)
.distinct()
.collect(Collectors.toList);
Но он его не вызывает. Я ведь не вижу в консоли вывода.
Почему не работает?
Как я смогу убрать дубликаты интерфейсов, если дублируются по полю controlActivityId?
Ответы (2 шт):
Переопределение методов класса Object в интерфейсе как дефолтных методов вполне бессмысленно, так как такие методы всё равно будут недостижимые (unreachable).
Дело в том, что реализация метода в классе всегда имеет высший приоритет по сравнению с дефолтной реализацией в интерфейсе, даже если эта реализация выполнена в классе Object.
Поэтому и возникает ошибка компиляции Default method 'equals' overrides a member of 'java.lang.Object' при попытке переопределить метод с сигнатурой public boolean equals(Object o):
interface Foo {
int foo();
@Override
default boolean equals(Object o) { // ошибка компиляции
return false;
}
}
А определение метода boolean equals(Foo f) в интерфейсе не является переопределением соответствующего метода, о чём указано в комментариях (аннотация @Override не может применяться).
Выход заключается только в использовании абстрактного класса, но для корректной фильтрации при помощи distinct потребуется также переопределить метод hashCode:
interface GoodFoo {
int foo();
}
abstract class FooImpl implements GoodFoo {
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof GoodFoo && this.foo() == ((GoodFoo) o).foo();
}
@Override
public int hashCode() {
return foo();
}
}
Тест:
List<GoodFoo> foos = Arrays.asList(
new FooImpl() {
@Override public int foo() { return 1; }
},
new FooImpl() {
@Override public int foo() { return 2; }
},
new FooImpl() {
@Override public int foo() { return 1; }
}
);
foos.stream()
.distinct()
.mapToInt(GoodFoo::foo)
.forEach(System.out::println);
Вывод:
1
2
Связанный вопрос на основном SO: Java8: Why is it forbidden to define a default method for a method from java.lang.Object
- Чтобы переопределить метод нужно повторить его сигнатуру и возвращаемое значение. Смотрим в документацию и видим, что он объявлен как:
public boolean equals(Object obj)
То есть он возвращает boolean, а принимает на вход Object. У вас же в параметрах тип OverdueControlActivityProjection. Вы указываете свой конкретный тип. А это объявление другого метода, достаточно вспомнить про перегрузку методов (англ. methods overloading).
Для переопределения прописываем метод так, как он написан в Object. Добавляем аннотацию @Override:
@Override
public boolean equals(Object obj) {
//Тут будем писать реализацию метода
}
Внутри метода уже можно из типа Object получить ваш тип OverdueControlActivityProjection. Проводим нисходящее преобразование (англ. downcasting):
OverdueControlActivityProjection otherObject = (OverdueControlActivityProjection) obj;
И дальше пишем реализацию. Хотя надо иметь в виду случай, когда в параметрах придёт null:
if(obj == null) return false;
- Переопределение метода в интерфейсе — это что вообще? Если вы понимаете, какая у классов-наследников будет реализация
equals(), то вам нужен абстрактный класс. Смотрите вопрос Отличия абстрактного класса от интерфейса (abstract class and interface) и ответы на него.