Java 8 Predicate - почему нельзя объединять предикаты универсальных шаблонов? - PullRequest
0 голосов
/ 29 августа 2018

Рассмотрим следующий код:

public class Main {
    private static Predicate<? extends TestObject> predicate = testObject -> true;
    private static Predicate<? extends TestObject> predicate1 = testObject -> true;

    public static void main( String[] args ) {

         List<TestObject> objects = Lists.newArrayList( new TestObject(), new TestObject() );

         objects.stream().filter( predicate.or( predicate1 ) ).findFirst();
    }
}

Не компилируется, выдавая ошибку:

Error:(17, 48) java: incompatible types: java.util.function.Predicate<capture#1 of ? extends test.test.TestObject> cannot be converted to java.util.function.Predicate<? super capture#2 of ? extends test.test.TestObject>

Кажется, что мы не можем объединить такие предикаты с логическими операторами, такими как "или" или "и", но почему Java не может с ними справиться?

Он компилируется с простыми предикатами, такими как Predicate<TestObject>, но не с Predicate<? super TestObject> или Predicate<? extends TestObject>.

Ответы [ 4 ]

0 голосов
/ 29 августа 2018

Это снова PECS, так как вы потребляете немного TestObject через Predicate, вам потребуется ? super TestObject

0 голосов
/ 29 августа 2018

Дело не в том, как Java обрабатывает предикаты, а просто в вопросе об обобщениях.

Короче говоря, ответ таков: predicate и predicate1 не обязательно совместимы. Тип параметра or - Predicate<? super T>, при условии, что T в двух Predicate объектах - это один и тот же аргумент конкретного типа. Но это не тот случай.

Чтобы проиллюстрировать проблему, предположим:

class TestObject2 extends TestObject {
    boolean isTrue() {
        return true;
    }
}
class TestObject3 extends TestObject {
    boolean isTrue3() {
        return true;
    }
}

И (обратите внимание, что объявленные типы не изменились, но действительные лямбда-выражения изменились):

private static Predicate<? extends TestObject> predicate = 
        (TestObject2 testObject) -> testObject.isTrue();
private static Predicate<? extends TestObject> predicate1 = 
        (TestObject3 testObject) -> testObject.isTrue3();

В этом примере or будет получать неправильное значение (predicate принимает объект TestObject2, но в этом случае его можно вызывать с объектом TestObject3).

Как вы думаете, predicate1 будет работать в этом filter?

Stream.of(new TestObject2(), new TestObject2()).filter(predicate.or(predicate1));

Дело в том, что компилятор гарантирует, что Predicate<? extends TestObject> совместим как с Predicate<TestObject2>, так и с Predicate<TestObject3> (и это согласно закону обобщения)

0 голосов
/ 29 августа 2018

Возможно, вы знаете, что предикаты совместимы, а компилятор - нет.

Представьте себе этот пример:

Predicate<? extends Collection<Object>> p1 = (Set<Object> s) -> s.isEmpty();
Predicate<? extends Collection<Object>> p2 = (List<Object> l) -> l.get(0) != null;

Мы, разработчики, видим, что первый предикат может технически обрабатывать все коллекции, а второй - только списки. Но представьте, что предикаты были инициализированы где-то еще или будут изменены тем временем. Компилятор не может точно знать, для какого типа коллекций были созданы объекты предикатов. В результате вы не можете использовать их вообще:

Set<Object> set = new HashSet<>();
List<Object> list = new ArrayList<>();
p1.test(set);
p2.test(list);
p1.test(list);
p2.test(set);

Все эти вызовы не будут компилироваться, потому что компилятор не может сказать, могут ли фактические объекты, стоящие за p1 и p2, именно для этих типов коллекций. В этом смысл ? extends Collection<>: вы знаете, что это определенный подтип, но вы не можете сказать компилятору, какой именно.


Чтобы проиллюстрировать это на более простом примере:

Collection<Apple> appleBasket = ...;
appleBasket.add(new Apple());  // works
appleBasket.add(new Orange()); // does not work (obviously)

Collection<Fruit> mixedFruitBasket = ...;
mixedFruitBasket.add(new Apple());  // works
mixedFruitBasket.add(new Orange()); // works

// Now the tricky part
Collection<? extends Fruit> unknownButPureFruitBasket = ...;
unknownButPureFruitBasket.add(new Apple());  // does not work 
unknownButPureFruitBasket.add(new Orange()); // does not work

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

Попробуйте в вашей IDE:

List<? extends String> l1 = new ArrayList<>();
List<? extends String> l2 = new ArrayList<>();
l1.addAll(l2);

Затмение говорит мне:

Метод addAll (Collection1-of? Extends String>) в типе List 1 -of? extends String> не применим для аргументов (List 2 -of? extends String>)

Обратите внимание на различные типы: addAll ожидает коллекцию capture#1, l2 - коллекцию capture#2.

0 голосов
/ 29 августа 2018

filter требует Predicate<? super TestObject>, а не Predicate<? extends TestObject>.

Почему super TestObject? Поскольку здесь TestObject является входным параметром, потребитель . Согласно правилу PECS , оно должно быть помечено как супер.

Predicate<? extends TestObject> полностью отличается от Predicate<? super TestObject>. Первый может принимать все подклассы TestObject и TestObject. Последний может принимать все суперклассы TestObject и TestObject. Очевидно, они разные и несовместимые.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...