Является ли Javas Collectors.toSet() гарантированным разрешить null?

Интерфейс Set не делает promises о том, позволяют ли реализации использовать элементы null. Каждая реализация должна объявить об этом в своей документации.

Collectors.toSet() promises, чтобы вернуть реализацию Set, но явно не дает никаких гарантий относительно типа, изменчивости, сериализуемости, или безопасность потока Set возвращена ". Нулевая безопасность не упоминается.

Текущая реализация Collectors.toSet() в OpenJDK всегда использует HashSet, которая допускает нулевые элементы, но это может измениться в будущем, а другие реализации могут по-другому.

Если реализация Set запрещает элементы null, она бросает NullPointerException в разное время, в частности во время попытки add(null). Казалось бы, если Collectors.toSet() решил использовать реалистичную реализацию Set с нулевым нетерпимостью, вызов stream.collect(Collectors.toSet()) на Stream stream бросить. Спецификация collect не содержит никаких исключений, а также не спецификация любого из методов Collector. Это может означать, что вызов collect разрешает null в пределах stream, но, с другой стороны, неясно, действительно ли это означает много, поскольку NullPointerException является неконтролируемым исключением и не обязательно должен быть указан.

Является ли это более четким в любом другом месте? В частности, является ли следующий код гарантированным не бросать? Гарантируется ли возврат true?

import java.util.stream.*;

class Test {
    public static boolean setContainsNull() {
        return Stream.of("A", "list", "of", null, "strings")
                     .collect(Collectors.toSet())
                     .contains(null);
    }
}

Если нет, то я предполагаю, что мы всегда должны гарантировать, что поток не содержит нулей перед использованием Collectors.toSet() или будет готов к обработке NullPointerException. (Достаточно ли этого исключения?) Альтернативно, когда это неприемлемо или сложно, мы можем запросить конкретную реализацию набора с использованием кода типа Collectors.toCollection(HashSet::new).

Изменить: существует существующий вопрос, который кажется поверхностным схожим, и этот вопрос закрылся как предполагаемый дубликат этого. Однако связанный вопрос вообще не затрагивает Collectors.toSet(). Более того, ответы на этот вопрос составляют основные предположения моего вопроса. Этот вопрос спрашивает: допустимы ли пустые значения в потоках? Да. Но что происходит, когда (полностью разрешенный) поток, содержащий нули, собирается через стандартный сборщик?

Ответ 1

Существует разница между преднамеренно неопределенным поведением, таким как "тип, изменчивость, сериализуемость или безопасность потоков", и недоопределенным поведением, таким как null поддержка.

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

Обратите внимание, что хотя зарезервированное право на возвращение действительно неизменяемого или сериализуемого Set non- не использовалось, просто потому, что такого типа не было в релизе Java 8, применение null поведения non- было возможно даже без существования адекватного хэша Тип карты, как и в groupingBy запрещает null ключи, хотя и не указан.

Следует также отметить, что в то время как groupingBy коллектор намеренно отвергает null ключи в коде реализации, toMap является хорошим примером того, как фактическое поведение становится частью договора. В Java 8 toMap допускает null ключи, но отклоняет null значения просто потому, что вызывает Map.merge который имеет такое поведение. Кажется, это не было намеченным поведением в первую очередь. Теперь в Java 9 сборщик toMap без функции Map.merge больше не использует Map.merge (JDK-8040892, см. Также этот ответ), но намеренно отклоняет null значения в коде сборщика, чтобы быть поведенчески совместимыми с предыдущей версией. Просто потому, что никогда не говорилось, что null поведение намеренно не определено.

Итак, Collectors.toSet() (и аналогично Collectors.toList()) теперь допускают null значения для двух основных версий Java, и нет никакой спецификации, говорящей, что вы не должны принимать это как должное, так что вы можете быть совершенно уверены, что это не изменится в будущее.