Java-дженерики с подстановочным кодом в Eclipse, но не в javac
Как следует за обобщающие Java файлы в Eclipse, но не в javac, я публикую еще один фрагмент, который компилируется и работает отлично в Eclipse, но вызывает ошибку компиляции в джавах. (Это предотвращает извлечение фрагмента из проекта из Maven.)
Автономный фрагмент:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
}
public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
public static class Foo<T> implements Comparable<Foo<T>> {
@Override
public int compareTo(Foo<T> o) {
return 0;
}
}
}
Компиляция в javac возвращается:
Main.java:11: <T>asSortedList(java.util.Collection<T>) in Main cannot be applied to (java.util.Set<Main.Foo<?>>)
List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
^
При подстановке Foo<?>
с Foo<String>
приведенный выше фрагмент будет компилироваться в javac, что означает, что проблема связана с используемым шаблоном. Поскольку предполагается, что компилятор Eclipse более терпим, возможно ли, что сниппет не является допустимой Java?
(Я использую javac 1.6.0_37 и Eclipse Indigo с уровнем соответствия компилятора 1.6)
( EDIT1: Включен другой пример, который был удален в EDIT2.)
EDIT2: Указывается неутешительный, что сравнение Foo<A>
и Foo<B>
может быть концептуально неправильным и вдохновлено ответом seh, рабочий asSortedFooList
можно записать следующим образом:
public static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
(Простая подстановка Comparable<T>
с помощью Foo<?>
в определении метода выше).
Таким образом, для javac и imho, как представляется, безопасно сравнивать любые Foo<A>
и Foo<B>
. Но по-прежнему невозможно написать общий метод asSortedList
, который возвращает представление отсортированного списка для общего набора, если его аргумент типа параметризован с помощью подстановочного знака. Я попытался "обмануть" javac, заменив Foo<?>
на S extends Comparable<S>
в asSortedFooList
, но это не сработало.
EDIT3: Позже Rafaelle указал, что есть недостаток в дизайне, поскольку реализация Comparable<Foo<T>>
не нужна, и реализация Comparable<Foo<?>>
обеспечивает ту же функциональность, что позволяет решить начальную задачу по уточненному дизайну.
(Первоначальная причина и преимущество заключались в том, что a Foo<T>
может не заботиться в некоторых целях о конкретном типе, но все же использовать экземпляр конкретного типа T
, он создается для других целей. не может использоваться для определения порядка среди других Foo
s, поскольку он может использоваться в других частях API.
Конкретный пример: предположим, что каждый Foo создается экземпляром другого аргумента для T
. Каждый экземпляр Foo<T>
имеет инкрементирующий id типа int
, который используется при реализации метода compareTo
. Теперь мы можем отсортировать список этих по-разному напечатанных Foo
и не заботиться о конкретном типе T
(выражая его с помощью Foo<?>
) и до сих пор иметь экземпляр конкретного типа T
, доступный для последующей обработки. )
Ответы
Ответ 1
Для меня это еще одна ошибка javac
. Когда вы пытаетесь отправить Collection<Foo<?>>
методу с сигнатурой:
public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c)
компилятор отмечает, что формальный параметр T
имеет верхнюю границу, поэтому проверяет, соблюдается ли ограничение с помощью вызывающего. Аргумент type является (подстановочным) экземпляром параметризованного типа Foo<T>
, поэтому тест пройдет, если Foo<?>
is-a Comparable<Foo<?>>
. Основываясь на общем определении:
class Foo<T> implements Comparable<Foo<T>>
Я бы сказал, что это правда, поэтому снова Eclipse прав, а javac
имеет ошибку. Эта запись Angelika Langer никогда не связана достаточно. Также см. соответствующие JLS.
Вы спросили, безопасен ли он или нет. Мой ответ заключается в том, что он безопасен по типу, и он показывает, что у вас есть недостаток в вашем дизайне. Рассмотрим вашу фиктивную реализацию интерфейса Comparable<T>
, где я добавил еще два поля:
public static class Foo<T> implements Comparable<Foo<T>> {
private T pState;
private String state;
@Override
public int compareTo(Foo<T> other) {
return 0;
}
}
Вы всегда возвращаете 0
, поэтому проблема не замечена. Но когда вы пытаетесь сделать это полезным, у вас есть два варианта:
- Сравнение со строковым полем
- Сравнение с элементом
T
Поле String
всегда равно String
, поэтому вы не пользуетесь переменной типа T
. С другой стороны, T
не имеет другой информации о типе, поэтому в compareTo()
вы можете иметь дело только с простым объектом, и снова параметр типа бесполезен. Вы можете достичь той же точной функциональности, выполнив Comparable<Foo<?>>
Ответ 2
В этом случае javac является правильным. Понятно, что ваш код не может работать, поскольку набор может содержать Foo<A>
и Foo<B>
, которые нельзя сравнивать друг с другом.
Вероятно, вы хотите, чтобы set был Set<Foo<X>>
для некоторой переменной типа X; к сожалению, мы не можем вводить переменную типа внутри тела метода; только в сигнатуре метода
<X> void test(){
Set<Foo<X>> setOfFoos = new HashSet<Foo<X>>();
List<Foo<X>> sortedListOfFoos = asSortedList(setOfFoos);
}
Вы можете заставить его работать чем-то вроде
<T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c)
class Foo<T> implements Comparable<Foo<?>>
Ответ 3
Я не знаю, если это вопрос, но вот (не очень хороший) ответ:
Если вы жертвуете некоторой безопасностью типа, вы можете написать
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends Comparable> List<T> asSortedList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
И он работает как в eclipse, так и в javac. Единственный риск, о котором я знаю, заключается в том, что если кто-то создает class Foo extends Comparable<Bazz>
, вы не обнаружите его во время компиляции.
Но если кто-то создает Foo extends Comparable<Bazz>
, просто убейте его/ее.
Ответ 4
Я нашел решение, которое компилируется с javac, хотя я не доволен тем, что не могу точно объяснить, почему он работает. Это требует введения промежуточной функции:
public final class Main {
public static class Foo<T> implements Comparable<Foo<T>> {
@Override
public int compareTo(Foo<T> o) {
return 0;
}
}
public static <T extends Comparable<? super T>>
List<T> asSortedList(Collection<T> c) {
final List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
private static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
return asSortedList(c);
}
public static void main(String[] args) {
final Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
final List<Foo<?>> listOfFoos = asSortedFooList(setOfFoos);
}
}
Я думаю, что это работает в силу принятия пошагового решения шаг за шагом; asSortedFooList()
фиксирует один тип, известный как Foo
, независимо от параметра типа Foo
. С этим параметром типа, связанным в asSortedFooList()
, мы можем затем вызвать ваш оригинальный asSortedList()
(ну, с одной модификацией — обратите внимание на нижнюю границу параметра типа для Comparable
), требующую привязки Foo
как типа, Comparable
.
Опять же, это слабое, случайное объяснение. Мое главное в ответе здесь просто предоставить еще один способ добраться до места назначения.
Ответ 5
Если вы можете заменить использование подстановочного знака точным типом (который может быть супертипом), ваш код будет работать. Заменить
List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
с
List<Foo<String>> sortedListOfFoos = Main.<Foo<String>>asSortedList(setOfFoos);