Ограниченные шаблоны в ссылочных типах - PullRequest
3 голосов
/ 09 октября 2019

У меня проблемы с пониманием следующего кода о двух Predicate объектах. Первый использует нижний ограниченный подстановочный знак, второй ограниченный верхним.

Predicate<? super String> p1 = s -> s.startsWith("a"); // why can I call startsWith()?
Predicate<? extends String> p2 = s -> s.startsWith("a");

p1.test("a"); // works
p2.test("a"); // doesn't work (why?)

Что я не понимаю в p1, так это то, почему можно вызывать методы из класса String,например startsWith()? Почему я могу только передать String объекты в p1.test(), я ожидал, что смогу вызывать его и для Number и Object объектов.

Поскольку p1 ведет себя, я думал p2будет, но это не так. Я даже не могу передать объект String в p2.test(). Это не имеет смысла для меня, потому что мы ожидаем, что объект наследуется от String (включая String).

Я думаю, что это может быть связано с тем фактом, что мы указываем ссылкутип, а не тип самого объекта. Но какой тип тогда используется для объекта?

Ответы [ 2 ]

5 голосов
/ 09 октября 2019

Для вас законно вызывать startsWith для p1, хотя p1 набирается с нижней границей ? super String, потому что аргумент типа выводится как String. Лямбда-выражение s -> s.startsWith("a"); выводится как Predicate<String>, что разрешено присваивать переменной типа Predicate<? super String>.

. Компилируется:

Predicate<String> ps = s -> s.startsWith("a");
Predicate<? super String> p1 = ps;

Это не:

// no "startsWith" on Object
Predicate<? super String> p1 = (Object s) -> s.startsWith("a");

Ссылка JLS находится в Раздел 15.27.3 , "Тип лямбда-выражения".

Если T является параметризованным подстановочным знакомтип функционального интерфейса и лямбда-выражение вводятся неявно, тогда тип наземной цели - это параметризация без подстановочных знаков (§9.9) T.

Здесь тип наземной цели является типом лямбда-выражения, а T является целевым типом, который здесь представляет собой тип переменной вашей нижней границы. Это позволяет компилятору назначить Predicate<String> в качестве основного целевого типа, который становится типом лямбда-выражения.

Также обратите внимание, что вы не можете передать объект суперкласса в p1.test, потому что вы могли (и вы уже назначили Predicate<String> на p1, что занимает String.

p1.test(new Object()); // Error: can't pass something higher than String

Что касается того, почему вы не можете передать String на p2.test, когда выиметь ограниченный сверху символ подстановки, такой как ? extends String, это означает, что параметром типа может быть любой класс, который является String или подтипом. (Компилятор игнорирует, что String здесь final, и не может быть никаких подклассов String.) Предикату p2 можно присвоить Predicate<SillyString>, предполагая, что SillyString является подклассом String. Но вы не можете передать String методу, который может ожидать SillyString.

2 голосов
/ 09 октября 2019

Если у вас есть Predicate<? super String>, это означает, что фактическая реализация Predicate гарантированно сможет обрабатывать экземпляр String. Видит ли реализация String как String или CharSequence или даже Object, не имеет значения, если она может обрабатывать тип. Это обеспечивает гибкость в API, который использует интерфейс. Например:

void addFilter(Predicate<? super String> filter) { ... }

Вы можете вызвать addFilter с экземпляром Predicate<CharSequence> или Predicate<Object>, это не имеет значения для API. Например:

addFilter((CharSequence cs) -> cs.length() % 2 == 0);

Однако, когда API фактически вызывает filter, он имеет для передачи экземпляра String в test. Если бы API было разрешено вызывать filter.test(new Object()), то Predicate<CharSequence>, переданный в addFilter выше, завершится ошибкой с ClassCastException.

Когда у вас есть Predicate<? extends CharSequence> (с использованием CharSequence, начиная с Stringявляется последним классом) вы не можете вызвать test, потому что реализация, вероятно, не сможет обработать любой тип CharSequence. Например:

Predicate<? extends CharSequence> predicate = (String s) -> s.startsWith("...");
predicate.test(new StringBuilder());

A ClassCastException будет выброшено, потому что «реальный тип» predicate на самом деле Predicate<String>, а не Predicate<StringBuilder>. Таким образом, компилятор отклоняет вызов test, поскольку он не является типобезопасным.

Я также рекомендую прочитать Что такое PECS (Producer Extends Consumer Super)? .

...