Generics - несоответствие компилятора [jdk 1.8.0_162] - PullRequest
0 голосов
/ 12 января 2019

Я столкнулся с проблемой с обобщенными типами, которая заставляет меня озадачиться тем, как компилятор на самом деле работает с обобщенными типами. Учтите следующее:

// simple interface to make it a MCVE
static interface A<F, S> {
    public F getF();    
    public S getS();
}

static <V, S> Comparator<A<V, S>> wrap(Comparator<S> c) {
    return (L, R) -> c.compare(L.getS(), R.getS());
}

Следующее не скомпилируется, поскольку оба универсальных типа уменьшаются до Object при вызове thenComparing:

Comparator<A<String, Integer>> c = wrap((L, R) -> Integer.compare(L, R))
    .thenComparing(wrap((L, R) -> Integer.compare(L, R)));

Но если я разбью их, как в следующем примере, все будет правильно скомпилировано (и запущено):

Comparator<A<String, Integer>> c = wrap((L, R) -> Integer.compare(L, R));
c = c.thenComparing(wrap((L, R) -> Integer.compare(L, R)));

Итак, вопрос в следующем: что здесь происходит? Я подозреваю, что это связано со странным поведением компилятора, а не с предполагаемой спецификацией языка? Или я что-то упускаю здесь очевидное?

Ответы [ 2 ]

0 голосов
/ 13 января 2019

Просто чтобы сделать одну центральную часть информации более заметной, «цепной вызов»:

В любом виде T t = m1().m2() все, что осталось от последней точки, будет иметь ограниченный вывод типа без типа цели . Тип цели T может помочь только вывести m2(), а не m1().

Это преднамеренный выбор в дизайне языка, чтобы держать сложность в страхе. В этом подходе вывод типа m1() должен завершиться перед началом вывода типа для m2() (в противном случае, где вы будете искать метод m2?) .

0 голосов
/ 12 января 2019

Вторая попытка компилируется правильно, потому что вы сами указали тип переменной, сообщая компилятору, что это такое, потому что у компилятора недостаточно информации, чтобы это выяснить.

Посмотрите на этот упрощенный пример, он из vavr (кстати, здорово). Существует класс Try<T>, представляющий результат какой-либо операции. Общий параметр T - это тип этого результата. Существует статическая фабрика для немедленного создания ошибки, то есть у нас нет результата, но универсальный параметр все еще там:

static <T> Try<T> failure(Throwable exception) {
    return new Try.Failure(exception);
}

Откуда взялся T? Использование выглядит следующим образом:

public Try<WeakHashMap> method() {
  return Try.failure(new IllegalArgumentException("Some message"));
}

Здесь Try<WeakHashMap> - мой выбор, а не компиляторы, вы можете поместить туда все, что захотите, потому что выбираете тип.

То же самое в вашем примере, Comparator имеет только общий параметр String, потому что вы указали его и согласился с ним компилятор (как с Try<WeakHashMap>). Когда вы добавляете цепочечный вызов, вы заставляете компилятор выводить сам тип, и это был Object, потому что какого еще типа он мог быть?

Что еще вы можете сделать (обратите внимание на Testing.<String, Integer>wrap):

public class Testing {
  static interface A<F, S> {
    public F getF();
    public S getS();
  }

  static <V, S> Comparator<A<V, S>> wrap(Comparator<S> c) {
    return (L, R) -> c.compare(L.getS(), R.getS());
  }

  public static void main(String[] args) {
    Comparator<A<String, Integer>> comp = Testing.<String, Integer>wrap((L, R) -> Integer.compare(L, R))
      .thenComparing(wrap((L, R) -> Integer.compare(L, R)));
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...