스트림의 장점은 별도의 저장공간(변수)이 필요없이 사용 가능하다는 점이다.

하지만 스트림의 단점은 저장해두지 않았기 때문에 한 번 사용하면 재사용을 할 수 없다.

스트림은 스트림 데이터와 스트림 연산으로 이루어져있다.

스트림 데이터는 연속된 데이터이면 된다. 데이터의 집합체라면 전부 스트림 데이터로 만들 수 있다.

스트림의 특징: 조립성, 병렬화, 선언형

선언형이라는 것은 내가 구현할 필요가 없이 선언만 하면 된다는 것이다.

스트림 연산은 메소드의 인자가 전부 다 람다식 또는 메소드 참조이다.

스트림이 뭔지 아직 감이 안 올텐데 스트림 데이터가 들어오면 내가 필요한 것만 빼낼 수도 있고 몇 개 skip할 수도 있고 개수를 count할 수도 있고 평균도 낼 수 있고 매핑도 할 수 있고 집계도 할 수 있고 많은 것을 할 수 있다.

스트림 연산은 how 방식이 아니라 what 방식이다. 어떻게(how) 코딩할까 복잡하고 디테일한 것은 신경쓰지 않고 무슨(what)동작을 수행시킬지만 명시하면 된다. 디테일한 것들은 전부 구현체에 맡겨버리는 것이다.

 

컬렉션 vs 스트림

1. 처리 방식

스트림은 처리방식이 스트리밍이고 컬렉션은 처리방식이 다운로드 방식이다.

따라서 컬렉션은 한 번 만들어 놓으면 재사용할 수 있다. (물론 컬렉션 객체에서 iterator를 뽑아내면 iterator는 한 번 밖에 사용 못 한다.)

따라서 컬렉션은 저장공간이 필요하고 스트림 방식은 저장공간이 필요없다.

 

2. 반복 방식

컬렉션은 전부 외부 방식(Iterator 방식)이다. 요즘은 for문 대신 for~each문을 많이 사용하는데 for~each문은 iterator을 뽑아서 사용하지는 않지만 자세히 생각하면 궁극적으로는 외부 iteration(외부반복)이다. 바깥에서 누군가가 건드려줘야 한다.

하지만 스트림은 iterator가 필요 없다. 내부에서 다 알아서 해 준다.

 

3. 코드 구현

컬렉션은 명령형, 스트림은 선언형이다. 명령형이 훨씬 더 어렵다.

 

4. 원본 데이터 변경 여부

컬렉션은 원본데이터가 변경되지만 스트림은 원본데이터는 변경하지 않고 소비만 한다. 한 번 쓰고 끝나는거.

 

여기서도 느끼는 인생의 진리는 하나를 얻으면 하나를 포기할 수밖에 없다.

컬렉션은 다운로드를 해야하므로 저장공간이 크게 필요하지만 재사용이 가능하고

스트림은 시간에 따라 흘러가는 것이므로 별도의 저장공간이 필요하지 않지만 재사용이 불가능하다.

 

예제로 살펴보자.

 

예제1

다음 두 개의 코드는 똑같은 일을 수행한다.

랜덤으로 20개의 정수를 만들어서 리스트에 추가한 뒤 그 중에서 10보다 큰 수만 뽑아서 출력하는 일을 수행한다.

public class Stream1Demo {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        List gt10 = new ArrayList<>();
        Random r = new Random();
        for(int i = 0; i < 20; i++) {
            list.add(r.nextInt(30)); //30 미만의 정수 20개를 list에 추가
        }
        for (int i : list)
            gt10.add(i);
        Collections.sort(gt10);
        System.out.println(gt10);
    }
}
public class Stream1Demo {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        Random r = new Random();
        for(int i = 0; i < 20; i++) {
            list.add(r.nextInt(30)); //30 미만의 정수 20개를 list에 추가
        }
        list.stream().filter(i -> i > 10).sorted().forEach(x -> System.out.print(x + " "));
    }
}

그러니까 이 4줄과

for (int i : list)
	gt10.add(i);
Collections.sort(gt10);
System.out.println(gt10);

이 1줄은

list.stream().filter(i -> i > 10).sorted().forEach(x -> System.out.print(x + " "));

똑같은 일을 한다.

 

심지어 스트림을 사용한 코드는 gt10이라는 저장공간이 따로 필요하지 않으므로 gt10 리스트를 선언하는 부분도 필요 없으니까 코드가 4줄 더 짧은 것이다. 스트림을 사용하면 코드도 짧아지고 저장공간도 덜 든다는 것을 알 수 있다.

 

예제2

아래의 코드는 int 배열에서 5보다 큰 수들만 더해서 출력하는 것이다.

public class Stream2Demo {
    public static void main(String[] args) {
        int[] ia = {1, 6, 3, 9, 5, 4, 2};
        IntStream is = Arrays.stream(ia); //배열을 스트림으로 만들 때에는 Arrays.stream(배열) 이렇게 사용
        int sum = is.filter(i -> i > 5).sum();
        System.out.println(sum);
    }
}

실행결과는 아래와 같다.

배열을 스트림 객체로 만들고 싶을 때에는 Arrays 클래스의 static 메소드 stream(배열)를 사용하면 스트림 객체를 만들 수 있다. Arrays.stream(배열)의 인자로 int 타입의 배열이 들어가면 IntStream이 나오고 double 타입의 배열이 들어가면 DoubleStream이, long 타입의 배열이 들어가면 LongStream이 만들어진다. 이 외의 타입은 제네릭 타입의 Stream<T> 객체를 반환한다.

여기에 들어가면 Arrays 클래스의 메소드들을 볼 수 있다.

 

Arrays (Java SE 10 & JDK 10 )

Compares two int arrays lexicographically over the specified ranges. If the two arrays, over the specified ranges, share a common prefix then the lexicographic comparison is the result of comparing two elements, as if by Integer.compare(int, int), at a rel

docs.oracle.com

왠만하면 그냥 Stream보다는 IntStream 같이 어느 타입에 특화된 스트림을 사용하는 것이 좋다. IntStream에는 그냥 Stream에는 없는 int에 특화된, 정수이기 때문에 가능한, 편리한 메소드들이 있다.

+ Recent posts