Подтвердить что ты не робот

Почему Stream.allMatch() возвращает true для пустого потока?

У моего коллеги была ошибка, связанная с нашим предположением, что пустой поток, вызывающий allMatch(), вернет false.

if (myItems.allMatch(i -> i.isValid()) { 
    //do something
}

Конечно, это наша вина за допущение и не чтение документации. Но я не понимаю, почему поведение allMatch() по умолчанию для пустого потока возвращает true. Каковы были причины для этого? Подобно anyMatch() (который возвращает false), эта операция используется императивным способом, который отсылает монаду и, вероятно, используется в инструкции if. Учитывая эти факты, существует ли какая-либо причина, почему для большинства применений желательно иметь allMatch() по умолчанию true в пустом потоке?

4b9b3361

Ответ 1

Это называется пустой истины. Все члены пустой коллекции удовлетворяют вашему состоянию; в конце концов, можете ли вы указать на тот, который этого не делает? Аналогично, anyMatch возвращает false, потому что вы не можете найти элемент вашей коллекции, который соответствует условию. Это сбивает с толку множество людей, но оказывается, что это самый полезный и последовательный способ определить "все" и "все" для пустых множеств.

Ответ 2

Когда я вызываю list.allMatch (или его аналоги на других языках), я хочу определить, не соответствуют ли какие-либо элементы в list предикату. Если элементов нет, ни один из них не может совпадать. Моя следующая логика будет выбирать элементы и ожидать, что они совпадут с предикатом. Для пустого списка я не буду выбирать элементы, и логика будет по-прежнему звучать.

Что делать, если allMatch возвратил false для пустого списка?

Моя простая логика потерпит неудачу:

 if (!myList.allMatch(predicate)) {
   throw new InvalidDataException("Some of the items failed to match!");
 }
 for (Item item : myList) { ... }

Мне нужно будет запомнить замену с помощью !myList.empty() && !myList.allMatch().

Короче говоря, allMatch возврат true для пустого списка не только логически звучит, но и лежит на счастливом пути выполнения, требуя меньше проверок.

Ответ 3

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

Если поток пуст, квантификация называется вакуумированной и всегда верна. Oracle Docs: потоковые операции и конвейеры

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

В чистой математике осмысленные истинные утверждения обычно не представляют интереса сами по себе, но они часто возникают как базовый случай доказательств с помощью математической индукции. Википедия: Свободная правда

Ответ 4

Вот еще один способ подумать об этом:

allMatch() - && & какая сумма() равна +

Рассмотрим следующие логические утверждения:

IntStream.of(1, 2).sum() + 3 == IntStream.of(1, 2, 3).sum()
IntStream.of(1).sum() + 2 == IntStream.of(1, 2).sum()

Это имеет смысл, потому что sum() является просто обобщением+. Однако, что происходит, когда вы удаляете еще один элемент?

IntStream.of().sum() + 1 == IntStream.of(1).sum()

Мы видим, что имеет смысл определить IntStream.of().sum() или сумму пустой последовательности чисел определенным образом. Это дает нам "элемент идентичности" суммирования или значение, которое при добавлении к чему-то не имеет эффекта (0).

Мы можем применить ту же логику к булевой алгебре.

Stream.of(true, true).allMatch(it -> it) == Stream.of(true).allMatch(it -> it) && true

В более общем виде:

stream.concat(Stream.of(thing)).allMatch(it -> it) == stream.allMatch(it -> it) && thing

Если stream = Stream.of(), то это правило все равно должно применяться. Мы можем использовать "элемент идентификации" && чтобы решить эту проблему. true && thing == thing, поэтому Stream.of().allMatch(it -> it) == true.