프로그래밍 언어/Java

[Java] 자바 스트림으로 변환과 스트림 생성 방법

happy_life 2022. 7. 5. 21:27

자바 스트림

 

스트림 사용 이유

기존에 컬렉션이나 배열의 데이터를 다룰 때 Iterator와 반복문을 사용해 코드를 작성했다. 하지만 길기도 할 뿐더러 재사용성도 떨어진다. 뿐만 아니라 데이터 소스마다 방식이 다르다는 문제도 있다. 각 컬렉션 클래스에는 같은 기능의 메서드가 중복 정의되어있다. List를 정렬할 때는 Collections.sort()를 사용해야 하고, 배열을 정렬할 때는 Arrays.sort()를 사용한다.

이러한 문제를 해결하기 위해 나온 것이 스트림이다.

 

기존의 코드 예제

public class StreamEx1 {
    public static void main(String[] args) {

        String[] strArr = {"aaa", "bbb", "ddd"};
        List<String> strList = Arrays.asList(strArr);

        // 정렬 방식이 다르다.
        Arrays.sort(strArr);
        Collections.sort(strList);

        for (String str : strArr) {
            System.out.println("str = " + str);
        }

        for (String str : strList) {
            System.out.println("str = " + str);
        }
    }
}

위와 같이 정렬 방식이 각기 다르고, 출력을 위해 반복문을 사용해야 하므로 코드가 길어진다.

 

Stream 코드 예제

public class StreamEx1 {
    public static void main(String[] args) {

        String[] strArr = {"aaa", "bbb", "ddd"};
        List<String> strList = Arrays.asList(strArr);

        // 스트림으로 변환
        Stream<String> arrStream = Arrays.stream(strArr);
        Stream<String> listStream = strList.stream();

        // 같은 방식으로 정렬하고 출력 가능
        arrStream.sorted().forEach(System.out::println);
        listStream.sorted().forEach(System.out::println);

    }
}

스트림으로 변환 후 동일한 방식으로 코드를 작성할 수 있다. 반복문을 사용하지 않으므로 코드가 한 줄로 간결하다.

 

스트림 생성

스트림의 소스가 될 수 있는 대상은 컬렉션, 배열, 임의의 수 등 다양하다.

 

컬렉션

컬렉션의 stream() 메서드가 존재한다. 그래서 Collection의 자손인 List와 Set을 구현한 클래스들에서 이 메서드를 사용해 stream을 만들 수 있다.

 

코드 예제

ArrayList<Integer> integers = new ArrayList<>();
HashSet<String> strings = new HashSet<>();


Stream<Integer> stream = integers.stream();
Stream<String> stream2 = strings.stream();

 

배열

배열을 소스로 스트림을 생성하는 방법엔 두 가지가 있다. Stream과 Arrays의 static 메서드를 사용하는 것이다.

 

코드 예제

//Stream 클래스의 of 사용
Stream<String> stream1 = Stream.of("a", "b", "c"); // 가변인자
Stream<String> stream2 = Stream.of(new String[]{"a", "b", "c"});

//Arrays 클래스의 stream 사용
Stream<String> stream3 = Arrays.stream(new String[]{"a", "b", "c"});
Stream<String> stream4 = Arrays.stream(new String[]{"a", "b", "c"},0,2);

 

 

특정 범위의 수

특정 범위의 수를 지정하여 Stream을 생성할 수 있다.

 

코드 예제

// 특정 범위의 수
IntStream intStream2 = IntStream.range(1, 10);// 1 2 3 .. 9
IntStream intStream3 = IntStream.rangeClosed(1, 10); // 1 2 3 .. 10

range는 끝을 포함하지 않고, rangeClosed는 끝을 포함한다. 이 두 가지 모두 지정한 범위 사이의 연속된 정수를  스트림으로 생성해 반환한다.

 

임의의 수

난수를 생성하는 데에 사용되는 Random클래스를 활용해 난수 Stream을 생성할 수 있다.

 

코드 예제

// 난수
IntStream intStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 5개만 자르기

 

람다식 - iterate(), generate()

Stream 클래스의 iterate()와 generate()는 람다식을 매개변수로 받아, 이 계산식에 의해 계산되는 값을 요소로 하는 스트림을 생성할 수 있다.

 

코드 예제

Stream<Integer> stream = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6 ...
Stream<Integer> stream2 = Stream.generate(() -> 2); // 2, 2, 2, 2

iterate()는 왼쪽 파라미터를 초기값으로, 아래와 같이 연산 결과를 스트림에 넣는다.

iterate()

 

 

 

generate()는 오른쪽 파라미터에 지정된 값만 계속 스트림에 넣는다.

 

스트림 특징

1. 데이터 소스를 변경하지 않는다.

스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐, 데이터 소스를 변경하지 않는다.

 

2. 일회용이다.

Iterator처럼 일회용이다. 한번 사용하면 닫혀 사용할 수 없다. 필요 시 다시 생성해야 한다.

 

3. 작업을 내부 반복으로 처리한다.

반복문을 메서드 내부에 숨긴다.

void forEach(Consumer<? super T> action){
  for(T t: src){
     action.accept(t); // 내부 반복
     }
}