it-roy-ru.com

Java 8 потоков и карт стоит?

Такое ощущение, что потоки и функции отображения Java 8 настолько многословны, что на самом деле не являются улучшением. Например, я написал некоторый код, который использует коллекцию для создания другой, модифицированной коллекции:

private List<DartField> getDartFields(Class<?> model) {
    List<DartField> fields = new ArrayList<>();
    for (Field field : model.getDeclaredFields()) {
        if (!Modifier.isStatic(field.getModifiers())) {
            fields.add(DartField.getDartField(field));
        }
    }
    return fields;
}

Это кажется идеальным вариантом использования для потоков Java 8 и их функций, поэтому я переписал это так:

private List<DartField> getDartFields(Class<?> model) {
    return Arrays.asList(model.getDeclaredFields())
            .stream()
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .map(field -> DartField.getDartField(field))
            .collect(Collectors.toList());
}

Но я не уверен, что мне это нравится больше. Это 236 символов по сравнению с 239 в обычном стиле Java. Это не кажется более или менее читабельным. Приятно, что вам не нужно объявлять ArrayList, но необходимость вызывать .collect(Collectors.toList()) и Arrays.asList (в зависимости от типа данных) не лучше.

Есть ли какое-то практическое улучшение использования .stream(), которого я просто не понимаю, или это просто забавный способ бросить коллег по работе, которые не знают функционального программирования?

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

23
CorayThan

Проблема в том, что вы не используете API Stream последовательно . Вы ограничиваете вариант использования чем-то, что лучше всего описать как «фактически не использующий Stream API», поскольку вы настаиваете на возвращении Collection. Это особенно абсурдно, так как это метод private, поэтому вы также можете полностью адаптировать вызывающих абонентов.

Рассмотрите возможность изменить метод на

private Stream<DartField> getDartFields(Class<?> model) {
    return Stream.of(model.getDeclaredFields())
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .map(field -> DartField.getDartField(field));
}

и посмотрите, что вызывающий (ие) на самом деле хочет сделать. Обычно им не нужна Collection как самоцель, но они хотят выполнить действие или даже больше операций, которые могут быть связаны, например, распечатать их:

getDartFields(Foo.class).forEach(System.out::println);

Наиболее интересной особенностью является ленивая природа потока, которая подразумевает, что после возврата getDartFields никаких действий еще не было выполнено, и если вы используете такие операции, как findFirst, нет необходимости обрабатывать все элементы. Вы потеряете эту функцию, если вернете Collection, содержащий все элементы.

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

33
Holger

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

private List<DartField> getDartFields(Class<?> model) {
    return Stream.of(model.getDeclaredFields())
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .map(DartField::getDartField)
            .collect(Collectors.toList());
}

При использовании статического импорта это выглядит так

private static List<DartField> getDartFields(Class<?> model) {
    return of(model.getDeclaredFields())
            .filter(field -> !isStatic(field.getModifiers()))
            .map(DartField::getDartField)
            .collect(toList());
}

Это не кажется более или менее читабельным.

Это часто бывает ИМХО. Однако я бы сказал, что в> 10% случаев это значительно лучше. Как и любая новая функция, вы, вероятно, начнете использовать ее до тех пор, пока не ознакомитесь с ней и не обнаружите, что используете ее в удобном для вас объеме.

Есть ли какое-то практическое улучшение использования .stream (), которого я просто не понимаю, или это просто забавный способ бросить коллег по работе, которые не знают функционального программирования?

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

ИМХО, стоит поощрять разработчиков изучать функциональное программирование, поскольку у него есть несколько очень полезных идей о том, как структурировать ваш код, и вы выиграете от него, даже если вы не используете синтаксис FP.

Где API-интерфейс Streams полезен в конструкциях, которые вы раньше не потрудились бы реализовать.

Например. скажем, вы хотите проиндексировать поле по имени.

private static Map<String, DartField> getDartFields(Class<?> model) {
    return of(model.getDeclaredFields())
            .filter(field -> !isStatic(field.getModifiers()))
            .map(DartField::getDartField)
            .collect(groupingBy(f -> f.getName()));
}

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

Теперь давайте посмотрим, будет ли это быстрее, если мы будем использовать больше потоков.

private static Map<String, DartField> getDartFields(Class<?> model) {
    return of(model.getDeclaredFields()).parallel()
            .filter(field -> !isStatic(field.getModifiers()))
            .map(DartField::getDartField)
            .collect(groupingByConcurrent(f -> f.getName()));
}

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

8
Peter Lawrey

