Java Generics super / extends - различное поведение (для целей изучения OCP java 8) - PullRequest
0 голосов
/ 09 мая 2018

Предположим, у меня есть следующая структура:

java.lang.Object
class A extends Object { public boolean mA() {return true;} }
class B extends A { public boolean mB() {return false;} }

Сейчас я пишу основной метод для его использования. Я в порядке с понятиями ниже: (тип 'Object', возвращаемый из метода get для super)

public static void m1(List<? super A> l ) {
    l.get(0).toString();
}

, так как я могу позвонить, используя

m1(new ArrayList<A>());
m1(new ArrayList<Object>());

У меня тоже все нормально: (тип 'A' возвращается из метода get для extends)

public static void m2(List<? extends A> l ) {
    l.get(0).mA();
}

так как я могу позвонить, используя

m2(new ArrayList<A>());
m2(new ArrayList<B>());

Наконец, мой вопрос: почему приведенное ниже определение с «super» получает тип «A» в качестве параметра вместо Object?

Predicate<? extends A> p1 = a -> a.mA(); // ok
Predicate<? super A> p2 = a -> a.mA(); // why is it of type 'A' too??

Я думал, что это превратится в нечто вроде (я знаю, что приведенный ниже код недействителен ... Я просто думаю, что это будет похоже на это).

PredicateImpl<T super A> {
    public boolean test(T t);
}

Я также проверил декомпилированный класс, но не получил никакой новой подсказки. Может кто-нибудь сказать мне, что происходит под капотом? Я уже читал обсуждения PECS, но это не помогло мне в этом конкретном вопросе.

Заранее спасибо! ;)

===== Редактирование с дополнительным кодом после получения ответа =====

Predicate<? super A> pSuper = a -> a.mA();
Predicate<? extends A> pExtends = a -> a.mA();      // ?? A ??

Predicate<Object> pObject = o -> o.equals(null);
Predicate<A> pA = a -> a.mA();
Predicate<B> pB = b -> b.mB();

pSuper = pObject;
pSuper = pA;
pSuper = pB;        // compiler error

pExtends = pObject; // compiler error
pExtends = pA;
pExtends = pB;

Интересный альтернативный синтаксис (хотя я думаю, что Predicate <& quest; extends Anything> довольно бесполезен, поскольку он принимает только 'null' в тестовом методе). Во всяком случае, я нахожу такие упражнения, и я должен научиться этому независимо.

Predicate<? extends A> pExtends2 = (B b) -> b.mB();

1 Ответ

0 голосов
/ 09 мая 2018

JLS , раздел 9.9 , говорит о выводе типа лямбда-выражений. В одном из разделов говорится о выводе типов при наличии подстановочных знаков и границ.

Когда универсальный функциональный интерфейс параметризован подстановочными знаками, существует много разных экземпляров, которые могут удовлетворять подстановочному знаку и создавать различные типы функций. Например, каждый из Predicate<Integer> (тип функции Integer -> boolean), Predicate<Number> (тип функции Number -> boolean) и Predicate<Object> (тип функции Object -> boolean) является Predicate<? super Integer>. Иногда из контекста, такого как типы параметров лямбда-выражения, можно узнать, какой тип функции предназначен (§15.27.3). В других случаях необходимо выбрать один; в этих обстоятельствах используются границы .

(жирный акцент мой)

Здесь с целевым типом Predicate<? super A> возможны несколько возможных типов лямбда-параметра a: Object и A. Граница целевого типа - ? super A, поэтому в качестве типа используется граница A.

Это имеет смысл для любого подстановочного знака, верхнего или нижнего. С верхней границей связанный тип имеет смысл. Тип A или ниже, но это может быть просто A, поэтому должны быть доступны методы A. Predicate<A> присваивается Predicate<? extends A>.

С нижней границей тип A или выше. Но почему бы не выбрать Object? Потому что A работает просто отлично.

A Predicate<A> можно назначить на Predicate<? super A>. Predicate<Object> также может быть назначен на Predicate<? super A>, но это было бы излишне ограничительным. Лямбда-выражение будет ограничено вызовом Object методов, когда оно может легко вызывать A методов, как вы написали (метод mA). Здесь имеет смысл выбрать наиболее ограничительный тип из возможных, чтобы программисту было доступно большинство методов для вызова. Кроме того, функциональный тип интерфейса, который является наиболее ограничивающим в соответствии с границами, должен быть назначаемым. Predicate<A> присваивается Predicate<? super A>.

Вы можете даже назначить лямбда-выражение a -> a.mA() для Predicate<? extends B>, Predicate<? super B> или даже Predicate<B>. Предполагаемый тип - B, и этот тип, безусловно, имеет метод mA, наследующий его от A.

Если вы попытаетесь присвоить лямбда-выражение a -> a.mA() Predicate<Object> или Predicate<?>, тогда Object должен быть выведенным типом. Здесь вы получите ошибку компилятора о том, что mA не определено в Object.

...