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

Как заставить параметр типового типа быть интерфейсом?

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

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

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

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

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

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

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

У меня есть API для представления диаграмм. A Диаграмма содержит объекты Node и Edge. Все эти три класса реализуют интерфейс Shape. Формы могут иметь дочерние фигуры, родительскую форму и принадлежать диаграмме.

Дело в том, что мне нужно сделать две версии этого API: один open-source с базовыми функциями и расширенный с большим количеством функций. Однако расширенный 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) в диаграмме типа не применим для аргументов (E)

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

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

Ответ 1

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

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

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

Ответ 2

В контексте generics <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.

Ответ 3

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

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

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

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

НТН

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

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

IMO -

Это очень запутанная структура, которая у вас есть. У вас есть SubClasses of 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();
    }
}

Однако, если вы сделали это, вы НЕ МОЖЕТЕ ВСЕГДА называть "говорить()" и "sniff()"

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

ВЫВОД:

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

Ответ 7

Зарезервированное слово "extends", так как наряду с параметром типа T используется для определения границы.

‘… В этом контексте extends используется в общем смысле для обозначения" расширяет "(как в классах) или" реализует "(как в интерфейсах).  [ https://docs.oracle.com/javase/tutorial/java/generics/bounded.html ]

Вкратце, "extends" может использоваться только для указания границы (класса или интерфейса) для некоторого параметра типа класса T, но не для любого параметра типа интерфейса T. В вашем случае,

открытый класс MyClass & lt; X расширяет SomeInterface, Y расширяет SomeOtherClass & Х & GT;

Компилятор разрешает X быть классом. Для второго вхождения X вместе с параметром типа Y (который в любом случае явно должен быть классом), требуется, чтобы X был интерфейсом. Поскольку он уже разрешил X быть классом, он сигнализирует об ошибке для второго появления X,

Тип X не является интерфейсом;

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

он не может быть указан как ограниченный параметр