스트림(Stream)이란?
자료의 연산을 위해서 사용하는 객체다.
입출력 스트림(java.io의 Stream)과는 다르다.
자료의 대상과 관계없이 동일한 연산을 수행할 수 있는 기능이다.
배열, 컬렉션에 동일한 연산이 수행되어 일관성 있는 처리 기능이며 한번 생성하고 사용한 스트림은
재사용할 수 없다.
스트림 연산은 기존자료를 변경하지 않고 중간연산과 최종연산으로 구분된다.
최종연산이 수행되어야 모든 연산이 적용되는 지연연산이다.
스트림은 자바 8부터 사용할 수 있다.
중간연산
중간연산에는 filter 와 map, sorted가 있다.
map은 요소들을 특정조건에 해당하는 값으로 변환해준다. 요소들을 대, 소문자 변형등의 작업을 하고 싶을 때
사용 가능하다.
filter는 요소들을 조건에 따라 걸러내는 작업을 해준다. 길이의 제한, 특정문자 포함등의 작업을 하고 싶을 때
사용 가능하다.
sorted는 요소들을 정렬해주는 작업을 해준다. 요소들의 가공이 끝났다면 리턴해줄 결과를 collect를 통해 만든다.
filter는 조건에 맞는 요소를 추출, map은 요소를 변환한다.
문자열의 길이가 5이상인 요소만 출력한다고 하면
sList.stream().filter(s->s.length() >= 5).forEach(s->System.out.println(s));
이렇게 작성한다.
여기서 sList.stream()이 스트림을 생성하는 부분이고 filter(s->s.length() >= 5) 이부분은 중간연산,
forEach(s->System.out.println(s)); 이부분이 최종연산이 된다.
하나 더 고객 클래스에서 고객이름만 가져온다고 하면
customerList.stream().map(c->c.getName()).forEach(s->System.out.println(s));
이렇게 작성하며 customerList.stream()이 스트림 생성하는 부분, map(c->c.getName()) 여기가 중간연산,
forEach(s->System.out.println(s)); 여기가 최종연산이 된다.
최종연산
스트림의 자료를 소모하면서 연산을 수행한다.
최종연산 후에 스트림은 더 이상 다른 연산을 적용할 수 없다.
forEach() : 요소를 하나씩 꺼내온다. (반복문과 비슷)
count() : 요소의 개수
sum() : 요소의 합
이외에도 여러가지 최종연산이 있다.
reduce()연산
정의된 연산이 아닌 프로그래머가 직접 지정하는 연산을 적용한다.
최종연산으로 스트림의 요소를 소모하며 연산을 수행한다.
배열의 모든 요소의 합을 구하는 reduce() 연산을
Arrays.stream(arr).reduce(0, (a,b) -> a + b);
이렇게 작성했다고 할 때 여기서 0은 초기값이 되며 (a, b)는 전달되는 요소가 된다.
(a, b) -> a + b는 각 요소가 수행해야 할 기능이다.
두번째 요소로 전달되는 람다식에 따라 다양한 기능을 수행하게 된다.
이 방법이 복잡하다면 binary operator를 구현한 클래스를 사용하는 방법도 있다.
예제코드
import java.util.*;
public class ArrayListStreamTest {
public static void main(String[] args) {
List<String> sList = new ArrayList<String>();
sList.add("Tomas");
sList.add("Edward");
sList.add("Jack");
Stream<String> stream = sList.stream();
stream.forEach(s-> System.out.println(s + " "));
System.out.println();
sList.stream().sorted().forEach(s-> System.out.println(s + " "));
System.out.println();
sList.stream().map(s->s.length()).forEach(n-> System.out.println(n));
}
}
결과값은
Tomas Edward Jack
Edward Jack Tomas
5
6
4
이렇게 출력된다.
스트림을 한번 생성했다고 해서 재사용을 할 수 없다고 했었다.
이 예제에서도 보면 stream이라는 이름으로 생성해주었지만 그 아래 코드에서는 계속
s.List.stream()으로 다시 생성하는것을 볼 수 있다.
막상 코드를 stream().sorted().forEach(s-> System.out.println(s + " ")); 이렇게 작성해도 IDE 상에서는 딱히 오류표시를
해주지 않지만 실행해보면 IllegalStateException이 발생한다.
그 옆으로는 stream has already been operated upon or closed 라고 나오는데
스트림이 이미 작동중이거나 닫혔다는 뜻이다.
재사용이 안되는데 사용하려고 했기 때문에 발생하는 오류인것이다.
그리고 sorted는 String에서 comparable이 구현되어 있기 때문에 별 다른 구현 없이 사용했지만 만약 String클래스가
아닌 직접 만든 Member 클래스였다면 직접 구현했어야 한다.
마지막 코드는 map으로 이름을 가져오고 각 데이터의 길이를 구한다. forEach에서는 map에서의 그 길이값을
매개변수로 받아 출력하게 된다.
그래서 5 6 4 의 값을 출력한것이다.
예제코드 2
import java.util.Arrays;
public class IntArrayList {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int sum = Arrays.stream(arr).sum();
int count = (int)Arrays.stream(arr).count();
System.out.println(sum);
System.out.println(count);
System.out.println(Arrays.stream(arr).reduce(0, (a,b)->a+b));
결과값은
15
5
15
가 출력되게 된다.
sum은 스트림을 생성하고 .sum()을 붙여주면 배열에 있는 값들을 모두 더해주게 된다.
.count역시 생성된 스트림의 개수를 세주기 때문에 5가 출력되는데 count는 long 타입이므로 (int)를 붙여
변환해준것이다.
마지막 출력문의 경우는 스트림을 생성하고 reduce를 이용해 초기값을 0으로 설정하고
operator를 a와 b를 받아 더하도록 했으므로 배열 내의 모든 값을 순차적으로 더한 값을 출력하게 된다.
예제코드 3
import java.util.Arrays;
import java.util.function.BinaryOperator;
class CompareString implements BinaryOperator<String> {
@Override
public String apply(String s1, String s2) {
if(s1.getBytes().length >= s2.getBytes().length)
return s1;
else
return s2;
}
}
public class ReduceTest {
public static void main(String[] args) {
String[] greetings = {"hi", "hello", "Good Morning", "bye"};
System.out.println(Arrays.stream(greetings).reduce("", (s1, s2)->
{
if(s1.getBytes().length >= s2.getBytes().length)
return s1;
else
return s2;
}));
System.out.println();
System.out.println(Arrays.stream(greetings).reduce(new CompareString()).get());
}
}
결과값은
Good Morning
Good Morning
이렇게 출력된다.
똑같은 처리를 했기 때문에 같은 결과값이 출력이 되는데
reduce는 두번째 요소로 전달되는 람다식에 따라 다양한 기능을 수행한다고 했었다.
첫 출력문이 람다식으로 직접 작성해서 처리하는 방법이고
이게 복잡하면 binary operator를 구현한 클래스를 사용해야 한다고 했는데 그게 두번째 출력문이다.
CompareString이라는 클래스가 binary operator를 구현한 클래스이므로 호출해서 처리한다.
만약 이 예제처럼 그냥 한번 구현하고 이러한 기능을 다시 사용할 일이 없다면 그냥 첫 출력문에 작성한
람다식을 사용한 방법처럼 쓰는게 좋겠지만 만약 여러번 같은 처리를 해야 하는 코드라면
binary operator를 받은 클래스를 구현해서 짧게 사용할 수 있으므로 좀 더 편하다.
import java.util.Arrays;
public class ReduceTest {
public static void main(String[] args) {
String[] greetings = {"hi", "hello", "Good Morning", "bye"};
System.out.println(Arrays.stream(greetings).reduce("", (s1, s2)->
{
if(s1.getBytes().length >= s2.getBytes().length)
return s1;
else
return s2;
}));
System.out.println();
String[] gree = {"a", "ab", "abc", "abcd"};
System.out.println(Arrays.stream(gree).reduce("", (s1, s2)->
{
if(s1.getBytes().length >= s2.getBytes().length)
return s1;
else
return s2;
}));
}
}
이렇게 사용하는것 보다는
import java.util.Arrays;
import java.util.function.BinaryOperator;
class CompareString implements BinaryOperator<String> {
@Override
public String apply(String s1, String s2) {
if(s1.getBytes().length >= s2.getBytes().length)
return s1;
else
return s2;
}
}
public class ReduceTest {
public static void main(String[] args) {
String[] greetings = {"hi", "hello", "Good Morning", "bye"};
System.out.println(Arrays.stream(greetings).reduce(new CompareString()).get());
System.out.println();
String[] gree = {"a", "ab", "abc", "abcd"};
System.out.println(Arrays.stream(gree).reduce(new CompareString()).get());
}
}
이렇게 클래스를 재사용을 하며 작성하는게 더 깔끔해 보인다는 말이다.
레퍼런스
패스트캠퍼스 올인원 패키지 - 자바 객체지향프로그래밍
'JAVA' 카테고리의 다른 글
쓰레드(Thread) (0) | 2021.02.13 |
---|---|
입출력스트림(IOStream) (0) | 2021.02.12 |
람다식(Lambda) (0) | 2021.02.10 |
제네릭프로그래밍(Generic Programming) (0) | 2021.02.09 |
컬렉션 프레임워크(Collection Framework) (0) | 2021.02.08 |