본문 바로가기
Programming/Java

[자바/Java] 스트림 Stream - 생성

by 코딩하는 랄로 2023. 10. 15.
728x90

스트림 (Stream) 이란

자바8에서 등장한 스트림은 람다를 활용할 수 있는 개념 중 하나이다. 스트림은 '집합 자료의 연산'을 위해 사용하는 객체로서 스트림이 등장하기 이전에는 배열 또는 컬렉션 객체를 다루기 위해서 for 또는 forEach문을 돌면서 요소 하나씩을 꺼내서 다루었다.

 

간단한 경우라면, 이러한 방식은 크게 문제가 되지 않지만 로직이 복잡해질 수록 코드의 양이 많아지고 복잡해져 로직이 섞이게 되고, 메소드를 나눌 경우 루프를 여러번 도는 경우가 발생한다.

 

이러한 문제를 해결하고자 등장한 개념이 바로 스트림인 것이다. 스트림을 더 잘 이해하기 위해서는 단순히 집합 자료의 연산을 해주는 객체라는 것, 이전에 스트림이 데이터의 흐름이라는 것을 인지해야 한다.

 

스트림(Stream)
=
데이터의 흐름

 

배열 또는 컬렉션 객체에 속해 있는 데이터를 흐름에 따라 함수 여러개를 조합하고 적용하여 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있는 것이다. 이 과정에서 람다를 이용하여 코드의 양을 줄이고 간결하게 표현할 수 있다.

 

스트림의 또 다른 장점은 간단하게 병렬처리(multi-threading)가 가능하다는 것이다. 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것을 병렬처리라고 한다. 즉, 쓰레드를 이용해 더 빠르게 작업을 처리할 수 있다.

 

 

 

스트림 단계

스트림에 대한 내용은 크게 3가지로 나눌 수 있다.

 

  • 생성하기 : 스트림 인스턴스 생성
  • 가공하기 : 필터링(filtering), 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations)
  • 결과도출 : 최종적으로 결과를 만들어내는 작업(terminal operations)

 

이번 글에서는 생성하기에 대해서 다루어 보겠다.

 

 

 

스트림 생성하기

스트림을 생성하는 방법으로는 배열과 컬렉션을 이용하는 방법 이외에도 다양한 방법이 있다.

 

 

배열 스트림

배열 스트림은 이미 선언된 배열을 이용하여 스트림을 생성한다. Arrays.stream 메소드를 사용한다.

public class Main {
    public static void main(String[] args) {
    
        String[] arr = { "a", "b". "c" };
        //모든 배열 요소 stream에 추가
        Stream<String> stream = Arrays.stream(arr);

        //특정 구간의 배열 요소만 stream에 추가
        Stream<String> stream2 = Arrays.stream(arr, 1, 3); // 1 ~ 2 : [b, c]
        
    }
}

 

 

컬렉션 스트림

컬렉션 타입의 경우 인터페이스에 추가된 디폴트 메소드 stream을 이용해 스트림을 생성할 수 있다.

public interface Collection<E> extends Iterable<E> {
    
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
}

public class Main {
    public static void main(String[] args) {
    
        List<String> list = Arrays.asList("a", "b", "c");
        Stream<String> stream = list.stream();
        Stream<String> stream2 = list.parallelStream(); //병령 처리 스트림
        
    }
}

 

 

비어 있는 스트림

비어있는 스트림 또한 생성할 수 있다. 빈 스트림을 이용하면 요소가 없을 때 null 대신 사용할 수 있다,

public Stream<String> streamOf(List<String> list) {
    return list == null || list.isEmpty()
        ? Stream.empty()
        : list.stream();
}

 

 

Stream.builder()

빌더를 사용하면 스트림에 직접적으로 원하는 값을 넣을 수 있다. 마지막에 build 메소드를 사용하여 생성한 스트림을 반환한다.

public class Main {
    public static void main(String[] args) {
    
        Stream<String> builderStream = 
            Stream.<String>builder()
            .add("hello").add("stream").add("builder")
            .build();
        
        builderStream.forEach(System::println);
        //hello
        //stream
        //builder
        
    }
}

 

 

