如何强制泛型类型参数作为接口?

时间:2011-05-19 16:24:22

标签: java generics interface

在java中是否有一种方法可以指定泛型类的类型参数必须是一个接口(而不仅仅是扩展它!)

我想做的是以下内容:

public class MyClass<X extends SomeInterface, Y extends SomeOtherClass & X>

意味着Y必须是SomeOtherClass的子类并实现X. 我目前得到的编译器是

  

X型不是界面;它不能被指定为有界参数

那么,我怎样才能告诉编译器X必须始终是一个接口?

修改
好吧,我想我有点过分简化了我的问题。让我们使用我的实际应用程序域来使其更清晰:

我有一个用于表示图表的API。 图表包含节点边缘对象。所有这三个类都实现了 Shape 接口。形状可以具有子形状,父形状并且属于图形。

问题是,我需要制作这个API的两个版本:一个只具有基本功能的开源和一个具有更多功能的扩展版本。但是,扩展API必须只提供返回扩展类型的方法( ExtendedDiagram ExtendedNode ExtendedEdge 和(此处出现问题) ExtendedShape )。
所以我有这样的事情:

/* BASIC CLASSES */
public interface Shape<X extends Shape<X,Y>, Y extends Diagram<X,Y>>{
    public List<X> getChildShapes();
    public X getParent();
    public Y getDiagram();
    ...
}

public class Diagram<X extends Shape<X,Y>, Y extends Diagram<X,Y>> implements Shape<X,Y>{...}
public class Edge<X extends Shape<X,Y>, Y extends Diagram<X,Y>> implements Shape<X,Y>{...}
...

/* EXTENDED CLASSES */
public interface ExtendedShape extends Shape<ExtendedShape,ExtendedDiagram> { ... }

public class ExtendedDiagram extends Diagram<ExtendedShape,ExtenedDiagram> implements ExtendedShape { ... }
public class ExtendedEdge extends Edge<ExtendedShape,ExtenedDiagram> implements ExtendedShape { ... }
...

扩展API工作正常,基本API代码提供了一些警告,但使用基本API时会出现主要问题:

public class SomeImporter<X extends Shape<X,Y>, Y extends Diagram<X,Y>, E extends Edge<X,Y>>{
    private Y diagram;

    public void addNewEdge(E newEdge){
        diagram.addChildShape(newEdge);
    ...

最后一行给了我以下警告:

  

类型Diagram中的方法addChildShape(X)不适用于参数(E)

所以现在,我只想指出E还需要实现X,一切都会好的 - 我希望;)

这一切都有意义吗?你们知道这样做的方法吗?或者是否有更好的方法来获得具有上述限制的扩展API? 感谢您坚持我,非常感谢任何帮助!

7 个答案:

答案 0 :(得分:15)

您可以使用:

class Foo<T extends Number & Comparable> {...}

具有一个类型参数的类Foo,必须使用类型为Number的类型实例化Foo,并实现Comparable。

答案 1 :(得分:3)

在泛型上下文中,<Type extends IInterface>处理扩展和实现。这是一个例子:

public class GenericsTest<S extends Runnable> {
    public static void main(String[] args) {
        GenericsTest<GT> t = new GenericsTest<GT>();
        GenericsTest<GT2> t2 = new GenericsTest<GT>();
    }
}

class GT implements Runnable{
    public void run() {

    }
}

class GT2 {

}

GenericsTest将接受GT,因为它实现了Runnable。 GT2没有,因此在尝试编译第二个GenericsTest实例时失败了。

答案 2 :(得分:1)

也许你可以稍微简化一下你的模型:太多的泛型在可读性方面很快变成了一个真正的痛苦,如果你定义一个公共API,这是一个很大的问题。通常情况下,如果你不能理解括号内应该是什么,那么你的需求太过分了 - 你不能指望用户比你自己更好地理解它......

无论如何,为了使你的代码编译,你可以尝试在Shape类型中定义这样的东西:

public <S extends Shape<?,?>> void addChildShape(S shape);

应该这样做。

HTH

答案 3 :(得分:1)

你写了以下内容:

public interface Shape<X extends Shape<X,Y>, Y extends Diagram<X,Y>>{
    public List<X> getChildShapes();
    public X getParent();
    public Y getDiagram();
    ...
}

我建议,至少要摆脱X型变量,如下所示:

public interface Shape<Y>{
    public List<Shape<Y>> getChildShapes();
    public Shape<Y> getParent();
    public Diagram<Y> getDiagram();
    ...
}

原因在于您最初编写的内容会受到类型参数的可能无限制的递归嵌套的影响。一个形状可以嵌套在一个父形状中,它可以嵌套在另一个形状中,所有这些都必须在类型签名中加以考虑...这不是一个好的可读性配方。嗯,在你的例子中并没有那么发生,你在其中声明了“Shape&lt; X&gt;”而不是“Shape&lt; Shape&lt; X&gt;&gt;”但是这就是你要进入的方向,如果你想要自己实际使用Shape ......

我可能还会建议更进一步,为了类似的原因摆脱Y变量。 Java泛型不能很好地处理这种组合。当尝试通过泛型强制执行此类建模的静态类型时,我发现当您稍后开始扩展时类型系统开始崩溃。

这是典型的动物/狗问题...动物有一个getChildren(),但狗的孩子也必须是狗... Java不能很好地应对这个因为(部分由于缺乏抽象类型)就像在Scala这样的语言中,但我并不是说你应该匆忙使用Scala来解决这个问题。)类型变量必须开始在各种不属于它们的地方声明。

