Определение общего метода для двух подклассов, реализующих один и тот же интерфейс

Я создаю "Менеджер контактов" на Java.

У меня есть суперкласс, называемый Contact, который имеет два базовых класса: PersonalContact и BusinessContact.

У меня есть интерфейс Событие, который реализуется классами День рождения и Встреча. (День рождения содержит один объект DateTime, а Meeting имеет два для начала и окончания).

PersonalContact содержит TreeSet Дни рождения и BusinessContact содержит набор Заседания.

Теперь, в суперклассе Контакт, я хочу создать абстрактный метод, называемый getEventsWithinPeriod(), который вернет TreeSet всех дней рождения и/или встреч за определенный промежуток времени.

Проблема в том, что я не знаю, как сказать абстрактный метод, а затем методы базового класса, что нужно вернуть.

Например, это код, который я использовал в Контакт;

public abstract Set<Event> getEventsWithinPeriod(DateTime start, DateTime end);

И в PersonalContact;

public Set<Birthday> getEventsWithinPeriod(DateTime start, DateTime end){

      Set<Birthday> birthdaysThatAreWithin = new TreeSet<Birthday>();
      //CODE
      return birthdaysThatAreWithin;

Однако в компиляторе я получаю сообщение об ошибке Set<Birthday>:

"Тип возврата несовместим с Contact.getEventsWithinPeriod(DateTime, DateTime)"

Каковы правильные термины и результаты, которые я должен использовать? Почему моя текущая попытка неправильная?

Ответ 1

У вас есть 3 решения.

Решение 1

Во-первых, вы можете сделать ваши классы обобщенными, например:

public abstract class Contact<E extends Event> {
    // ...

    public abstract Set<E> getEventsWithinPeriod(DateTime start, DateTime end);
}

И затем в вашей конкретной реализации:

public class PersonalContact extends Contact<Birthday> {

    public Set<Birthday> getEventsWithinPeriod(DateTime start, DateTime end) { ... }
}

Это лучшее решение, но у вас есть альтернативы.

Решение 2

Вы можете изменить тип поля birthdaysThatAreWithin:

Set<Event> birthdaysThatAreWithin = new TreeSet<Event>();

а также изменить подпись метода:

public Set<Event> getEventsWithinPeriod(DateTime start, DateTime end) {

и вернуть его так. Это ограничивает вас, потому что вы больше не можете использовать события как Birthday.

Решение 3

Вы также можете изменить свою подпись метода (как в своем абстрактном, так и в конкретном классе):

public Set<? extends Event> getEventsWithinPeriod(DateTime start, DateTime end)

и ничего не измените. Это имеет ту же проблему, что и решение 2, вы не сможете использовать события как Birthday экземпляры без их литья.

Изменить: нижние стороны до 2 и 3 - это то, что они потребуют кастинга. Например:

PersonalContact contact = ... ;
Set<Event> events = personalContact.getEventsWithinPeriod(start, end);
// I know all the events are birthdays, but I still have to do this:
for (Event event : events) {
    if (event instanceof Birthday) {
        Birthday birthday = (Birthday) event;
        // Do stuff with birthday
    } // else maybe log some error or something
}

При первом решении вы получите следующее:

PersonalContact contact = ... ;
Set<Birthday> birthdays = personalContact.getEventsWithinPeriod(start, end);
for (Birthday birthday : birthdays) {
    // Do stuff with birthday
}

Код выглядит более чистым и работает лучше, потому что вам не нужно делать instanceof проверки, чтобы убедиться, что вы не получаете ClassCastException. У вас также могут быть такие вещи:

public static void processBirthdaysFor(Contact<Birthday> birthdayContact, DateTime start, DateTime end) {
    Set<Birthday> birthdays = personalContact.getEventsWithinPeriod(start, end);
    for (Birthday birthday : birthdays) {
        // Do stuff with birthday
    }
}

И если у вас есть еще одна реализация Contact с событиями Birthday, вы можете передать их этому методу processBirthdaysFor без внесения каких-либо изменений.

Однако, если вам нужны только события, и вам все равно, какие типы находятся в коде, вызывающем ваш Contact.getEventsWithinPeriod, тогда решения 2 и 3, безусловно, ваши лучшие ставки. Я бы просто использовал решение 2, если бы это было так.

Ответ 2

Вам нужно использовать generic Types

public abstract class Contact<T extends Event> {
    public abstract Set<T> getEventsWithinPeriod(Date start, Date end);
}
public class BirthDay extends Contact<BirthDay> implements Event {

    @Override
    public Set<BirthDay> getEventsWithinPeriod(Date start, Date end) {
        return null;
    }
}

Ответ 3

Подпись метода должна оставаться одинаковой при переопределении любого метода, ваша подпись должна оставаться такой же и возвращать Set в классе PersonalContact

Ответ 4

При использовании дженериков вы не хотите явно указывать тип. Вы можете связать этот тип, но не хотите быть явным.

Измените свой метод Contact на

public abstract Set<T extends Event> getEventsWithinPeriod(DateTime start, DateTime end);

и измените PersonalContact на

public Set<T extends Event> getEventsWithinPeriod(DateTime start, DateTime end){

      Set<T> birthdaysThatAreWithin = new TreeSet<Birthday>();
      //CODE
      return birthdaysThatAreWithin;
}

Это ДОЛЖНО получить то, что вы хотите.