Java Stream (1) : 스트림 생성하기

이민호·2024년 5월 9일
0

이번 포스트는 자바 스트림 : stream() 에 대해서 적어볼까 한다.

자바 스트림은 자바 8부터 추가된, 함수형 프로그래밍에 자바가 적응하면서 생긴 결과물이라고 할 수 있다. 함수형 프로그래밍 기능의 일부인 람다식을 이용하여 컬렉션과 같은 데이터들을 순서대로 흐름을 만들어 원하는 결과물을 간단한 구문으로 표현하는 문법이라고 할 수 있겠다.

스트림에 대한 내용은 크게 세 가지.

1. 생성하기 : 스트림 인스턴스 생성.
2. 가공하기 : 필터링 및 맵핑 등 데이터를 가공해가는 중간 작업(intermediate operations).
3. 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).

생성하기

스트림을 생성하는 것은 생각보다 간단하다. 깊게 들어간다면 여러가지가 있겠지만, 간단하게 객체 뒤에 .stream() 만 붙여줘도 생성할 수 있다.

배열

String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
Stream<String> streamOfArrayPart = 
 	Arrays.stream(arr, 1, 3); // 1~2 요소 [b, c]

컬렉션 : 리스트

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();

비어있는 스트림

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

스트림을 사용할때, null값을 대체하기 위해 사용한다고 한다.

Stream.builder()

.builder()를 사용하면 스트림에 직접 원하는 값을 할당할 수 있다. 마지막에 .build() 메소드로 만든 스트림을 리턴할 수 있다.

Stream<String> builderStream = 
  Stream.<String>builder()
    .add("Eric").add("Elena").add("Java")
    .build(); // [Eric, Elena, Java]

Stream.generate()

.generate() 메소드를 이용하면 Supplier 에 해당하는 람다로 값을 넣을 수 있다.

  • Supplier : 인자는 없고 리턴값만 있는 함수형 인터페이스
public static<T> Stream<T> generate(Supplier<T> s) { ... }
  
//example
Stream<String> generatedStream = 
  Stream.generate(() -> "gen").limit(5); // [el, el, el, el, el]
  // 스트림의 크기를 제한하는 .limit

Stream.iterate()

.iterate() 메소드를 이용하면 초기값과 람다를 이용해 스트림에 요소를 반복적으로 할당할 수 있다.

Stream<Integer> iteratedStream = 
  Stream.iterate(30, n -> n + 2).limit(5); // 30부터 2씩 증가하는 값 5개를 할당.
  // [30, 32, 34, 36, 38]
  // 스트림의 크기를 제한하는 .limit

기본 타입형 스트림

제네릭을 사용하면 리스트나 배열을 이용해서 기본 타입(int, long, double) 스트림을 생성할 수 있다.

IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]

제네릭을 사용하지 않기 때문에 불필요한 오토박싱(auto-boxing)이 일어나지 않습니다. 필요한 경우 boxed 메소드를 이용해서 박싱(boxing)할 수 있습니다.

Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();

Java 8 의 Random 클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream, LongStream, DoubleStream)을 생성할 수 있다. 쉽게 난수 스트림을 생성해서 여러가지 후속 작업을 취할 수 있어 유용하다고 한다.

DoubleStream doubles = new Random().doubles(3); // 난수 3개 생성

문자열 스트링

스트링을 이용해서 스트림을 생성할수도 있습니다. 다음은 스트링의 각 문자(char)를 IntStream 으로 변환한 예제입니다. char 는 문자이지만 본질적으로는 숫자이기 때문에 가능합니다.

IntStream charsStream = 
  "Stream".chars(); // [83, 116, 114, 101, 97, 109]

다음은 정규표현식(RegEx)을 이용해서 문자열을 자르고, 각 요소들로 스트림을 만든 예제입니다.

Stream<String> stringStream = 
  Pattern.compile(", ").splitAsStream("Eric, Elena, Java");
  // [Eric, Elena, Java]

파일 스트림

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

Stream<String> lineStream = 
  Files.lines(Paths.get("file.txt"), 
              Charset.forName("UTF-8"));

병렬 스트림 Parallel Stream

스트림 생성 시 사용하는 stream 대신 parallelStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다. 내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join framework 를 사용합니다.

// 병렬 스트림 생성
Stream<Product> parallelStream = productList.parallelStream();

// 병렬 여부 확인
boolean isParallel = parallelStream.isParallel();

따라서 다음 코드는 각 작업을 쓰레드를 이용해 병렬 처리됩니다.

boolean isMany = parallelStream
  .map(product -> product.getAmount() * 10)
  .anyMatch(amount -> amount > 200);
다음은 배열을 이용해서 병렬 스트림을 생성하는 경우입니다.

```java
Arrays.stream(arr).parallel();

컬렉션과 배열이 아닌 경우는 다음과 같이 parallel 메소드를 이용해서 처리합니다.

IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();

다시 시퀀셜(sequential) 모드로 돌리고 싶다면 다음처럼 sequential 메소드를 사용합니다. 뒤에서 한번 더 다루겠지만 반드시 병렬 스트림이 좋은 것은 아닙니다.

IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();

스트림 연결하기

Stream.concat 메소드를 이용해 두 개의 스트림을 연결해서 새로운 스트림을 만들어낼 수 있습니다.

Stream<String> stream1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream2 = Stream.of("Python", "Go", "Swift");
Stream<String> concat = Stream.concat(stream1, stream2);
// [Java, Scala, Groovy, Python, Go, Swift]

참고 포스트.

profile
둘뺌

0개의 댓글