Java泛型:强制兼容通配符

43

我有这些课程。

class RedSocket {}
class GreenSocket {}
class RedWire {}
class GreenWire {}

我有一个使用了两个泛型类型的类

public class Connection<W, S> {}

其中W代表导线类型,S代表插座类型。

我正在尝试强制进行编译时检查,以确保插座和导线具有相同的颜色。

我尝试了以下方法:

public class Connection<W extends Wire & Color, S extends Socket & Color> {}

interface Color {}

interface Red extends Color {}
interface Green extends Color {}

interface Socket {}
interface Wire {}

class RedSocket implements Socket, Red {}
class GreenSocket implements Socket, Green {}
class RedWire implements Wire,  Red {}
class GreenWire implements Wire, Green {}

但这并不能确保Color在两个泛型类型中使用相同,并且仍然允许我这样做:

public class Connection<W extends Wire & Color, S extends Socket & Color> {
    public static void main(String[] args) {
        new Connection<RedWire, GreenSocket>();
        new Connection<GreenWire, RedSocket>();
    }
}

(这个问题已经被Radiodef在这里的讲解得非常透彻)

如何强制进行编译时检查以确保套接字和电线颜色相同?


2
可能没有人在意,但是你选择将Wire和Socket的类型设为MQ的原因是什么呢? - Jean-François Savard
1
啊!我的原始程序有不同的实体。我改变了其他所有的东西,但忘记改变这些。谢谢你指出来,我会改变它的。 :) - mindreader
3个回答

56

似乎更好的做法是使用颜色参数化 SocketWire

interface Socket<C extends Color> {}
interface Wire<C extends Color> {}

class RedSocket implements Socket<Red> {}
class GreenSocket implements Socket<Green> {}
class RedWire implements Wire<Red> {}
class GreenWire implements Wire<Green> {}

通过这种方式,您可以向Connection引入一个更通用的参数:

public class Connection<C extends Color, M extends Wire<C>, Q extends Socket<C>> {...}

像这样使用:

new Connection<Red, RedWire, RedSocket>(); // ok
new Connection<Green, GreenWire, GreenSocket>(); // ok
new Connection<Green, GreenWire, RedSocket>(); // error

36
作为Tagir Valeev的回答的微小变化:您可能可以通过将Connection类的构造函数设置为private(或者可能是包可见),并提供一个工厂方法来创建确保给定的WireSocket类型的Color类型相同的Connection实例,从而摆脱Connection类的第三个泛型参数。
class Connection<
    W extends Wire<? extends Color>, 
    S extends Socket<? extends Color>> 
{
    static <C extends Color, 
        W extends Wire<C>, 
        S extends Socket<C>> Connection<W, S> create()
    {
        return new Connection<W, S>();        
    }

    // Private constructor
    private Connection() {}
}

interface Color {}

interface Red extends Color {}
interface Green extends Color {}

interface Socket<C extends Color> {}
interface Wire<C extends Color> {}

class RedSocket implements Socket<Red> {}
class GreenSocket implements Socket<Green> {}
class RedWire implements Wire<Red> {}
class GreenWire implements Wire<Green> {}

public class CompatibleGenericsTest
{
    public static void main(String[] args)
    {
        Connection<RedWire, RedSocket> c0 = Connection.create(); // ok
        Connection<GreenWire, GreenSocket> c1 = Connection.create(); // ok
        Connection<GreenWire, RedSocket> c2 = Connection.create(); // error
    }
}

虽然定义有点复杂,但考虑到 API 的使用变得更加简洁,我会投票支持这种方法。 - David Welch
我以前做过类似的事情,我更喜欢这种方法,但要注意它使错误(与Tagir的解决方案相比)更难读取。 - CoatedMoose

3

尝试混入像“颜色”这样的任意属性是整个继承与组合,即is-a与has-a辩论的典型触发器。对于像Java这样的语言,一般的智慧是在大多数情况下应该优先选择组合而不是继承,以避免出现棘手的通配符等问题。其他语言可能在面向方面的编程方面提供更多支持。

虽然其他答案可能会帮助您实现正确的泛型,但我建议阅读有关该主题的维基百科页面,并考虑您是否真的需要在编译时强制执行颜色匹配,或者运行时构造函数检查是否能够胜任工作。


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