Stream.generate()

generate 메소드를 이용하면 Supplier<T> 에 해당하는 람다값을 넣을 수 있다. Supplier<T>는 인자는 없고 리턴값만 있는 함수형 인터페이스이다.

public static<T> Stream<T> generate(Supplier<T> s) { ... }

 

이 때 생성되는 스트림은 크기가 정해져있지 않고 무한하기 때문에 특정 사이즈로 최대 크기를 제한해야 한다.

public class Main {
    public static void main(String[] args) {
    
        Stream<String> generateStream =
            Stream.generate(() -> "gen").limit(5); //5개의 gen이 들어간 스트림 생성
        
    }
}

 

 

Stream.iterate()

iterate 메소드를 이용하면 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어갈 요소를 만들 수 있다. 예를 들어 1부터 2씩 증가하는 숫자들 10개를 이용하여 스트림을 만들기 위해서 다음과 같이 코드를 작성할 수 있다.

public class Main {
    public static void main(String[] args) {
    
        Stream<String> iterateStream = 
            Stream.iterate(1, n -> n + 2).limit(10); 
        
    }
}

 

iterate()도 스트림의 사이즈가 무한하기 때문에 limit()메소드를 이요하여 사이즈를 제한하는 것이 필요하다.

 

 

기본 타입형 스트림

기본 타입형 스트림을 생성하기 위해서는 Wrapper 클래스를 이용하여 스트림 객체를 생성할 수 있다. 하지만, 이러한 방법은 boxing(), unboxing() 작업이 추가로 필요하기 작업량이 늘어난다는 단점이 있다.

 

그렇기 때문에 기본 타입을 위해 제공되는 stream 클래스인 IntStream, LongStream, DoubleStream을 사용하는 것이 좋다.

public class Main {
    public static void main(String[] args) {
    
        IntSteam intStream = IntStream.range(1, 5); // 1, 2, 3, 4
        
        //boxed() -> boxing도 가능
        Stream<Integer> boxedIntStream = intStream.boxed();
        
        //Random클래스를 이용한 난수 스트림 생성
        DoubleStream doubles = new Random.doubles(3); //난수 3개 생성
        
    }
}

 

 

문자열 스트림

문자열을 이용해서 스트림을 생성할 수도 있다.

public class Main {
    public static void main(String[] args) {
    
        IntStream charsStream = "Stream".chars(); // [83, 116, 114, 101, 97, 109]
        
        Stream<String> stringStream = 
            Pattern.compile(", ").splitAsStream("Hello, String, Stream");
        // [Hello, String, Stream]
        
    }
}

 

 

파일 스트림

자바 NIO의 Files 클래스의 lines 메소드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어 준다.

public class Main {
    public static void main(String[] args) {
    
        Stream<String> fileStream = 
            Files.lines(Path.get("file.txt"), Charset.forName("UTF-8");
        
    }
}

 

 

병렬 스트림

스트림 생성 시 stream 대신 parallelStream 메소드를 사용하면 병령 스트림을 생성할 수 있다.

public class Main {
    public static void main(String[] args) {
    
        List<Integer> intList = Arrays.asList(1, 2, 3);
        
        //병렬 스트림 생성
        Stream<Integer> parallelStream = intList.parallelStream();
        
        //병령 여부 확인
        boolean isParallel = parallelStream.isParallel(); //true
        
        //각 작업을 쓰레드를 이용해 병렬 처리
        boolean isMany = parallelStream
            .map(n -> n * 10)
            .anyMattch(num -> num > 20);
            
        //배열을 이용하여 병렬 스트림 생성
        int[] arr = {1, 2, 3};
        Arrays.stream(arr).parallel();
        
        //기본 타입 병렬스트림
        IntStream intStream = IntStream.range(1, 10).parallel();
        
        //기존의 sequential 모드로 전환 -> sequential() 사용
        IntStream seqStream = intStream.sequential();
        
    }
}

 

무조건 병렬 스트림을 쓰는 것이 좋은 것이 아니기 때문에 parallel 모드와 sequential 모드를 적재적소에 쓰는 연습이 필요하다.

728x90