答案 4 :(得分:0)

使用预处理器生成代码的“简化”版本。使用apt和annotations可能是一种很好的方法。

答案 5 :(得分:0)

我可能会在这里找到基础,但我对泛型的理解有点不同。

如果我错了,我要求别人纠正我。

IMO -

这是一个非常令人困惑的结构。你有无限引用的SubClasses,它看起来像。

你的Shape接口的使用方式与HashMap相同,但我从未见过HashMap做你想做的事情,最终你必须让X成为Shape中的一个类。否则你正在做HashMap

如果你总是希望X与接口成为“IS A”关系,那么它就不会发生。这不是泛型的用途。泛型用于将方法应用于多个对象,而接口不能是对象。接口定义客户端和类之间的契约。您可以做的就是说您将接受任何实现Runnable的Object,因为您需要使用所有或部分方法来使用Runnable接口方法。否则,如果您未指定并定义为,则您的类与客户端之间的合同可能会产生意外行为并导致错误的返回值或抛出异常。

例如:

public interface Animal {
    void eat();

    void speak();
}

public interface Dog extends Animal {
    void scratch();

    void sniff();
}

public interface Cat extends Animal {
    void sleep();

    void stretch();
}

public GoldenRetriever implements Dog {
    public GoldenRetriever() { }

    void eat() {
        System.out.println("I like my Kibbles!");
    }

    void speak() {
        System.out.println("Rufff!");
    }

    void scratch() {
        System.out.println("I hate this collar.");
    }

    void sniff() {
        System.out.println("Ummmm?");
    }
}

public Tiger implements Cat {
    public Tiger() { }

    void eat() {
        System.out.println("This meat is tasty.");
    }

    void speak() {
        System.out.println("Roar!");
    }

    void sleep() {
        System.out.println("Yawn.");
    }

    void stretch() {
        System.out.println("Mmmmmm.");
    }
}

现在,如果你上了这门课,你可以期待你总能称之为'speak()'&amp; '嗅探()'

public class Kingdom<X extends Dog> {
    public Kingdom(X dog) {
        dog.toString();
        dog.speak();
        dog.sniff();
    }
}

但是,如果你这样做,你就不能总是打电话给'speak()'&amp; '嗅探()'

public class Kingdom<X> {
    public Kingdom(X object) {
        object.toString();
        object.speak();
        object.sniff();
    }
}

结论:

泛型使您能够在各种对象上使用方法,而不是接口。你最后进入泛型必须是一种对象。

答案 6 :(得分:0)

保留字“ extends”与类型参数T一起用于指定界限。

‘…在此上下文中,extends在广义上表示“扩展”(如在类中)或“实现”(如在接口中)。  [https://docs.oracle.com/javase/tutorial/java/generics/bounded.html]

简而言之,“扩展”只能用于为某些类类型参数T(而不是任何接口类型参数T)指定界限(无论是类还是接口)。 就您而言,

公共类MyClass

编译器将X解析为一个类。对于X的第二次出现以及类型参数Y(无论如何显然都需要是一个类),它要求X是接口。由于它已经将X解析为类,因此它会在第二次出现X时发出错误信号,

类型X不是接口;

此外,如果在第一次出现时将X指定为无界参数,则编译器将其解析为类或接口,并且它将第二次出现的X视为可能的接口,因此允许编译。既然不是这样,编译器会澄清,

不能将其指定为有界参数