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

Можно ли разрешить/рекомендовать повторное использование Коллектора?

У меня в моем коде довольно много точек, которые делают:

someStream.collect(Collectors.toList())

где Collectors.toList() создает новый коллекционер при каждом использовании.

Это приводит меня к вопросу, разрешено ли и целесообразно сделать что-то вроде:

private final static Collector<…> TO_LIST = Collectors.toList()

для каждого типа, который я использую, а затем использовать этот единственный коллекционер, например:

someStream.collect(TO_LIST)

когда требуется сборщик.

Поскольку коллекторы не имеют апатрида и просто набор функций и характеристик, я должен подумать, что он должен работать, но OTOH, Collectors.toList() создает новый CollectorImpl<> для каждого вызова.

Каковы недостатки повторного использования Коллектора?

4b9b3361

Ответ 1

Я думаю, что это скорее вопрос стиля, но давайте подумаем:

  • Кажется, что обычная практика не использует такой объект коллектора CONST. В этом смысле: это может удивить некоторых читателей, и удивительным читателям редко бывает хорошо.
  • Затем: несколько кодов можно просто "скопировать" (и, вероятно, не следует избегать дублирования кода); но все же: указание на отдельный объект коллектора может сделать это немного сложнее для вас, чтобы повторно использовать или повторно использовать ваши потоковые конструкции.
  • Помимо этого: вы сами это сказали; повторное использование коллектора зависит от реализации безстоящих. Таким образом, вы заставляете себя зависить от любой реализации, являющейся апатридом. Наверное, не проблема; но, возможно, риск иметь в виду!
  • Возможно, более важно: на первый взгляд, ваша идея выглядит неплохо для оптимизации. Но хорошо; когда вы беспокоитесь о "эффектах производительности" использования потоков, то одно единственное создание объекта для окончательного сборщика "не сократит"!

Что я имею в виду: если вы обеспокоены "потерей" производительности; вам лучше изучить каждую и любую строку кода, которая использует потоки, чтобы определить, работает ли этот поток с "достаточными" объектами, чтобы оправдать использование потоков в первую очередь. Эти потоки приходят с довольно чем-то служебными данными!

Короче говоря: сообществу java еще предстоит найти "стандартные лучшие практики" для потоков; таким образом, мои (личные) два цента в это время: предпочитают те шаблоны, которые "все" используют, - избегайте делать свое дело. Особенно, когда это "связано с производительностью".

Ответ 2

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

Основной причиной не повторного использования Collector s, как видно из встроенного Collectors, является то, что вы не можете сделать это безопасным способом. Когда вы предлагаете сборщик для произвольно введенных List s, вам понадобятся непроверенные операции, чтобы всегда раздавать один и тот же экземпляр Collector. Если вы сохраните Collector в правильно типизированной переменной, вместо этого, чтобы использоваться без непроверенных операций, вы можете использовать его только для одного типа List s, чтобы остаться с этим примером.

В случае Collections.emptyList() и т.д. разработчики JRE пошли по-другому, но константы EMPTY_LIST, EMPTY_MAP, EMPTY_SET уже существовали до введения Generics, и Id говорят, что они больше универсальный, чем несколько кэшируемых Collectors, которые представляют собой всего четыре специальных случая из других более тридцати встроенных коллекционеров, которые не могут быть кэшированы из-за их функциональных параметров. Поскольку параметры функции часто реализуются с помощью лямбда-выражений, которые генерируют объекты неопределенного идентификатора/равенства, кэш, сопоставляющий их с экземплярами коллектора, будет иметь непредсказуемую эффективность, но, скорее всего, будет гораздо менее эффективным, чем менеджер памяти будет заниматься временными экземплярами.

Ответ 3

Хорошей практикой для библиотеки является предоставление factory метода для получения полезных объектов. Поскольку библиотека предоставила такой метод: Collectors.toList(), опять же хорошей практикой является позволить библиотеке решить, создавать ли новый экземпляр каждый раз, когда объект запрашивается или нет, вместо того, чтобы подделывать библиотеку, тем самым уменьшая читаемость и рискуя будущими проблемами при изменении реализации.

Это будет добавлено к ответам GhostCat и Holger в качестве аргумента поддержки:)

Ответ 4

Просто крошечная заметка, что @Holger говорит в своем ответе о том, что оптимизатор является умным и полностью заменяет эту конструкцию полностью выполнимым, и называется scalar replacement. Когда объект, используемый внутри метода, деконструируется, а его поля stack allocated like normal local variables. Таким образом, результат Collector может не обрабатываться на уровне JVM в качестве объекта per-se. Это произойдет при JIT time.

Ответ 5

Классическая проблема использования одного статического объекта для создания одного, созданного "на лету", является изменчивостью. Быстрое сканирование источника Java 8 подчеркивает поле Set<Characteristics> как возможную проблему.

Очевидно, что в каком-то коде можно было бы что-то сделать:

private final static Collector<Object, ?, List<Object>> TO_LIST = Collectors.toList();


public void test() {
    // Any method could do this (no idea why but it should be possible).
    TO_LIST.characteristics().add(Collector.Characteristics.IDENTITY_FINISH);
}

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

Так ИМХО - не надо!

Ответ 6

Это будет случай преждевременной оптимизации. Создание объекта довольно дешево. На обычном ноутбуке я ожидал бы, что смогу создать между объектами 10M-50M в секунду. С учетом этих чисел все упражнение становится бессмысленным.