为实现相同接口的两个子类定义通用方法

11

我正在使用Java构建一个“联系人管理器”。

我有一个名为“Contact”的超类,它有两个基类;PersonalContactBusinessContact

我有一个名为Event的接口,由类BirthdayMeeting实现。(Birthday包含一个DateTime对象,而Meeting有两个对象用于开始和结束时间)。

PersonalContact 持有一组Birthdays,而 BusinessContact持有一组Meetings

现在,在超类 Contact 中,我想创建一个抽象方法 called "getEventsWithinPeriod()",它将返回给定时间段内所有生日和/或会议的TreeSet。

问题是,我不知道如何告诉抽象方法,然后再告诉基类方法返回什么。

例如,这是我在 Contact 中使用的代码;

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)不兼容"

我应该使用哪些正确的术语和返回值?为什么我的当前尝试是错误的?


2
为什么你不想继续使用 Event 接口?接口的主要思想是通过定义实现的可见方法列表来使代码清晰。只需使用 TreeSet<Event> 即可。 - Maxim Shoustin
我完全同意,@Fess - "getEvents..." 返回的是事件,这很明显,不是吗?在这种情况下使用泛型只会让人感到困惑和无助。 - Bill K
是的,你们说得对,我最终是这样做的。我没有清晰地认识到接口可以如何被用来将两个类作为相同类型统一在“事件”横幅之下。感谢你们的帮助! - CodyBugstein
4个回答

10
你需要使用 泛型类型
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;
    }
}

我不确定 <T> 是否是一个好的实践。因为 T 应该是任何东西,就像默认接口一样。也许可以使用 Contact<T extends Event> - Maxim Shoustin
但这意味着在使用Contact的每个其他类和方法中(例如包含Contacts TreeSet的ContactManager类中),我都必须将其更改为Contact <T extends Event>吗?所以我会有TreeSet <Contact <T extends Event>>吗? - CodyBugstein
@Imray 如果你有这样的特定需求,可以使用 TreeSet<Contact<? extends Event>>。这种方法可以避免使用其他方法时需要进行类型转换的问题。 - Amit Deshpande

6

您有三种解决方案。

解决方案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会很棒。我的问题是,实际上有什么缺点呢?如果我不能将事件用作生日实例,这意味着什么?我会失去什么? - CodyBugstein

0

在使用泛型时,您不希望明确指定类型。您可以限制类型,但不必明确。

将您的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;
}

这应该能给你想要的结果。


我难道不需要将Contact类更改为Contact<T extends Event>吗? - CodyBugstein

0

在覆盖任何方法时,方法签名应保持不变,您的签名应保持不变,并在PersonalContact类中返回Set。


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