Проблемы с пониманием нижних границ при использовании с лямбдой и функциональным интерфейсом

Изучая потоки Java8, я наткнулся на следующий фрагмент кода:

Predicate<? super String> predicate = s -> s.startsWith("g");

Поскольку общий параметр является нижней границей, я решил, что это не скомпилируется. Как я это вижу, если Object является супертипом для String, то передача типа Object должна сломать его, поскольку Object не имеет функцииstartsWith(). Однако я был удивлен, увидев, что он работает без каких-либо проблем.

Далее, когда я настроил предикат, чтобы получить верхнюю границу:

<? extends String>,

он не будет компилироваться.

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


person piper1970    schedule 10.02.2016    source источник
comment
extends тоже компилируется -- Predicate<? extends String> predicate = s -> s.startsWith("g") -- однако мы не можем передать String этому предикату:) см. также мою статью по подстановочному знаку   -  person ZhongYu    schedule 10.02.2016
comment
stackoverflow.com/questions/ 33085151/   -  person ZhongYu    schedule 10.02.2016
comment
Predicate<? super String> не означает, что вы можете передать ему Object (и попытка сделать это вызовет ошибку компилятора). Подпись Predicate<? super String> просто подразумевает, что может быть Predicate<Object>, что нормально, поскольку этот предикат по-прежнему способен обрабатывать String входных данных. Другими словами, Predicate<? super String> говорит, что этот предикат может потреблять строки независимо от его фактического типа, поэтому Predicate<String> является действительной реализацией Predicate<? super String>.   -  person Holger    schedule 10.02.2016
comment
Приношу свои извинения за неполный код. Фактическим кодом, который не скомпилировался, была строка boolean test = anyMatch(predicate);, которая появилась позже. Фактическая строка предиката сама по себе компилируется как с верхним, так и с нижним синтаксисом границ. После некоторого логического размышления я понял, почему у anyMatch такая подпись.   -  person piper1970    schedule 10.02.2016


Ответы (2)


Тип аргумента Lambda является точным, он не может быть ? super или ? extends. Это описано в JLS 15.27.3. Тип лямбда-выражения. В нем представлена ​​концепция типа наземной цели (по сути, это лямбда-тип). Среди прочего указано, что:

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

Акцент мой. По сути, когда вы пишете

Predicate<? super String> predicate = s -> s.startsWith("g");

Ваш лямбда-тип Predicate<String>. Это то же самое, что:

Predicate<? super String> predicate = (Predicate<String>)(s -> s.startsWith("g"));

Или даже

Predicate<String> pred = (Predicate<String>)(s -> s.startsWith("g"));
Predicate<? super String> predicate = pred;

Учитывая тот факт, что аргументы лямбда-типа являются конкретными, после этого применяются обычные правила преобразования типов: Predicate<String> является Predicate<? super String> или Predicate<? extends String>. Так что и Predicate<? super String>, и Predicate<? extends String> должны скомпилироваться. И оба действительно работают у меня на javac 8u25, 8u45, 8u71, а также на ecj 3.11.1.

person Tagir Valeev    schedule 10.02.2016
comment
Спасибо за разъяснение. Поведение Lambda по умолчанию с ограниченными параметрами было в центре моего замешательства. - person piper1970; 10.02.2016

Я только что проверил это, само задание компилируется. Что меняется, так это то, можете ли вы на самом деле вызвать predicate.test().

Давайте сделаем шаг назад и воспользуемся заполнителем GenericClass<T> для объяснения. Для аргументов типа Foo расширяет Bar, а Bar расширяет Baz.

Расширения: когда вы объявляете GenericClass<? extends Bar>, вы говорите: «Я не знаю, что на самом деле представляет собой его аргумент универсального типа, но это подкласс Bar». Фактический экземпляр всегда будет иметь аргумент типа, отличного от подстановочного знака, но в этой части кода вы не знаете, каково его значение. Теперь рассмотрим, что это означает для вызовов методов.

Вы знаете, что у вас на самом деле есть либо GenericClass<Foo>, либо GenericClass<Bar>. Рассмотрим метод, который возвращает T. В первом случае возвращаемый тип — Foo. В последнем Bar. В любом случае, это подтип Bar, и его безопасно назначать переменной Bar.

Рассмотрим метод с параметром T. Если это GenericClass<Foo>, то передача ему Bar является ошибкой — Bar не является подтипом Foo.

Таким образом, с верхней границей вы можете использовать общие возвращаемые значения, но не общие параметры метода.

Супер: когда вы объявляете GenericClass<? super Bar>, вы говорите: «Я не знаю, что на самом деле представляет собой его аргумент универсального типа, но это суперкласс Bar». Теперь рассмотрим, что это означает для вызовов методов.

Вы знаете, что у вас на самом деле есть либо GenericClass<Bar>, либо GenericClass<Baz>. Рассмотрим метод, который возвращает T. В первом случае возвращается Bar. В последнем Baz. Если он возвращает Baz, то присвоение этого значения переменной Bar является ошибкой. Вы не знаете, что это такое, поэтому вы не можете с уверенностью предполагать что-либо здесь.

Рассмотрим метод с параметром T. Если это GenericClass<Bar>, то передача Bar допустима. Если это GenericClass<Baz>, то передача ему Bar по-прежнему допустима, потому что Bar является подтипом Baz.

Таким образом, с нижней границей вы можете использовать общие параметры метода, но не общие возвращаемые значения.

В итоге: <? extends T> означает, что вы можете использовать общие возвращаемые значения, но не параметры. <? super T> означает, что вы можете использовать общие параметры, но не возвращать значения. Predicate.test() имеет общий параметр, поэтому вам нужен super.

Еще одна вещь, которую следует учитывать: границы, указанные подстановочным знаком, относятся к фактическому аргументу типа объекта. Их последствия для типов, которые вы можете использовать с этим объектом, противоположны. Подстановочный знак верхней границы (extends) — это нижняя граница типов переменных, которым можно присваивать возвращаемые значения. Подстановочный знак нижней границы (super) — это верхняя граница типов, которые вы можете передавать в качестве параметров. predicate.test(new Object()) не будет компилироваться, потому что с нижней границей String он будет принимать только подклассы String.

person Douglas    schedule 10.02.2016
comment
Спасибо за ваше понимание. Обобщение типа возвращаемого значения/параметра значительно упрощает понимание их использования. - person piper1970; 10.02.2016