接口如何包含在其签名或返回类型中引用接口的具体实现类型的方法?

时间:2011-09-29 22:21:11

标签: java generics interface return-type method-signature

假设我正在设计类似以下界面:

public interface MyInterface{
  public MyInterface method1();
  public void method2(MyInterface mi);
}

但是,需要注意的是method1的返回类型和method2的参数与具体实现相匹配,而不仅仅是MyInterface。也就是说,如果我MyInterfaceImpl实施MyInterface,则需要具备以下条件:

public class MyInterfaceImpl implements MyInterface{
  @Override
  public MyInterfaceImpl method1(){...}

  @Override
  public void method2(MyInterfaceImpl mi){...}
}

如上所述,method1不会导致任何编译错误,但没有任何保证返回类型在所有实现中都匹配。当然method2甚至不会编译,因为签名与接口不匹配。

一个候选解决方案是在泛型中使用自引用或递归边界:

public interface MyInterface<T extends MyInterface<T>>{
  public T method1();
  public void method2(T mi);
}

public class MyInterfaceImpl implements MyInterface<MyInterfaceImpl>{
  @Override
  public MyInterfaceImpl method1();

  @Override
  public void method2(MyInterfaceImpl mi);
}

这会得到我想要的一个例外:其他实现可能会传递错误的泛型类型(没有强制T来匹配具体类型)。因此,其他人可能会实施以下内容:

public class NotMyInterfaceImpl implements MyInterface<MyInterfaceImpl>{
  @Override
  public MyInterfaceImpl method1();

  @Override
  public void method2(MyInterfaceImpl mi);
} 

即使NotMyInterfaceImpl 应该实现MyInterface<NotMyInterfaceImpl>,那也会很好地编译。*这让我觉得我还需要别的东西。

*请注意,我不认为我试图违反LSP;我很好,返回类型/参数是NotMyInterfaceImpl的子类。

所以我不知道干净的方法。这让我相信我可能过于关注界面中的实现细节,但对我来说似乎并不是这样。有没有办法做我描述的那种东西,或者这是某种气味,我把东西放在一个不属于那里的界面?

5 个答案:

答案 0 :(得分:13)

这是Comparable接口所面临的确切情况(其compareTo方法想要获取与调用它的对象相同类型的参数)。那它是做什么的?它只是定义为Comparable<T>。这个想法是一个实现类“应该”实现Comparable自己作为参数(允许它“自我比较”);但这没有强制执行(因为没有办法实现)。

是的,正如您所指出的,这将允许任何类使用任何其他类的参数实现Comparableclass Foo implements Comparable<Bar>其中FooBar与之无关彼此。但是,这不是一个真正的问题。

需要Comparable个对象的所有方法和类(排序,最大值等)都具有以下泛型类型约束<T extends Comparable<? super T>>。这确保了类型T的对象与它们自身相当。这样,它完全是类型安全的。因此,强制执行不是在Comparable接口的声明中,而是在使用它的地方。

(我注意到您使用<T extends MyInterface<T>>Comparable仅使用<T>。虽然<T extends MyInterface<T>>将排除type参数未实现MyInterface的情况,它不会排除类型参数实现MyInterface但与类不同的情况。那么半数排除某些情况的重点是什么?如果采用Comparable的方式将其限制在哪里它们被使用,无论如何都是类型安全的,所以添加更多限制毫无意义。)

答案 1 :(得分:2)

我认为这不可能做到。在泛型框架中根本没有办法引用对象的实现类,据我所知,也没有办法用纯泛型构造一个笼子,它能够约束实现类以匹配一个类型参数。

我可以建议最有用的是使用自引用参数,然后总是从工厂方法中获取实现的实例,如下所示:

public <T extends MyInterface<T>> T newInstance();

骆驼穿过针眼比穿过该返回类型的NotMyInterfaceImpl实例更容易。因此,虽然麻烦制造者可以编写不符合您的总体规划的课程,但他们无法从工厂返回。除非NotMyInterfaceImpl延长MyInterfaceImpl;但是,从某种意义上说,它也可能是MyInterfaceImpl,所以也许那就是犹太人?

编辑:这个想法的一个稍微有用的版本是始终在适当限制的持有者中传递接口的实现实例,例如:

class Holder<T extends MyInterface<T>> {
    public final T value;
}

如果有人为您提供了Holder<Q>,那么您就知道Q必须是与MyInterface绑定的版本,这就是您所追求的。

答案 2 :(得分:0)

您尝试做的事情是不合法的,因为您正在尝试缩小已实现类型的参数,并且“没有意义”。您可以尝试使用“协变”参数,并且只允许使用协变返回类型(甚至是逻辑,只有supported from Java 5)。

我的意思是,如果可以使用协变参数类型,您可以执行以下操作:

MyInterface instance = new MyInterfaceImpl();

然后,使用接口支持的另一个实现调用“instance”方法,但MyInterfaceImpl类不支持这种方式:

instance.method2(new MyInterfaceImpl_2());

Java无法将MyInterfaceImpl_2转换为MyInterfaceImpl,因此它会阻止您在编译时这样做。

你可以做的是使用“逆变”参数扩大参数,这将是逻辑。有关详细信息,请查看此anser:

Demonstrate covariance and contravariance in Java?

我能想到的唯一解决方法是在运行时解决问题,我的意思是做这样的事情:

public class MyInterfaceImpl implements MyInterface{
  @Override
  public void method2(MyInterface mi){
        realMethod((MyInterfaceImpl) mi);
  }
  public void realMethod(MyInterfaceImpl) {...}
}

但是你当然可以获得ClassCast异常。

答案 3 :(得分:0)

返回接口的意义在于该方法不关心返回对象的实际实现。在您的情况下,您实际上想要来强制该类型成为该接口的特定子实现。

要应用上面描述的约束,恕我直言,设计应该是基类而不是接口。这允许您控制实现,例如顶级流,并将低级策略留给子类来实现:

class MyBaseImpl {
    public final void fixedFlow() {
        MyBaseImpl obj = method1();
        obj.method2(this);
    }
    protected abstract MyBaseImpl method1();
    ....
}

必须有其他方法让它变得有趣......;也许你有充分的理由想要这样做......

希望这有帮助!

答案 4 :(得分:0)

这是你在找什么?

public interface MyInterface {

    static abstract class MyInterfaceImpl implements MyInterface {

        @Override
        public abstract MyInterfaceImpl method1();

        @Override
        public abstract void method2(MyInterfaceImpl mi);

    }    

    MyInterfaceImpl method1();
    void method2(MyInterfaceImpl mi);

}

你甚至可以实现方法1或2,而不是将它们抽象化。