Java8의 Stream Collect 사용 방법 및 예제

JS · 09 Oct 2018

Collect는 Stream의 데이터를 변형 등의 처리를 하고 원하는 자료형으로 변환해 줍니다.

Collect는 다음과 같은 기능들을 제공합니다.

  • 스트림의 아이템들을 List 또는 Set 자료형으로 변환
  • 스트림의 아이템들을 joining
  • 스트림의 아이템들을 Sorting하여 가장 큰 객체 리턴
  • 스트림의 아이템들의 평균 값을 리턴

이 외에 다른 많은 기능들도 제공합니다.

Collect의 기본적인 기능들을 예제와 함께 알아보겠습니다.

Stream의 아이템들을 HashSet으로 리턴

collect()는 Stream의 아이템들을 우리가 원하는 자료형으로 변환할 수 있습니다.

collect는 다음 3개의 인자를 받습니다. collect는 이 3개의 인자를 이용하여 스트림의 아이템들을 어떤 자료형으로 만들어 줍니다.

collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)

아래는 Stream의 결과를 HashSet<String>로 리턴하는 코드입니다. HashSet에 대한 Supplier, accumulator, combiner 를 collect의 인자로 전달해주었고 HashSet 객체가 리턴되었습니다.

Stream<String> fruits = Stream.of("banana", "apple", "mango", "kiwi", "peach", "cherry", "lemon");
HashSet<String> fruitHashSet = fruits.collect(HashSet::new, HashSet::add, HashSet::addAll);
for (String s : fruitHashSet) {
    System.out.println(s);
}

결과

banana
apple
cherry
kiwi
lemon
peach
mango

위의 코드는 collect에 3개의 params을 넣어야 해서 다소 사용하기 번거롭습니다. Collectors라는 라이브러리가 기본적인 기능들을 제공해주어 이런 불편함을 해결해줍니다.

다음은 Collectors와 함께 collect로 HashSet 객체를 만드는 예제입니다. 인자에 toSet()만 넣어주면 Set 자료형으로 만들어 줍니다.

Stream<String> fruits = Stream.of("banana", "apple", "mango", "kiwi", "peach", "cherry", "lemon");
Set<String> fruitSet = fruits.collect(Collectors.toSet());
for (String s : fruitSet) {
    System.out.println(s);
}

결과

banana
apple
cherry
kiwi
lemon
peach
mango

Stream의 아이템들을 List 객체로 리턴

Collectors를 이용하여 스트림의 아이템들을 List 객체로 리턴할 수 있습니다.

다음 예제처럼 toList()를 인자에 넣어주면 List 객체로 리턴됩니다.

Stream<String> fruits = Stream.of("banana", "apple", "mango", "kiwi", "peach", "cherry", "lemon");
List<String> fruitList = fruits.collect(Collectors.toList());
for (String s : fruitList) {
    System.out.println(s);
}

결과

banana
apple
mango
kiwi
peach
cherry
lemon

스트림 아이템을 1개의 String 객체로 만들기

스트림의 모든 아이템들을 1개의 String으로 만들 수도 있습니다.

아래 코드처럼 joining()을 인자로 넣어주시면 됩니다.

Stream<String> fruits = Stream.of("banana", "apple", "mango", "kiwi", "peach", "cherry", "lemon");
String result2 = fruits.collect(Collectors.joining());
System.out.println(result2);

결과를 보면 1개의 string에 모든 값이 더해져 있습니다.

bananaapplemangokiwipeachcherrylemon

만약 string 객체 간에 구분자를 넣어주고 싶다면 Collectors.joining(", ")처럼 param으로 구분자를 전달하면 됩니다.

Stream<String> fruits = Stream.of("banana", "apple", "mango", "kiwi", "peach", "cherry", "lemon");
String result2 = fruits.map(Object::toString).collect(Collectors.joining(", "));
System.out.println(result2);

결과

banana, apple, mango, kiwi, peach, cherry, lemon

가장 큰 객체 1개만 리턴하기

Stream의 아이템들을 어떤 조건으로 비교하여 충족되는 1개의 아이템만 리턴받을 수 있습니다.

다음 코드는 문자열의 길이로 아이템들을 비교해서, 가장 긴 문자열을 리턴하는 예제입니다. 리턴 타입은 Optional입니다. 비교 함수(Comparator)를 직접 만들어 넣어줄 수 있습니다.

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.maxBy;

