1. Stream
Stream은 Java 8부터 추가된 Collections의 저장 요소를 참조하여 처리할 수 있는 반복자를 말합니다. API 설명을 직역하면 Colletion의 map-reduce(?) 변환과 같이 함수 스타일(람다식을 의미하겠죠?) 작업을 지원하는 클래스라고 적혀있습니다.
한 줄로 설명하면 Collection에 저장되어 있는 element들을 순회하면서 처리할 수 있는 코드패턴이라고 보면 되겠네요. 코드로 한번 살펴보겠습니다.
2. 사용법
2.1 Stream 객체 생성
List<String> list = Arrays.asList("a","b","c");
Stream <String> stream = list.stream();
Stream을 사용하려면 Stream 객체를 생성해야합니다. Collection 객체들은 stream() 메서드를 지원하며, 위의 예시는 List를 호출하여 Stream 객체로 만든다는 의미가 됩니다.
2.2 Stream - Array
String[] array = new String[]{"a", "b", "c"};
Stream<String> stream1 = Arrays.stream(array); // a, b, c
Stream<String> stream2 = Arrays.stream(array, 1, 3); // b, c
Arrays.stream()의 패러미터에 배열인 array를 넣게되면 배열을 순회하는 stream 객체를 만들 수 있습니다.
패러미터로 배열, 시작, 종료 index를 준다면 배열 일부를 순회하는 stream 객체를 만들 수도 있습니다.
2.3 Stream - Builder
String<Strig> buidler = Stream<String>builder()
.add("A")
.add("B")
.add("C")
.build();
builder를 통해 만든 stream 객체에 "A","B","C" 순서로 문자열의 데이터를 처리가 가능합니다.
2.4 Stream - Generator
Stream<String> gen = Stream.generate(() -> "this generator");
Stream<String> gen = Stream.generate(() -> "this generator").limit(3);
첫 줄의 코드는 generate() 메서드를 통해 this generator를 무한하게 생성하는 stream을 만들어냅니다.
두 번째 줄의 코드에서 limit 메서드를 통해 반복 횟수를 정하여 3번만 반복하도록 제한을 할 수도 있습니다.
2.4 Stream - 기본 타입 생성
JDK 1.5 이상에서는 Primitive(기본) Type을 객체 타입의 데이터에 할당하면 *오토박싱이 발생합니다.
*Boxing은 Primitive type인 byte, short, int, long, float 등.. 에서 Wrapper type인 Byte, Short, Integer, Long 로 바뀌는 것을 의미합니다. (Unboxing은 Wrapper type -> Primitive type)
이러한 다른 타입간의 형변환은 오버헤드를 일으켜 애플리케이션의 성능에 영향을 줄 수 밖에 없는데, Stream에서는 이 오토박싱이 일어나지 않도록 성능을 개선한 API를 제공합니다.
IntStream intstream = IntStream.range(1,10);
LongStream longstream = LngStream.range(1,1000);
Int, Long, DoubleStream의 range메서드는 Primitive type을 취급하도록 강제하여 오토박싱이 일어나지 않게됩니다.
Stream<Integer> stream = IntStream.range(1,10).boxed();
LongStream stream = new Random().Long(5); // Long형 난수 5가지 생성
기본형 타입이 아닌 제네릭을 이용한 클래스를 사용하기를 원한다면 .boxed를 사용할 수 있고, Random() 클래스를 사용해 Stream으로 뽑아낼 수도 있습니다.
2.5 Stream - 문자열
IntStream stream = "This is Stream".char();
Stream<String> st = Pattern.complie(",").splitAsStream("Apple","Banana","Cherry");
// [Apple, Banana, Cherry]
.char는 문자열을 구성하고 있는 문자들의 ASCII 코드 값을 Stream 형태로 뽑아줍니다.
또한 splitAsStream으로 문자열의 split처럼 구분할 수 있습니다.
Stream<String> st1 = Stream.of("Korea", "GANA");
Stream<String> st2 = Stream.of("Porutugal","Uruguay");
Stream<String> st3 = Stream.concat(st1, st2); // "Korea", "GANA", "Porutugal", "Uruguay"
concat() 메서드는 인자로 받은 Stream을 연결하여 새로운 Stream을 만들어냅니다.
3. 람다식을 활용한 Stream 데이터 처리
3.1 Filter
Filter는 stream 객체에서 나오는 데이터 중 특정한 데이터를 선별하는 역할을 합니다.
boolean 값을 return하는 람다식을 넘겨 데이터에 대해 람다식을 적용하여 true를 반환하는 데이터를 선별하게 됩니다.
Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(v -> ((v % 2) == 0))
.forEach(System.out::println);
// 2, 4, 6, 8
첫 줄의 stream은 1부터 9까지의 Integer를 취급합니다. .filter() 메서드에 2로 나누어 떨어지는 조건을 삽입한 뒤, forEach를 이용해 stream에서 필터링한 값을 출력을 해본다면 짝수만을 뽑은 stream 객체를 return할 수 있습니다.
3.2 Map
Map은 stream의 데이터를 변경을 가해주는 메서드로, stream으로 생성된 데이터에 map메서드의 인자로 받은 람다식을 적용하여 데이터를 변경합니다.
Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(v -> ((v % 2) == 0))
.map(v -> v * 10)
.forEach(System.out::println);
// 20, 40, 60, 80
위의 예제에서 한 가지 추가하였는데, 짝수를 선별한 뒤 그 짝수에 10을 곱하여 출력한 경우입니다.
3.3 Sorted
Sorted는 stream 데이터를 정렬할 때 사용하며, 패러미터가 없다면 기본으로 오름차순으로 정렬합니다. 이 때 두 값을 비교하는 경우에는 comparator를 sorted()의 인자로 사용할 수 있습니다.
저번 프리코스 미션 중 lotto 미션에서 사용한 sorted입니다. 1부터 45까지의 6자리 난수를 정렬하기위해 사용했습니다.
3.4 Peek
peek은 stream 내의 element를 대상으로 map 연산을 수행합니다. 이 때, 새로운 stream을 생성하지 않고 인자로 받은 람다식을 적용하게 됩니다.
어찌보면 foreach()와 동일한 것 같지만, peek() 같은 경우 중간 처리 메서드로 중간 연산의 수행 결과를 디버깅하는 수단이고, forEach()는 최종 처리 메서드라는 것입니다. 무엇을 의미하느냐 peek()는 최종 처리 메서드가 없으면 동작하지 않는 stream이라는 것입니다.
public static void main(String[] args){
int[] intArr = {1, 2, 3, 4, 5};
// 최종처리 메소드가 없으면 동작 X
Arrays.stream(intArr)
.filter(a -> a%2 == 0)
.peek(System.out::println); // peek은 중간 처리 메소드(intermediate)
// 최종처리 메소드(Terminal) sum()이 존재, 정상 작동
Arrays.stream(intArr)
.filter(a -> a%2 == 0)
.peek(System.out::println)
.sum();
// forEach는 최종처리 메소드(Terminal)로 정상 동작
Arrays.stream(intArr)
.filter(a -> a%2 == 0)
.forEach(System.out::println);
}
참조
'Skills > Java' 카테고리의 다른 글
Java - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2022.12.05 |
---|---|
Java - Stream 결과 생성 (0) | 2022.11.30 |
Java - FIrst Class Collection(일급 컬렉션) (0) | 2022.11.17 |
Java - NumberFormatException 에러 (0) | 2022.11.14 |
Java - 간단한 예외처리 테스트 (0) | 2022.11.12 |