Преобразовать список в отображение с использованием потоков Java

У меня в коде повторяется следующий шаблон:

class X<T, V>
{
    V doTransform(T t) {
        return null; // dummy implementation
    }

    Map<T, V> transform(List<T> item) {
        return item.stream().map(x->new AbstractMap.SimpleEntry<>(x, doTransform(x))).collect(toMap(x->x.getKey(), x->x.getValue()));
    }
}

Требовать использования AbstractMap.SimpleEntry беспорядочно и неуклюже. Использование Linqs анонимных типов более элегантно.

Есть ли более простой способ добиться этого с помощью потоков?

Спасибо заранее.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
52
3

Ответы 3

Вы можете вызвать doTransform в преобразователе значений:

Map<T, V> transform(List<T> item) {
    return item.stream().collect(toMap(x -> x, x -> doTransform(x)));
}

К сожалению, в Java нет точного эквивалента анонимных типов C#.

В этом конкретном случае вам не нужна промежуточная операция map, как предложил @Jorn Vernee. вместо этого вы можете выполнить извлечение ключей и значений в сборщике toMap.

Однако когда дело доходит до случаев, когда вы думаете, что вам нужно что-то подобное из анонимных типов C#, вы можете подумать:

  1. анонимные объекты (не всегда может быть тем, что вы хотите, в зависимости от вашего варианта использования)
  2. Arrays.asList(...), List.of(...) (может не всегда быть тем, что вам нужно, в зависимости от вашего варианта использования)
  3. массив (не всегда может быть тем, что вы хотите, в зависимости от вашего варианта использования)

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

Тем не менее, ваш текущий пример можно упростить до:

Map<T, V> transform(List<T> items) {
    return items.stream().collect(toMap(Function.identity(),this::doTransform));
}

Приятно !, я, наверное, немного поленился с ответом;) Еще есть фабричный метод Map.entry(), начиная с Java 9, который немного короче.

Jorn Vernee 04.06.2018 00:28

В этом конкретном примере нет необходимости делать промежуточное хранилище:

Map<T, V> transform(List<T> item) {
    return item.stream().collect(toMap(x -> x, x -> doTransform(x)));
}

Но если вам это нужно, Java 9 предлагает более простой заводской метод,

Map<T, V> transform(List<T> item) {
    return item.stream()
               .map(x -> Map.entry(x, doTransform(x)))
               .collect(toMap(x -> x.getKey(), x -> x.getValue()));
}

до тех пор, пока вам не придется иметь дело с null.

Здесь вы можете использовать анонимный внутренний класс,

Map<T, V> transform(List<T> item) {
    return item.stream()
               .map(x -> new Object(){ T t = x; V v = doTransform(x); })
               .collect(toMap(x -> x.t, x -> x.v));
}

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

Последнее можно обойти с помощью метода, например

Map<T, V> transform(List<T> item) {
    return item.stream()
               .map(x -> new Object(){ T getKey() { return x; } V v = doTransform(x); })
               .collect(toMap(x -> x.getKey(), x -> x.v));
}

Но читабельности это не добавляет.

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

Map<T, V> transform(List<T> item) {
    return item.stream()
               .map(x -> capture(x, doTransform(x)))
               .collect(HashMap::new, (m,f) -> f.accept(m::put), HashMap::putAll);
}
public static <A,B> Consumer<BiConsumer<A,B>> capture(A a, B b) {
    return f -> f.accept(a, b);
}

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

Другие вопросы по теме