Каковы недостатки в возвращении пустой неизменяемой коллекции вместо выделения новой и пустой? - PullRequest
2 голосов
/ 16 июня 2019

Раньше я вызывал Collections.emptyList(), emptySet и emptyMap, чтобы вернуть ссылку на неизменяемую пустую коллекцию вместо null из моих функций.Это потому, что я не вижу причин для выделения нового пустого экземпляра коллекции:

private static Collection<Cheese> getCheesesOld() {
    if (cheesesInStock.isEmpty()) {
        return Collections.emptyList();
    }
    return new ArrayList<>(cheesesInStock);
}

Сегодня я прочитал следующее в 54-м выпуске Effective Java:

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

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

Должен ли я изменить свое поведение по умолчанию и в целях защиты вернуть копию (элемент 50) внутреннего спискадаже если он пуст вместо возврата уже выделенного Collections.emptyList()?

Ответы [ 3 ]

1 голос
/ 16 июня 2019

Но помните, это - это оптимизация

Я думаю, что здесь это относится к практике избежания нового выделения пустых коллекций.Если вы возвращаете общую пустую коллекцию, она должна быть неизменной.Однако, как я читаю статью 54, Блох говорит, что если у вас нет доказательств того, что выделение нового пустого списка (new ArrayList<>()) наносит ущерб производительности, это нормально.

1 голос
/ 17 июня 2019

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

Затем возвращается тот факт, что в некоторых обстоятельствах фактически изменяемый ArrayList возвращается, чтоне обеспечивает неизменность, это оптимизация.

Это не так уж и много, учитывая

private static Collection<Cheese> getCheesesOld() {
    if(cheesesInStock.isEmpty()) {
        return Collections.emptyList();
    }
    return Collections.unmodifiableList(new ArrayList<>(cheesesInStock));
}

, который только добавляет небольшие накладные расходы, с точки зрения производительности или с точки зрения разработки / сопровождения кода,Три строки кода, специфичные для обработки пустых списков, весят больше.

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

К счастью, в последних версиях Java есть простое решение.Вы можете использовать

private static Collection<Cheese> getCheesesOld() {
    return List.copyOf(cheesesInStock);
}

с Java 10 или новее.Возвращаемый список является неизменным, но не является списком, заключенным в другой объект защиты, и он будет иметь оптимизированные версии не только для пустых списков, но и для списков небольших размеров, для которых разработчики JRE сочли это полезным.

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

Вам нужно только справиться с новой обработкой null, то есть радикальным отклонением null элементов.

1 голос
/ 16 июня 2019

Collections.emptyList() не размещать внутренний массив, знать их размер мгновенно, возможно, для них написаны лучшие итераторы / сплитераторы (имеет значение, когда вы вызываете .stream()); с другой стороны, ветвь if может быть дороже, чем вы получили бы от Collections.emptyList().

Обычный совет здесь - используйте то, что работает и читается для вас легче. Единственное место, где эта оптимизация может присутствовать, но это не так, находится в Collectors.toList(): она всегда возвращает пустой ArrayList, вместо того, чтобы потенциально возвращать Collections.emptyList() в java-8. Я предполагаю, что это сделано именно потому, что проверки для такого редко используемого сценария привели бы к штрафам в часто используемых сценариях; таким образом, не сделано.

Вы также должны взять этот совет из книги с недоверием - этот человек пишет библиотеки, которыми пользуются миллионы (очень хорошие советы), но вы делаете то же самое?

...