Stream<String> fruits = Stream.of("banana", "apple", "mango", "kiwi", "peach", "cherry", "lemon");
Function<String, Integer> getCount = fruit-> fruit.length();
Optional<String> result = fruits.map(Object::toString).collect(maxBy(comparing(getCount)));
System.out.println("result: " + result.orElse("no item"));

결과

result: banana

Collectors로 평균 값 구하기

Collectors는 스트림 아이템들의 평균 값을 구해주는 기능들도 제공합니다.

List<Integer> list = Arrays.asList(1,2,3,4);
Double result = list.stream().collect(Collectors.averagingInt(v -> v*2));
System.out.println("Average: "+result);

결과

Average: 5.0

Custom 객체에 Collect 적용하기

Custom 객체에 대해서도 Collect를 할 수도 있습니다. Collectors.toMap()을 사용하면 됩니다.

Stream<Fruit> fruits2 = Stream.of(new Fruit("1", "banana"), new Fruit("2", "apple"),
        new Fruit("3", "mango"), new Fruit("4", "kiwi"),
        new Fruit("5", "peach"), new Fruit("6", "cherry"),
        new Fruit("7", "lemon"));
Map<String, String> map = fruits2.collect(Collectors.toMap(Fruit::getId, Fruit::getName));
for (String key : map.keySet()) {
    System.out.println("key: " + key + ", value: " + map.get(key));
}

static class Fruit {
    public String id;
    public String name;

    Fruit(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}

결과

key: 1, value: banana
key: 2, value: apple
key: 3, value: mango
key: 4, value: kiwi
key: 5, value: peach
key: 6, value: cherry
key: 7, value: lemon

toMap 수행시, 동일한 Key에 대한 예외처리

toMap() 수행 중, 동일한 key를 갖고 있는 데이터를 만나면 IllegalStateException을 발생시킵니다. 이런 경우 예외처리를 할 수 있습니다. 3번째 param으로, 존재하는 값과 새로운 값이 중 어떤 값을 저장할지 정의해야 합니다. 아래 코드는 기존에 등록된 값을 사용하도록 (existingValue, newValue) -> existingValue)로 정의 했습니다. (코드를 보면 key 5를 갖고 있는 데이터가 두개 있습니다.)

Stream<Fruit> fruits2 = Stream.of(new Fruit("1", "banana"), new Fruit("2", "apple"),
        new Fruit("3", "mango"), new Fruit("4", "kiwi"),
        new Fruit("5", "peach"), new Fruit("6", "cherry"),
        new Fruit("5", "lemon"));
Map<String, String> map = fruits2.collect(
    Collectors.toMap(item -> item.getId(), item -> item.getName(),
            (existingValue, newValue) -> existingValue));
for (String key : map.keySet()) {
    System.out.println("key: " + key + ", value: " + map.get(key));
}

key 5의 경우 peach와 lemon이 있는데, peach가 먼저 등록되었기 때문에 peach가 map에 저장됩니다.

key: 1, value: banana
key: 2, value: apple
key: 3, value: mango
key: 4, value: kiwi
key: 5, value: peach
key: 6, value: cherry

이번에는 동일 key를 갖고 있는 데이터가 있을 때, 두개의 값을 합하여 저장하도록 정의할 수 있습니다. 3번째 param으로, 두개의 값을 더한 값을 리턴하는 함수를 정의하면 됩니다.

Stream<Fruit> fruits2 = Stream.of(new Fruit("1", "banana"), new Fruit("2", "apple"),
        new Fruit("3", "mango"), new Fruit("4", "kiwi"),
        new Fruit("5", "peach"), new Fruit("6", "cherry"),
        new Fruit("5", "lemon"));
Map<String, String> map = fruits2.collect(
        Collectors.toMap(item -> item.getId(), item -> item.getName(),
                (existingValue, newValue) -> {
                    String concat = existingValue + ", " + newValue;
                    return concat;
                }));
for (String key : map.keySet()) {
    System.out.println("key: " + key + ", value: " + map.get(key));
}

key 5의 값을 보면, 두 개의 문자열이 합쳐진 문자열이 저장되었습니다.

key: 1, value: banana
key: 2, value: apple
key: 3, value: mango
key: 4, value: kiwi
key: 5, value: peach, lemon
key: 6, value: cherry
codechachaCopyright ©2019 codechacha