본문 바로가기

IT/개발공부

java stream에 대해 알아보자

반응형

 

 

 

안녕하세요.

열정열정열정 개발자입니다.

 

 

제가 프로그래머스에서 코딩테스트 연습을 하다보니 stream을 많이 볼 수 있었습니다.

그래서 이번엔 stream이 뭔지 어떻게 사용하는 건지 등등 알아보려 합니다!

 

 

 

stream이란 무엇인가

java stream은 java8부터 도입된 기능입니다.

컬렉션을 처리하고 다양한 연산을 수행하기 위한 기능을 제공하는 API입니다.

stream은 데이터 요소의 연속적인 흐름을 나타내며, 컬렉션의 요소를 필터링, 변환, 집계 등 다양한 작업을 위해 사용됩니다.

기존에는 for, foreach와 같은 루프문을 사용하면서 복잡한 코드가 만들어 지기도 했는데, stream을 이용하여 선언형으로 더욱 쉽게 처리할 수 있다는 장점이 있습니다. 또한 병렬처리를 별도의 멀티스레드 구현 없이도 쉽게 구현할 수 있습니다.

 

 

 

stream 사용 안할때,

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
		
for(String a : list) {
	if(a.equals("c")) {
		System.out.println("c");
	}
}

 

 

stream 사용 할 때,

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
		
list.stream().filter("c"::equals).forEach(System.out::println);

이와 같이,  stream을 사용하면 1줄로도 표현이 가능합니다.

 

 

 

 

 

Stream 사용하는 방법!

  1. stream 생성
  2. stream API 함수로 데이터 처리연산

 

 

1. Stream 생성방법

1) 컬렉션

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

 

 

2) 배열

String[] array = new String[]{"a", "b", "c"};
Stream<String> st1 = Arrays.stream(array);
Stream<String> st2 = Arrays.stream(array, 1, 3);	//인덱스 1포함, 3제외

 

 

3) 배열이나 컬렉션을 통해 생성하는 것이 아닌, 직접 스트림 객체 생성

Stream<String> stream = Stream<String>builder().add("a").add("b").add("c").build();

 

 

 

4) 람다식으로 스트림 생성

Stream<String> stream = Stream.generate(() -> "A").limit(5);

generate() 메소드의 인자로 "A"를 찍어주는 람다식을 주고 "A" 문자열을 5개만 찍어내도록 하였습니다.

 

 

 

5) iterate() 메소드를 이용하여 수열 형태 생성

Stream<String> stream = Stream.iterate(100, n -> n + 10).limit(5);

람다를 인자로 넘겨 초기 값 100부터 10씩 증가하도록 하였습니다.

 

 

 

6) 문자열 스트림 생성

IntStream stream = "Hello".chars();

문자열을 구성하고 있는 문자들의 ASCII 코드 값을 스트림 형태로 뽑아줍니다.

 

 

 

7) 두개의 스트림 붙여서 생성하기

Stream<String> stream1 = Stream.of("Apple", "Banana", "Melon");
Stream<String> stream2 = Stream.of("one", "two", "three");

Stream<String> stream3 = Stream.concat(stream1, stream2);
// Apple, Banana, Melon, one, two, three 출력

 

 

 

2. 데이터 처리 방법!

java.util.stream.Stream에는 스트림 API에서 제공하는 여러가지 연산이 정의 되어있습니다.

크게 중간연산, 최종연산으로 구분됩니다.

 

중간연산

1) Filter

: 특정 데이터 골라내기

Stream<T> filter(Rredicate(? super T> predicate);

//예)
Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(v -> ((v%2) == 0)).forEach(System.out::println);
//2, 4, 6, 8 출력

filter() 메소드에는 boolean 값을 리턴하는 람다식을 넘겨주게 됩니다.

그러면 뽑아져 나오는 데이터에 대해 람다식을 적용해서 true가 리턴되는 데이터만 나오게 됩니다.

 

 

2) Map

: 데이터 변경

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

//예)
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 출력

map() 메소드는 값을 변환해주는 람다식을 인자로 받습니다. 

데이터에 map() 메소드의 인자로 받은 람다식을 적용해 새로운 데이터를 만들어 냅니다.

 

 

3) flatMap

map() 메소드와 비슷

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

