개요.

Java8을 이해하고 모던자바를 더 잘 사용하기 위함.

 

관련소스코드

 

이전글

20200421 [java8] Java8InAction 읽기 & 기록 [ch01 ~ ch03]

 

Chapter04 : 스트림 소개

스트림은 무엇인가?
스트림을 이용하면 선언형, 즉 질의를 표현할 수 있다. (물어볼 수 있음을 의미)
루프와 if 조건문 등의 제어블록을 사용해서 어떻게 동작하는지 지정할 필요가 없이 `특정한 동작` 만을 수행시킬수 있다.

스트림과 컬렉션의 차이

컬렉션은 현재 자료구조가 포함되는 모든 값을 메모리에 저장하는 자료구조이다. 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다. (컬렉션에 요소를 추가하거나 컬렉션의 요소를 있다. 이런연산을 수행할 때마다 컬렉션의 모든 요소를 메모리에 저장해야하며, 컬렉션에 추가하려는 요소는 미리 계산되어야 한다.) 스트림은 요청할때만 요소를 계산하는 고정된 자료구조이다. 사용자가 요청한 값만 스트림에서 추출한다는 것이 핵심요소이다. 

 

스트림은 딱 한번 소비된다. 

final List<String> list = Arrays.asList("Seoul", "Busan", "Daegu");
final Stream<String> stream = list.stream();
stream.forEach(System.out::println);

// 컴파일 에러는 나지 않지만 두 번 이상 스트림을 재활용 하지못한다.
// stream has already been operated upon or closed
// stream.forEach(System.out::println);

 

외부반복과 내부반복

컬렉션의 인터페이스를 사용하려면, 사용자가 직접 요소를 반복해야 한다.(외부반복) 반면 스트림에서는 내부반복을 수행한다. 

 

내부반복과 외부반복의 그림적차이

 

Chapter05 : 스트림 활용

스트림으로 활용할 수있는 것은 무궁무진하다. 거기서 모든 내용을 설명하기 보다는 내가 모른 것만 작성하려고 한다.

  • 필터링과 슬라이싱 :: filter(), limit(), skip()
  • 매핑과 평면화 :: map(), flatMap()
  • 검색과 매칭 :: allMatch(), anyMatch(), noneMatch(), findFirst(), findAny() ...
  • 리듀싱(모든 요소를 처리해 값으로 도출) :: reduce()
+) 쇼트서킷
때로는 전체 스트림은 처리하지 않더라도 결과를 반환할 수 있다. 표현식에서 하나라도 거짓이면 결과가 나오며 나머지 표현식에 결과에 상관없이 전체 결과도 거짓이 된다. 이러한 상황을 쇼트서킷이라고 한다. 쇼트서킷 기법에는 anyMatch(), allMatch(), noneMatch() 가 있다.

map() 메소드는 Stream<T> 를 반환한다. 그래서 Stream API 에서는 박싱비용을 피할수 있도록 기본형 매핑으로 특화된 메소드를 제공하고 있다.

  • mapToInt(), mapToDouble(), mapToLong()
public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;

    public Dish(String name, boolean vegetarian, int calories, Type type){
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }
}
List<Dish> menu = Arrays.asList(
        new Dish("pork", false, 800, Dish.Type.MEAT),
        new Dish("beef", false, 700, Dish.Type.MEAT),
        new Dish("chicken", false, 400, Dish.Type.MEAT),
        new Dish("french fries", true, 530, Dish.Type.OTHER),
        new Dish("rice", true, 350, Dish.Type.OTHER),
        new Dish("season fruit", true, 120, Dish.Type.OTHER),
        new Dish("pizza", true, 550, Dish.Type.OTHER),
        new Dish("prawns", false, 300, Dish.Type.FISH),
        new Dish("salmon", false, 450, Dish.Type.FISH));

int calories = menu.stream()
        .mapToInt(Dish::getCalories)    // Stream<T> 형으로 반횐되기 때문에 언박싱을 수행
        .sum();

 

함수로 무한 스트림 만들기

스트림 API 에서는 함수에서 스트림을 만들 수 있는 두 개의 정적 메소드 Stream.iterate, Stream.generate를 제공한다. 두 연산을 통해서 무한 스트림, 즉 고정되지 않은 스트림을 만들 수 있다. 여기서 무한 스트림을 이용하게 되면 무한하게 값이 출력되기 때문에 limit() 메소드와 함께 사용해주고 있다. 만약 limit() 이 없다면 언바운드 스트림이 된다.

 

iterate 함수

이터레이트 함수는 초깃값과 람다를 받아서 무한정 값을 생산할 수 있다. 일반적으로 연속된 일련의 값들을 만들 때에는 이터레이트 함수를 이용한다.

 

iterate 함수를 이용하여 피보나치 수열 만들기

Stream.iterate(new int[]{0, 1}, (array) -> new int[]{array[1], array[0] + array[1]})
        .limit(10)
        .forEach(t -> System.out.println(t[0]));

 

generate 함수

generate 는 iterate 와는 다르게 생산된 각 값을 연속적으로 계산하지 않는다. generate 는 Supplier<T> 를 인수로 받아서 새로운 값을 생산한다.

 

generate 함수를 이용하여 피보나치 수열 만들기

IntSupplier fib = new IntSupplier() {
    private int previous = 0;
    private int current = 1;

    @Override
    public int getAsInt() {
        int oldPrevious = this.previous;
        int nextValue = this.previous + this.current;
        this.previous = this.current;
        this.current = nextValue;
        return oldPrevious;
    }
};

IntStream.generate(fib).limit(10).forEach(System.out::println);

위 코드에서 IntSupplier 인스턴스를 만들었고, 만들어진 객체는 기존 피보나치 요소와 두 인스턴스 변수에 어떤 피보나치 요소가 들어갔는지 추적하므로 가변상태 객체이다. 

 

getAsInt() 를 호출하면 객체상태가 바뀌며 새로운 값을 생성한다. iterate 를 사용했을 때는 각 과정에서 새로운 값을 생성하면서도 기존 상태를 바꾸지 않는 순수한 불변상태를 유지했다.  스트림을 병렬로 처리하면 불변상태의 객체를 고수해야 한다.

 

기억하려고 하는 문장.

  • filter(), map() 등은 상태를 저장하지 않는 상태 없는 연산이다. reduce 같은 연산은 값을 계산하는데 필요한 상태를 저장한다. 
  • sorted(), distinct() 등은 새로운 스트림에 변환하기에 앞서서 스트림의 모든 요소를 버퍼에 저장한다. 이런 메소드는 상태 있는 연산이라고 한다.
Posted by doubler
,