14

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

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

但是,需要注意的是,for 的返回类型method1和参数method2与具体实现相匹配,而不仅仅是MyInterface. 也就是说,如果我有MyInterfaceImpl该 implements 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.

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

4

5 回答 5

13

这正是Comparable接口所面临的情况(它的compareTo方法想要使用与调用它的对象相同类型的参数)。那么它有什么作用呢?它被简单地定义为Comparable<T>。这个想法是实现类“应该”Comparable以自身作为参数来实现(允许它与自身“比较”);但这不是强制的(因为没有办法做到这一点)。

是的,正如您所指出的,这将允许任何类Comparable使用任何其他类的参数来实现:class Foo implements Comparable<Bar>whereFooBar彼此没有关系。然而,这并不是一个真正的问题。

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

(我注意到您使用<T extends MyInterface<T>>whileComparable使用简单<T>。虽然<T extends MyInterface<T>>会排除类型参数未实现的情况MyInterface,但不会排除类型参数确实实现MyInterface但与类不同的情况。那么半排除一些有什么意义情况?如果你采用Comparable's 的方式在使用它们的地方进行限制,那么无论如何它都是类型安全的,所以添加更多限制没有意义。)

于 2011-09-30T02:43:01.627 回答
2

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

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

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

骆驼穿过针眼比穿过针眼更容易NotMyInterfaceImpl。因此,尽管麻烦制造者可以编写不符合您的总体规划的课程,但他们无法将它们从工厂退回。除非NotMyInterfaceImpl延长MyInterfaceImpl;但是,从某种意义上说,它也将是 a MyInterfaceImpl,所以也许那将是犹太洁食?

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

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

如果有人给你 a Holder<Q>,那么你知道它Q一定是MyInterfacebound to its 的一个版本,这就是你所追求的。

于 2011-09-29T22:51:19.017 回答
0

你试图做的事情是不合法的,因为你试图缩小实现类型的参数,而这“没有意义”。您正在尝试使用“协变”参数,并且只允许协变返回类型(甚至是逻辑,并且仅受 Java 5 支持)。

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

MyInterface instance = new MyInterfaceImpl();

然后,在“实例”上调用具有接口支持但 MyInterfaceImpl 类不支持的另一个实现的方法:

instance.method2(new MyInterfaceImpl_2());

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

您可以做的是使用“逆变”参数来扩大参数,这将是逻辑。有关这方面的更多详细信息,请检查此分析器:

演示 Java 中的协变和逆变?

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

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

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

于 2011-09-29T22:51:48.547 回答
0

返回接口的目的是使该方法不关心返回对象的实际实现。在您的情况下,您实际上希望将类型强制为该接口的特定子实现。

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

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

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

希望这可以帮助!

于 2011-09-29T23:21:48.487 回答
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 而不是将它们抽象化。

于 2011-09-29T23:29:43.023 回答