Как заставить параметр общего типа быть интерфейсом? - PullRequest
44 голосов
/ 19 мая 2011

Есть ли способ в java, чтобы указать, что параметр типа универсального класса должен быть интерфейсом (а не просто расширять его!)

Я хочу сделать следующее:

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

Это означает, что Y должен быть подклассом SomeOtherClass AND, чтобы реализовать X. То, что я сейчас получаю от компилятора, это

Тип X не является интерфейсом; его нельзя указывать в качестве ограниченного параметра

Итак, как мне сказать компилятору, что X всегда должен быть интерфейсом?

Edit:
Хорошо, я немного упрощаю свою проблему. Давайте используем мой фактический домен приложения, чтобы сделать его более понятным:

У меня есть API для представления диаграмм. A Диаграмма содержит Узел и Edge объекты. Все эти три класса реализуют интерфейс 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);
    ...

Эта последняя строка дает мне следующее предупреждение:

Метод addChildShape (X) в типе Diagram не применим для аргументов (E)

Так что теперь я хотел бы указать, что E также должен реализовывать X, и все будет хорошо - я надеюсь;)

Имеет ли все это смысл? Ребята, вы знаете, как это сделать? Или есть даже лучший способ получить расширенный API с указанными ограничениями?
Спасибо, что остаетесь со мной, любая помощь очень ценится!

Ответы [ 6 ]

15 голосов
/ 08 июня 2011

Вы можете использовать:

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

Класс Foo с одним параметром типа, T. Foo должен быть создан с типом, который является подтипом Number и реализует Comparable.

3 голосов
/ 20 мая 2011

В контексте обобщенного типа, <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.

1 голос
/ 01 июня 2011

Вы написали следующее:

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 » вместо «Shape >», но это направление, в котором вы идете, если вы когда-либо хотели на самом деле использовать форму самостоятельно ...

Вероятно, я бы также рекомендовал пойти еще дальше и избавиться от переменной Y по тем же причинам. Обобщения Java не очень хорошо справляются с подобной композицией. При попытке применить статические типы для этого типа моделирования с помощью обобщений я обнаружил, что система типов начинает разрушаться, когда вы начинаете расширять ее позже.

Это типично для проблемы Animal / Dog ... Animal имеет getChildren (), но дети Dog также должны быть Dogs ... Java не справляется с этим хорошо, потому что (частично из-за отсутствия абстрактных типов) как в таких языках, как Scala, но я не говорю, что вам следует спешить и использовать Scala для решения этой проблемы) переменные типа должны начинать объявляться во всех местах, где они на самом деле не принадлежат.

1 голос
/ 20 мая 2011

Может быть, вы можете немного упростить свою модель: слишком много обобщений быстро превращаются в реальную проблему с точки зрения читабельности, и это весьма проблематично, если вы определяете публичный API. Обычно, если вы больше не можете понять, что должно быть в скобках, тогда вы заходите слишком далеко для своих нужд - и вы не можете ожидать, что пользователи поймут это лучше, чем вы сами ...

В любом случае, чтобы ваш код компилировался, вы можете попытаться определить что-то вроде этого в Shape типе:

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

Это должно сделать это.

НТН

0 голосов
/ 03 июня 2011

Я мог бы уйти с базы, но мое понимание дженериков немного отличается.

Я прошу кого-то исправить меня, если я ошибаюсь.

ИМО -

Это очень запутанная структура, которая у вас есть.У вас есть подклассы Shape, на которые ссылаются бесконечно, это выглядит так.

Ваш интерфейс Shape используется так же, как HashMap, но я никогда не видел, чтобы HashMap делал то, что вы пытаетесь сделать, в конце концов вам придетсяпусть X будет классом в Shape.В противном случае вы выполняете HashMap

Если вы всегда хотите, чтобы X был «IS A» отношением к интерфейсу, этого не произойдет.Это не то, что дженерики для.Обобщения используются для применения методов к нескольким объектам, а интерфейсы не могут быть объектамиИнтерфейсы определяют контракт между клиентом и классом.Все, что вы можете сделать, это сказать, что вы примете любой объект, который реализует Runnable, потому что все или некоторые из ваших методов необходимы для использования методов интерфейса 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 ()' & 'sniff ()'

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

Однако, если вы сделали это, вы НЕ МОЖЕТЕ ВСЕГДАcall 'speak ()' & 'sniff ()'

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

ЗАКЛЮЧЕНИЕ:

Обобщения дают вам возможность использовать методы для широкого спектра объектов, а не интерфейсов.Ваша последняя запись в универсальный ДОЛЖЕН быть типом Object.

0 голосов
/ 02 июня 2011

Используйте препроцессор для генерации «сокращенной» версии вашего кода.Использование apt и аннотаций может быть хорошим способом сделать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...