Почему Stream.allMatch() возвращает true для пустого потока?
У моего коллеги была ошибка, связанная с нашим предположением, что пустой поток, вызывающий allMatch()
, вернет false
.
if (myItems.allMatch(i -> i.isValid()) {
//do something
}
Конечно, это наша вина за допущение и не чтение документации. Но я не понимаю, почему поведение allMatch()
по умолчанию для пустого потока возвращает true
. Каковы были причины для этого? Подобно anyMatch()
(который возвращает false), эта операция используется императивным способом, который отсылает монаду и, вероятно, используется в инструкции if
. Учитывая эти факты, существует ли какая-либо причина, почему для большинства применений желательно иметь allMatch()
по умолчанию true
в пустом потоке?
Ответы
Ответ 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
.