Подтвердить что ты не робот

Java Generics обеспечивает совместимость подстановочных знаков

У меня есть эти классы.

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

У меня есть класс, который использует 2 генерических типа

public class Connection<W, S> {}

где W - тип провода, а S - тип Socket.

Я пытаюсь обеспечить проверку времени компиляции, чтобы убедиться, что сокет и провод имеют один и тот же цвет.

Я попытался сделать это:

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 здесь)

Как я могу обеспечить проверку времени компиляции, чтобы убедиться, что сокет и провод имеют один и тот же цвет?

4b9b3361

Ответ 1

Кажется, что лучше параметризовать Socket и Wire с цветом:

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

Ответ 2

Как незначительная вариация ответа Тагир Валеев: вы можете избавиться от третьего общего параметра класса Connection, создав его конструктор private (или, возможно, пакет видимый), и предложите метод factory для создания экземпляров Connection, который гарантирует, что тип Color будет одинаковым для заданных типов Wire - и Socket:

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
    }
}

Ответ 3

Попытка смешать произвольно, как "Цвет", является типичным триггером для всего наследования и композиции. is-a vs has-debate. Общая мудрость для языка, такого как Java, заключается в том, что в большинстве случаев предпочтение следует отдавать композиции над наследованием, чтобы избежать таких вещей, как сложные подстановочные знаки. Другие языки могут предлагать больше на пути аспектно-ориентированного программирования

В то время как другие ответы могут помочь вам достичь правильных дженериков, я бы предложил прочитать страницу wikipedia по этому вопросу и подумать о том, действительно ли вы необходимо обеспечить соответствие цвета во время компиляции или выполнить проверку конструктора времени выполнения.