//예)
List<List<String>> list = Arrays.asList(Arrays.asList("A", "B", "C"), Arrays.asList("a", "b", "c");
List<String> flatList = list.stream().flatMap(Collection::stream).collect(Collectors.toList());
//["A", "B", "C", "a", "b", "c"] 출력

 

 

4) Sorted

정렬

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

오름차 순으로 정렬됩니다.

 

 

 

5) Peek

스트림 내 엘리먼트들을 대상으로 map() 메소드처럼 연산 수행

Stream<T> peek(Consumer<? super T> action);

//예)
int sum = IntStream.range(1, 10).peek(System.out::println).sum();

Peek() 메소드는 새로운 스트림을 생성하지는 않고 그냥 인자로 받은 람다를 적용하기만 합니다. 

중간에 로깅 같은 것을 하고자 할 때, peek() 메소드를 사용해주면 좋습니다.

 

 

 

 

최종연산

1) 통계 값

종합, 최대값, 최소값, 숫자의 개수, 평균 값 등의 대한 계산

int sum = IntStream.range(1, 10).sum();
int count = IntStream.range(1, 10).count;
int max = IntStream.range(1, 10).max();
int min = IntStream.range(1, 10).min();
int avg = = IntStream.range(1, 10).average();
int evenSum = IntStream.range(1, 10).flter(v -> ((v%2) == 0).sum();

만약 비어있는 스트림이라면 count()와 sum() 메소드는 0을 리턴합니다.

최대, 최소 값이 경우 Optional을 이용해 리턴합니다.

 

 

 

2) Reduce

결과값 산출

Optional<T> reduce(BinaryOperator<T> accomulator);	// 스트림에서 나오는 값들을 accumulator 함수로 누적
T reduce (T identity, BinaryOperator<T> accumulator);	// 동일하게 accumulator 함수로 누적하지만 초기값(identity)이 있다

reduce() 메소드는 파라미터에 따라 3가지 종류가 있습니다.

우선 스트림에서 뽑아져 나오는 값들을 누적시키는 accumulator 함수는 2개의 파라미터를 인자로 받아 하나의 값을 리턴하는 함수형 인터페이스입니다.

중간에 flter()와 같은 메소드를 거치면서 값이 하나도 나오지 않는 경우가 있기 때문에 reduce() 메소드의 리턴값은 Optional입니다. 만약 초기값을 주는 reduce() 메소드를 사용한다면, 초기값이 있기 때문에 Optional()이 아닌 것을 확인할 수 있습니다. 

뽑아져 나오는 값은 초기값에 accumulator 함수가 적용됩니다.

 

 

 

3) Collect

컬렉션의 엘리먼트 중 일부를 필터링하고, 값을 변형해서 또 다른 컬렉션으로 만듦

Set<Integer> evenNumber = IntStream.range(1, 100).boxed().filter(n -> (n%2 == 0)).collect(Collectors.toSet());

List<String> fruit = Arrays.asList("A", "B", "C"_;
String returnValue = fruit.stream().collect(Collectors.joining(",", "<", ">"));
System.out.println(returnValue);
//<A,V,C> 출력

collect() 메소드를 이용해 뽑아져 나오는 데이터들을 컬렉션으로 모아둘 수 있습니다.

또, Collector 클래스에 있는 정적 메소드를 이용해서 뽑아져 나오는 객체들을 원하는 컬렉션으로 만들 수 있습니다.

Collector.toList()를 호출하면 리스트로 만들고, Collector.toSet()을 호출하면 Set으로 만들어줍니다.

그리고 Collector.joining()을 사용하면 작업한 결과를 하나의 문자열로 이어 붙일 수 있습니다. (첫번째 인자는 구분자, 두번째 인자는 문자열의 맨 처음, 세번재 인자는 문자열의 마지막)

 

 

 

4) foreach

값에 대한 작업

Set<Integer> evenNumber = IntStream.range(1, 1000).boxed().filter(n -> (n%2 == 0)).forEach(System.out::println);
//1~999까지 숫자 중 짝수만 뽑아내서 출력

foreach 메소드는 값을 리턴하지 않는 메소드입니다.

 

 

 

 

 

 

 

[참조 사이트]

https://ksr930.tistory.com/237

https://hbase.tistory.com/171

 

반응형