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

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(), подобных этому, которые я просто не получаю, или это просто забавный способ бросить любых сотрудников для цикла, которые не знают функционального программирования?

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

4b9b3361

Ответ 1

Проблема заключается в том, что вы не используете API Stream последовательно. Вы ограничиваете вариант использования тем, что лучше всего описывать как "фактически не используя API Stream", поскольку вы настаиваете на возврате 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, содержащий все элементы.

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

Ответ 2

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

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(), подобные этому, которые я просто не получаю, или это просто забавный способ бросить любых сотрудников для цикла, которые не знают функционального программирования?

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

IMHO, стоит поощрять разработчиков к изучению функционального программирования, поскольку у него есть некоторые очень полезные идеи о том, как структурировать ваш код, и вы выиграете от него, даже если вы не использовали синтаксис 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()));
}

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

Ответ 3

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

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

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

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

Ответ 5

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

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));
}

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

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

Ответ 6

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

Это истинная философия создания API с точки зрения Software Engineering.

Ответ 7

Ниже представлено более короткое решение 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;

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