Ответ 1
Вы можете использовать:
class Foo<T extends Number & Comparable> {...}
Класс Foo с одним параметром типа, T. Foo должен быть создан с типом, который является подтипом Number и реализует Comparable.
Есть ли способ в 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 с указанными ограничениями?Вы можете использовать:
class Foo<T extends Number & Comparable> {...}
Класс Foo с одним параметром типа, T. Foo должен быть создан с типом, который является подтипом Number и реализует Comparable.
В контексте 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.
Возможно, вы можете немного упростить свою модель: слишком много дженериков быстро становятся настоящей болью с точки зрения удобочитаемости, и это довольно проблема, если вы определяете открытый API. Обычно, если вы больше не можете понять, что должно быть внутри скобок, тогда вы заходите слишком далеко для своей потребности - и вы не можете ожидать, что пользователи поймут это лучше, чем вы сами...
В любом случае, чтобы скомпилировать ваш код, вы можете попробовать определить что-то вроде этого в типе Shape
:
public <S extends Shape<?,?>> void addChildShape(S shape);
Это должно сделать это.
НТН
Вы написали следующее:
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 для этой проблемы либо) переменные типа должны начинаться объявляться во всех местах, где они действительно не принадлежат.
Используйте предварительный процессор для генерации "уменьшенной" версии вашего кода. Использование apt и аннотаций может быть хорошим способом сделать это.
Я мог бы быть здесь не здесь, но мое понимание дженериков немного отличается.
Я прошу кого-то исправить меня, если я ошибаюсь.
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 дает вам возможность использовать методы для широкого круга объектов, а не для интерфейсов. Конечная запись в общий файл ДОЛЖНА быть типом объекта.
Зарезервированное слово "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 возможным интерфейсом и, таким образом, разрешил компиляцию. Поскольку это было не так, компилятор уточняет,
он не может быть указан как ограниченный параметр