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);