0

Я создал интерфейс. В нём хочу переопределить метод 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,262
  • 2
  • 11
  • 39
  • 3
    Вы не переопределили метод equals(), а определили свой – Byb Jan 09 '24 at 11:30
  • Я не могу добавить аннотацию Override – Евгений Дорджиев Jan 09 '24 at 11:33
  • 1
    Почему бы не сделать общий метод в общем абстрактном классе, от которого наследуются контролы (или что там)? – Алексей Шиманский Jan 09 '24 at 11:37
  • 2
    Я не могу добавить аннотацию Override - немудрено, потому что то, что вы написали, не является переопределением метода – Byb Jan 09 '24 at 11:41

2 Answers2

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

Nowhere Man
  • 15,995
  • 33
  • 19
  • 29
0
  1. Чтобы переопределить метод нужно повторить его сигнатуру и возвращаемое значение. Смотрим в документацию и видим, что он объявлен как:
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;
  1. Переопределение метода в интерфейсе — это что вообще? Если вы понимаете, какая у классов-наследников будет реализация equals(), то вам нужен абстрактный класс. Смотрите вопрос Отличия абстрактного класса от интерфейса (abstract class and interface) и ответы на него.
Зонтик
  • 2,262
  • 2
  • 11
  • 39
  • 1
    Переопределение метода в интерфейсе — это что вообще? .... "Отличия абстрактного класса от интерфейса (abstract class and interface)" --- эти данные немного устарели.... и Java накостылял костыли..... теперь у Java (уже не один год) можно писать методы в интерфейсах ¯\(ツ) – Алексей Шиманский Jan 09 '24 at 15:09
  • @АлексейШиманский Тут пока я эти строки печатал написали ответ, что это невозможно :) Писать методы можно, переопределять нельзя. – Зонтик Jan 09 '24 at 15:11
  • Там ответ о "невозможности" не по причине, что в интерфейсе нельзя методы писать, а по другой причине. – Алексей Шиманский Jan 09 '24 at 15:12
  • @АлексейШиманский я не говорил, что писать нельзя — да, есть в Java модификатор default. – Зонтик Jan 09 '24 at 15:13
  • ну п.2, я так понимаю, говорит об обратном – Алексей Шиманский Jan 09 '24 at 15:14
  • @АлексейШиманский "Переопределение метода в интерфейсе — это что вообще?" намекает, что переопределение невозможно. Про то, что писать нельзя сами по себе методы, нет ни слова. – Зонтик Jan 09 '24 at 15:15
  • А, ок, значит не так понял – Алексей Шиманский Jan 09 '24 at 15:16