【问题标题】:How can an interface include a method that references the concrete implementation type of the interface in its signature or return type?接口如何包含在其签名或返回类型中引用接口的具体实现类型的方法?
【发布时间】:2011-09-29 22:21:10
【问题描述】:

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

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

但是,需要注意的是method1 的返回类型和method2 的参数与具体实现匹配,而不仅仅是MyInterface。也就是说,如果我有实现MyInterfaceMyInterfaceImpl,它需要具有以下内容:

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&lt;NotMyInterfaceImpl&gt;,它也能编译得很好。*这让我觉得我需要别的东西。

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

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

【问题讨论】:

  • 我不想这样说无礼,但通常试图制作一个需要知道其底层实现的接口,这表明根本缺乏对接口的目的。您可能需要从设计中退后一步。
  • 好吧,如果您阅读了我的最后一段,我有点担心我可能违反了我应该对界面执行的操作。

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


【解决方案1】:

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

是的,正如您所指出的,这将允许任何类使用任何其他类的参数来实现 Comparableclass Foo implements Comparable&lt;Bar&gt; 其中FooBar 彼此没有关系。但是,这并不是真正的问题。

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

(我注意到您使用&lt;T extends MyInterface&lt;T&gt;&gt;Comparable 仅使用&lt;T&gt;。虽然&lt;T extends MyInterface&lt;T&gt;&gt; 将排除类型参数未实现MyInterface 的情况,但它不会排除类型参数实现的情况实现MyInterface,但是和类不同。那么半排除某些情况有什么意义呢?如果你采用Comparable的方式限制它的使用,它无论如何都是类型安全的,所以没有点添加更多限制。)

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

    如果有人给你Holder&lt;Q&gt;,那么你知道Q 必须是绑定到自身的MyInterface 的一个版本,这就是你所追求的。

    【讨论】:

      【解决方案3】:

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

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

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

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

      希望这会有所帮助!

      【讨论】:

        【解决方案4】:

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

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

        MyInterface instance = new MyInterfaceImpl();
        

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

        instance.method2(new MyInterfaceImpl_2());
        

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

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

        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 异常。

        【讨论】:

          【解决方案5】:

          这是你要找的吗?

          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-02-10
            • 2011-05-16
            • 1970-01-01
            • 2020-06-09
            • 2011-06-22
            • 2012-07-24
            • 2016-01-09
            • 2016-03-15
            • 1970-01-01
            相关资源
            最近更新 更多