Потоки Java 8 особенно многословны, в основном из-за преобразования в поток, а затем обратно в другую структуру. В FunctionalJava эквивалентом является:

private List<DartField> getDartFields(Class<?> model) {
    return List.list(model.getDeclaredFields())
        .filter(field -> !Modifier.isStatic(field.getModifiers()))
        .map(field -> DartField.getDartField(field))
        .toJavaList();
}

Я предупреждаю против того, чтобы считать символы как меру сложности. Это едва имеет значение.

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

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

Если вы хотите неизменный поток, я рекомендую поток функциональной Java, https://functionaljava.ci.cloudbees.com/job/master/javadoc/fj/data/Stream.html .

4
Mark Perry

Если вы специально ограничиваете свой вариант использования только тем, что вы опубликовали, то идиома на основе Stream не намного лучше. Однако, если вам интересно узнать, где API-интерфейс Streams является истинным преимуществом, вот несколько моментов:

  • идиома, основанная на потоке, может быть распараллелена без каких-либо практических усилий с вашей стороны (это на самом деле самая веская причина, по которой Java получила лямбду в первую очередь);
  • Потоки являются составными: вы можете передавать их и добавлять этапы конвейера. Это может значительно помочь повторному использованию кода;
  • как вы уже заметили, вы также можете обойтись лямбда-кодами: легко написать шаблонные методы, в которых вы включаете только один аспект обработки;
  • как только вы освоитесь с идиомой, код FP станет более читабельным, поскольку он более тесно связан с чем вместо как . Это преимущество увеличивается со сложностью логики обработки.

Я также хотел бы отметить, что различие в удобочитаемости является скорее историческим артефактом, чем присущим идиомам: если бы разработчики обучались FP с самого начала и работали с ним ежедневно, то это была бы императивная идиома, которая было странно и трудно следовать.

3
Marko Topolnik

Не стоит, если вы настаиваете на возвращении в коллекции. Однако вы упустили возможность - рассмотрите следующее, и вы должны увидеть, где использование потоков повышает уровень гибкости и компоновки вашего кода:

private static final Predicate<Field> isStatic
        = field -> !Modifier.isStatic(field.getModifiers());

private Stream<Field> getDeclaredFields(Class<?> model) {
    return Stream.of(model.getDeclaredFields());
}

private Stream<Field> getStaticFields(Class<?> model) {
    return getDeclaredFields(model).filter(isStatic);
}

private Stream<DartField> getDartFields(Class<?> model) {
    return getStaticFields(model)
            .map(field -> DartField.getDartField(field));
}

Дело в том, что вы можете использовать потоки как коллекции вместо механизмов для создания новых коллекций.

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

3
OldCurmudgeon

С Java 8 команда взяла объектно-ориентированный язык программирования и применила «Объективацию» для создания функционально-объектно-ориентированного программирования (смеется ... FOOP). Это займет некоторое время, чтобы привыкнуть к этому, но я утверждаю, что любая и все иерархические манипуляции с объектами должны оставаться в своем функциональном состоянии . С этой точки зрения Java чувствует, что она устраняет разрыв PHP; Позвольте данным существовать в их естественном состоянии, и сформируйте их в графический интерфейс приложения.

Это истинная философия создания API с точки зрения разработки программного обеспечения.

0
Elysiumplain

Вот более короткое решение от StreamEx

StreamEx.of(model.getDeclaredFields())
        .filter(field -> !Modifier.isStatic(field.getModifiers()))
        .map(DartField::getDartField)
        .toList();

Я думаю, что это короче/проще, по сравнению с оригинальным для цикла.

List<DartField> fields = new ArrayList<>();
for (Field field : model.getDeclaredFields()) {
    if (!Modifier.isStatic(field.getModifiers())) {
        fields.add(DartField.getDartField(field));
    }
}
return fields;

Чем важнее, тем гибче. Просто подумайте, если вы хотите сделать больше filter/map или sort/limit/groupBy/..., вам просто нужно добавить больше потокового API-вызова, и код все еще остается кратким, вложенный цикл/if else станет больше и более сложный.

0
user_3380739

С моей точки зрения, API потоков Java (map, filter, forEach, groupBy ...) фактически обеспечивают обработку данных в процессе ежедневной разработки. Вместо того, чтобы пачкать руки, вы просто указываете потоковым API, что вы хотите, а не как делать. 

Тем не менее, я не чувствую себя комфортно при чтении Java-кодов, заполненных различными API потокового интерфейса. Иногда он очень удобен при использовании потоковых API в формате кода и в макете, особенно вместе с функциональной программой. Короче говоря, это ухудшает читаемость. 

0
Sunny