Вторая попытка компилируется правильно, потому что вы сами указали тип переменной, сообщая компилятору, что это такое, потому что у компилятора недостаточно информации, чтобы это выяснить.
Посмотрите на этот упрощенный пример, он из 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)));
}
}