sschmeck
sschmeck

Reputation: 7685

Reduce a collection to a single object of another type with streams

I found no solution yet for reducing a collection of one type (e.g. MyData) to one object of another type (e.g. MyResult) using Java streams.

@Test
public void streams() {
    List<MyData> collection = Arrays.asList(
            new MyData("1", "cool"), 
            new MyData("2", "green"),
            new MyData("3", "night"));

    // How reduce the collection with streams?
    MyResult result = new MyResult();
    collection.stream().forEach((e) -> {
        if (e.key.equals("2")) {
            result.color = e.value;
        }
    });

    MyResult expectedResult = new MyResult();
    expectedResult.color = "green";
    assertThat(result).isEqualTo(expectedResult);
}

public static class MyData {
    public String key;
    public String value;

    public MyData(String key, String value) {
        this.key = key;
        this.value = value;
    }
}

public static class MyResult {
    public String color;
    public String material;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        MyResult myResult = (MyResult) o;
        return Objects.equals(this.color, myResult.color) &&
                Objects.equals(this.material, myResult.material);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.color, this.material);
    }
}

Is there a solution using some kind of reduce or fold?

Upvotes: 5

Views: 15533

Answers (5)

Youcef LAIDANI
Youcef LAIDANI

Reputation: 59950

Did you mean :

collection.stream()
        .filter(e -> e.key.equals("2"))
        .findFirst()
        .orElse(null);//Or any default value

You can even throw an exception :

collection.stream()
        .filter(e -> e.key.equals("2"))
        .findFirst()
        .orElseThrow(() -> new IllegalArgumentException("No data found"));

Upvotes: 13

sschmeck
sschmeck

Reputation: 7685

I found a solution using Collector.of().

Collector<MyData, MyResult, MyResult> myCollector = Collector.of(
        () -> new MyResult(),
        (a, e) -> {
            if (e.key.equals("2")) {
                a.color = e.value;
            }
        },
        (a1, a2) -> null, // Not used, therefore not working scaffold
        a -> a
);
MyResult result = collection.stream().collect(myCollector);

After all it doesn't feel idiomatically.

Upvotes: 1

Abishek ram R
Abishek ram R

Reputation: 196

Are you trying to convert a list of some type to another based on a condition ?

MyResult result = new MyResult();
        List<MyResult> resultList = collection.stream().filter(e -> e.key.equals("2")).map(e -> {
            MyResult resultTemp = new MyResult();
            result.color = e.value;
            return result;
        }).collect(Collectors.toList());

try this

Upvotes: 1

Garreth Golding
Garreth Golding

Reputation: 1005

The operation you're looking for is Map. Take a read through the Stream docs for some more details: https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

Here is a code snippet for mapping your List of MyData objects into a List of MyResult objects.

    List<MyResult> collect = collection.stream().map(myData -> {
        MyResult myResult = new MyResult();
        myResult.color = myData.value;
        return myResult;
    }).collect(Collectors.toList());

If you want to find just a single value simply add a filter transformation before the Map transformation.

    List<MyResult> collect = collection.stream()
            .filter(myData -> "2".equals(myData.key))
            .map(myData -> {
                MyResult myResult = new MyResult();
                myResult.color = myData.value;
                return myResult;
            }).collect(Collectors.toList());

Upvotes: 0

daniu
daniu

Reputation: 14999

Well you can just first find the element you're looking for

Optional<MyData> found = stream.filter(data -> data.key == 2).findFirst();

then map it to your result

Optional<MyResult> result = found.map(data -> { 
        MyResult r = new MyResult(); 
        r.color = data.color; 
        return r; 
});

Now you have an Optional that is empty if the original stream didn't contain an item with key 2, and a matching MyResult otherwise.

Upvotes: 0

Related Questions