Универсальные символы Java с несколькими классами

385

Я хочу иметь объект Class, но я хочу заставить любой класс, который он представляет, расширить класс A и реализовать интерфейс B.

Я могу сделать:

Class<? extends ClassA>

Или:

Class<? extends InterfaceB>

но я не могу сделать оба. Есть ли способ сделать это?

Алекс Бердсли
источник

Ответы:

614

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

<T extends ClassA & InterfaceB>

Смотрите Generics Учебник по sun.com, а именно Bounded Тип Параметры секции, в нижней части страницы. Вы можете перечислить несколько интерфейсов, если хотите, используя & InterfaceNameкаждый из них, который вам нужен.

Это может быть сколь угодно сложно. Чтобы продемонстрировать, посмотрите декларацию JavaDoc Collections#max, которая (заключенная в две строки):

public static <T extends Object & Comparable<? super T>> T
                                           max(Collection<? extends T> coll)

почему так сложно? Как сказано в FAQ по Generics Java: для сохранения бинарной совместимости .

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

class classB { }
interface interfaceC { }

public class MyClass<T extends classB & interfaceC> {
    Class<T> variable;
}

чтобы получить variableто, что имеет ограничение, которое вы хотите. Для получения дополнительной информации и примеров, проверьте страницу 3 Generics в Java 5.0 . Обратите внимание, <T extends B & C>что имя класса должно идти первым, а интерфейсы - последующими. И, конечно, вы можете перечислить только один класс.

Эдди
источник
95
Это полезно Стоит отметить, что класс должен стоять первым, вы не можете сказать «<T extends InterfaceB & ClassA>».
EricS
5
Как вы делаете то же самое для T должен расширить класс ИЛИ реализовать интерфейс?
Рагунат Джавахар
1
@RagunathJawahar: Вы не можете быть слишком открытым по этому поводу. У вас должны быть некоторые границы, и один из них заранее знает, где у вас будут интерфейсы, где у вас будут классы, и как вы будете использовать наследование. Вы в значительной степени должны знать для параметра типа, будет ли это класс или интерфейс.
Эдди
4
В первой строке вашего ответа написано, что «ваш шаблон должен выглядеть примерно так». Там нет ?в этом выражении, так это действительно «подстановочный знак»? Я спрашиваю, потому что не могу заставить работать всю концепцию «расширения двух вещей одновременно», когда параметр типа действительно является подстановочным знаком.
The111
1
Да, это на самом деле не подстановочный знак, и вы не можете делать то, что попросил ОП, с подстановочным знаком. Вы можете делать то, что хотел ОП, эффективно, но не с подстановочным знаком.
Эдди
17

Вы не можете сделать это с параметрами «анонимного» типа (то есть с использованием подстановочных знаков ?), но вы можете сделать это с параметрами «именованного» типа. Просто объявите параметр типа на уровне метода или класса.

import java.util.List;
interface A{}
interface B{}
public class Test<E extends B & A, T extends List<E>> {
    T t;
}
user2861738
источник
2
Почему подстановочные знаки не разрешены? то есть я не понимаю, почему это не должно быть действительным: ArrayList <? расширяет список ClassA & InterfaceB>; И потом , если вы вытащили элемент из списка, вы можете присвоить его переменной типа ClassA или типа InterfaceB
Mark
Замена подстановочного знака на переменную - отличная техника! Но это не всегда работает. Например, Java не допускает переменные типа name в типах значений аннотации, в то время как она разрешает использование подстановочных знаков.
вздохнул