简体   繁体   中英

Transform list to mapping using java streams

I have the following pattern repeated throughout my code:

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

Requiring the use of AbstractMap.SimpleEntry is messy and clunky. Linqs use of anonymous types is more elegant.

Is there a simpler way to achieve this using streams?

Thx in advance.

You can call doTransform in the value mapper:

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

Unfortunately, Java doesn't have an exact equivalent of C#'s anonymous types.

In this specific case, you don't need the intermediate map operation as @Jorn Vernee has suggested. instead, you can perform the key and value extraction in the toMap collector.

However, when it gets to cases where you think you need something as such of C#'s anonymous types you may consider:

  1. anonymous objects (may not always be what you want depending on your use case)
  2. Arrays.asList(...) , List.of(...) (may not always be what you want depending on your use case)
  3. an array (may not always be what you want depending on your use case)

Ultimately, If you really need to map to something that can contain two different types of elements then I'd stick with the AbstractMap.SimpleEntry .

That, said your current example can be simplified to:

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

In this specific example, there is no need to do the intermediate storage at all:

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

But if you need it, Java 9 offers a simpler factory method,

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

as long as you don't have to deal with null .

You can use an anonymous inner class here,

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

but it's less efficient. It's an inner class which captures a reference to the surrounding this , also it captures x , so you have two fields, t and the synthetic one for capturing x , for the same thing.

The latter could be circumvented by using a method, eg

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

But it doesn't add to readability.

The only true anonymous types are the types generated for lambda expressions, which could be used to store information via higher order functions:

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

but you'd soon hit the limitations of Java's type system (it still isn't a functional programming language) if you try this with more complex scenarios.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM