본문으로 바로가기

stream을 이용한 간결하고 가독성 높은 코드 만들기

category Java 2020. 7. 29. 21:36

스트림(Stream)은 자바 8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.

Stream은 Iterator와 비슷한 역할을 하지만, 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하므로 병렬 처리가 쉽다는 점 그리고 중간 처리와 최종 처리 작업을 수행하는 점에서 많은 차이가 있다.

 

스트림의 특징

  • 람다식으로 요소 처리 코드를 제공한다.
    • 스트림이 제공하는 대부분의 요소 처리 메소드는 함수적 인터페이스 매개타입을 가진다.
    • 매개값으로 람다식 또는 메소드 참조를 대입할 수 있다.
  • 내부 반복자를 사용하므로 병렬 처리가 쉽다.
    • 병렬 스트림은 내부적으로 fork & join 프레임웍을 이용해서 자동적으로 연산을 병렬로 수행한다. parallel() 메서드를 호출하면 병렬로 수행한다. 병렬로 처리되지 않게 하려면 sequential()을 호출하고, 모든 스트림은 기본적으로 병렬 스트림이 아니므로 sequential()을 호출할 필요가 없다.
  • 스트림의 중간 처리와 최종 처리를 할 수 있다.
    • 스트림이 제공하는 다양한 연산을 이용해서 복잡한 작업들을 간단히 처리할 수 있다. 스트림에 정의된 메서드 중에서 데이터 소스를 다루는 작업을 수행하는 것을 연산(operation)이라고 한다.
    • 스트림이 제공하는 연산은 중간 연산과 최종 연산으로 분류할 수 있다.
      • 중간 연산: 연산결과를 스트림으로 반환하기 때문에 중간 연산을 연속해서 연결할 수 있다.
        • 핵심 메서드: map(), flatMap()
      • 최종 연산: 스트림의 요소를 소모하면서 연산을 수행하기 때문에 단 한번만 연산이 가능하다.
        • 핵심 메서드: reduce(), collect()

 

스트림과 컬렉션
자바의 기존 컬렉션과 새로운 스트림 모두 연속된 요소 형식의 값을 저장하는 자료 구조의 인터페이스를 제공한다. 여기서 sequenced이라는 표현은 순차적으로 값에 접근한다는 것을 의미한다. 데이터를 언제 계산하느냐가 컬렉션과 스트림의 가장 큰 차이라고 할 수 있겠다. 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조다. 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다. 
반면, 스트림은 이론적으로 요청할때만 요소를 계산하는 고정되 자료구조다. 이러한 스트림의 특성은 프로그래밍에 큰 도움을 준다. 사용자가 요청하는 값만 스트림에서 추출한다는 것이 핵심이다. 물론 사용자 입장에서는 이러한 변화를 알 수 없다. 결과적으로 스트림은 생산자와 소비자 관계를 형성한다. 또한 스트림은 lazy하게 만들어지는 컬렉션과 같다. 즉, 사용자가 데이터를 요청할 때만 값을 계산한다. 

 

 

다음은 실무에서 자주 쓰이는 Stream을 이용한 컬렉션 탐색 코드이다.

0. 아래 오브젝트가 있다고 가정한다.

class Human {
	private String name;
	private int age;
	private int gender;

	// getter, setter 만든 후

	public static List<Human> getHumans() {
		return Arrays.asList(
			new Human("홍길동1", 17, 1),
			new Human("홍길동2", 18, 2),
			new Human("홍길동3", 15, 2),
			new Human("홍길동4", 17, 1)
		);
	}
}

 

1. filter 를 사용하여 원하는 오브젝트 추출한다.

List<Human> mans = Human.getHumans()
			.stream()
			.filter(human -> human.getGender() == 1)
			.collect(Collectors.toList());

 

2. sorted 를 사용하여 정렬한다.

List<Human> sorteds = Human.getHumans()
			.stream()
			.sorted(Comparator.comparing(Human::getAge))
			.collect(Collectors.toList());

 

3. allMatch 를 사용하여 모든 항목의 조건이 만족하는지 확인한다.

boolean isAllMatch = Human.getHumans()
			.stream()
			.allMatch(human -> human.getAge() > 10);

 

4. anyMatch 를 사용하여 조건을 만족하는 항목이 하나라도 있는지 확인한다.

boolean isAnyMatch = Human.getHumans()
			.stream()
			.anyMatch(human -> human.getAge() > 19);

 

5. noneMatch 를 사용하여 조건을 만족하는 항목이 하나도 없는지 확인한다.

boolean isNoneMatch = Human.getHumans()
			.stream()
			.noneMatch(human -> human.getAge() > 19);

 

6. min, max 를 사용하여 특정 필드를 기준으로 가장 큰/작은 오브젝트를 가져온다.

Optional<Human> maxHuman = Human.getHumans()
			.stream()
			.max(Comparator.comparing(People:getAge));
            
Optional<Human> minHuman = Human.getHumans()
			.stream()
			.min(Comparator.comparing(People:getAge));

 

7. collect 와 Collectors.groupingBy 를 사용하여 특정한 필드로 그룹을 만들어 맵을 만든다.

Map<Integer, List<Human>> groupByGender = Human.getHumans()
			.stream()
			.collect(Collectors.groupingBy(People::getGender));

 

8. 위 메소드를 복합적으로 사용하여 활용한다.

Optional<String> human = Human.getHumans()
			.stream()
			.filter(human -> human.getGender() == 2)
			.max(Comparator.comparing(Human::getAge))
			.map(Human::getName);

댓글을 달아 주세요

대마도사 블로그
블로그 이미지 대마도사 님의 블로그
MENU
VISITOR 오늘4 / 전체17,683