이번 Chapter에서는 스트림에 대해 정리 해보자.
스트림을 사용하는 이유와 배경
자바 8
에서 추가
한 스트림
(Streams)은 람다
를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열
또는 컬렉션 인스턴스
를 다루는 방법은 for
또는 foreach
문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었습니다. 간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 여러 로직이 섞이게 되고, 메소드를 나눌 경우 루프를 여러 번 도는 경우가 발생합니다.
스트림
은 '데이터의 흐름'
입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.
// 컬렉션 인스턴스에 아래와 같은 데이터가 저장되 있는 상태.
-------------
| 1, 3, 5 |
| 6, 7, 8 |
-------------
List<String> list = new ArrayList(Arrays.asList("1", "3", "5"...));
컬렉션 인스턴스
에 저장된 데이터 중에서, 홀수 데이터의 합
을 계산 해보고 싶다.스트림을 사용 한다.
// 데이터를 일일이 하나씩 꺼내서, 해당 홀수 값을 더해 총 합을 구한다.
// 컬렉션 인스턴스에 저장된 데이터 중에서 홀수들의 합을 구한다.
List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 5, 6, 7, 9));
for(int i = 0; i < list.size(); i++) {
if(list.get(i) % 2 == 1)
System.out.println("list : " + list.get(i)); // 홀수 값 추출
}
위 코드는 우리가 흔히 쓰는 코드이며, 이상한 부분은 없다. 하지만 아래 내용에 대해 생각 해보자.
성능
면에서 위 같은 코드가 우수한가??// 아래 작업을 하나의 파이프라 생각한 그림.
// 아래와 같은 파이프가 존재하는 상태.
// 1번째 파이프 (작업1 파이프)
--------------------------
--> | --> 1, 5, 7 // 홀수만 걸러서 추출.
--------------------------
// 2번째 파이프 (작업2 파이프)
--------------------------
--> ● --> 1, 5, 7 = 13 // 위에서 걸러진 데이터의 합을 구한다.
--------------------------
List<String> list = new ArrayList<>(Arrays.asList("1", "3", "5"...));
홀수 값
을 추출하기 위해 한번의 작업(작업1
)이 필요하다.홀수 값
들을 더하기 위해 한번의 작업(작업2
)이 더 필요하다.파이프
는 실제로 메서드
를 의미한다.스트림은 인스턴스라 봐도 무방하다.
// before
public static void main(String[] args) {
int [] ar = {1, 2, 3, 4, 5};
IntStream stm1 = Arrays.stream(ar); // 배열 ar로부터 스트림 생성
IntStream stm2 = stm1.filter(n -> n % 2 == 1); // 중간 연산 진행
int sum = stm2.sum(); // 최종 연산 진행
System.out.println(sum);
}
// after
public static void main(String[] args) {
int [] ar = {1, 2, 3, 4, 5};
// 스트림 역시 인스턴스이기에 아래와 같이 연속으로 호출이 가능하다.
int sum = Arrays.stream(ar)
.fiter(n -> n % 2 == 1)
.sum(); // sum 통과 결과 반환.
System.out.println(sum);
}
stream
: 스트림 생성 (배열을 대상으로 스트림이 생성된다.)filter
: 중간 연산 (생성된 스트림에 파이프가 연결된다.)sum
: 최종 연산9
스트림 관련 전체 내용의 구분
스트림의 생성 방법
중간 연산의 종류와 내용
최종 연산의 종류와 내용
public static void main(String[] args) {
String [] name = {"YOON", "LEE", "PARK"};
Arrays.stream(name).forEach(e -> System.out.println(e));
}
중간 연산
은 생략
이 되어도 상관이 없지만, 최종 연산
은 생략
이 될 수 없다.Arrays.stream()
을 선언하면 된다.// 컬렉션 인스턴스를 대상으로 스트림 생성 시 호출하는 메소드
default Stream<E> stream()
// java.util.Collection<E>의 디폴트 메소드
public static void main(String[] args) {
List<String> list = Arrays.asList("Toy", "Robot", "Box");
list.stream().forEach(s -> System.out.println(s));
}
중간 연산자
)Stream<T> filter(Predicate<? super T> predicate) // Stream<T>에 존재
// Predicate<T> boolean test(T t)
public static void main(String[] args) {
int [] ar = {1, 2, 3, 4, 5};
Arrays.stream(ar)
.filter(n -> n % 2 == 1) // 홀수만 통과시킨다.
.forEach(n -> System.out.println(n + "\t"));
System.out.println();
List<String> s1 = Arrays.asList("Toy", "Robot", "Box");
s1.stream() // 컬렉션 인스턴스 기반 스트림 생성
.filter(s -> s.length() == 3) // 길이가 3이면 통과시킨다.
.forEach(s -> System.out.println(s + "\t"));
System.out.println();
}
filter() 메서드
를 통해 구현.필터링
을 한다 생각하면 편하다.뜰채
를 사용하여 내가 원하는 데이터만 추출.1 3 5
Toy Box
map
은 요소들을 특정 조건에 해당하는 값으로 변환해 줍니다.각각의 아이템
을 변경
하여 새로운 컨텐츠
를 생성
하는 기능을 의미 한다.// 아래와 같은 컬렉션 인스턴스 존재
---------------
| Box, Robot | ---> 3, 5, 6
| ,Simple |
---------------
// 예제 01
public static void main(String[] args) {
List<String> ls = Arrays.asList("Box", "Robot", "Simple");
ls.stream()
.map(s -> s.length())
.forEach(n -> System.out.println(n + "\t"));
System.out.println();
}
// map 메서드 형태
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
// Function<T, R> R apply(T t
3 5 6
아래의 메서드를 통해 불필요한 Boxing, Unboxing을 피할수 있다.
mapToInt
(ToIntFunction<? super T> mapper)mapToLong
(ToLongFunction<? super T> mapper)mapToDouble
(ToDoubleFunction<? super T> mapper)public static void main(String[] args) {
List<String> ls = Arrays.asList("Box", "Robot" ,"Simple");
ls.stream()
.mapToInt(s -> s.length())
.forEach(n -> System.out.println(n + "\t"));
System.out.println();
}
3 5 6
class ToyPriceInfo {
private String model;
private int price;
public ToyPriceInfo(String m, int p) {
this.model = m;
this.price = p;
}
public int getPrice() {
return price;
}
}
public static void main(String[] args) {
List<ToyPriceInfo> ls = new Arraylist<>();
ls.add(new ToyPriceInfo("GUN_LR_45", 200));
ls.add(new ToyPriceInfo("TEDDY_BEAR", 350));
ls.add(new ToyPriceInfo("CAR_TRANSFORM_VER_7719", 550));
int sum = ls.stream()
.filter(p -> p.getPrice() < 500)
.mapToInt(t -> t.getPrice()) // 맵핑 : 솔직히 맵핑은 왜 하는지 정리가 아직 안됬음.
.sum(); // 최종 연산
System.out.println("sum : " + sum);
}
데이터
를 변환하지 않고
, 더하거나 빼는
등의 연산
을 수행
하여 하나의 값을 만들 때 reduce 메서드
가 사용이 된다.// 리덕션 파이프(기능 : sum)
------------------
● <- 1, 2, 3
------------------
// reduce 메서드
T reduce(T identify, BinaryOperator<T> accumulator) // Stream<T>에 존재
// Binaryoperator<T> T apply(T t1, T t2)
// 리덕션 예제
public static void main(String[] args) {
List<String> ls
= Arrays.asList("Box", "Simple", "Complex", "Robot");
// BinaryOperator 메서드
BinaryOperator<String> lc = (s1, s2) -> {
if(s1.length() > s2.length()) {
return s1;
} else {
return s2;
}
};
String str = ls.stream()
.reduce("", lc); // 스트림 빈 경우 "" 반환
System.out.println(str);
}
reduce()
의 첫번째 매개변수 인자로는
stream 데이터가 null일 경우 지정할 데이터를 지정 한다.
.reduce("") → "" 역시 데이터로 바라본다.
.reduce("1234567") → 1234567도 스트림의 0번째로 붙는다는 사실을 인지하고 해야 한다.
// 아래와 같이 스트림에 존재하는 데이터와 비교를 하게 된다.
// if(s1.length() > s2.length()) return 와 같은 상황에서.
----------------------------------------
| 1234567, Box, Simple, Complex, Robot |
----------------------------------------
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum : " + sum));
병렬 처리
// 병렬 처리 예제
|----------------------------|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
public static void main(String[] args) [
List<String> ls = Arrays.asList("Box", "Complex", "Robot");
BinaryOperator<String> lc = (s1, s2) -> {
if(s1.length() > s2.length())
return s1;
else
return s2;
};
String str = ls.parallelStream() // 병렬 처리를 위한 스트림 생성
.reduce("", lc);
System.out.println(str);
}
parallelStream()
메서드 호출 시 병렬 처리
를 통해 스트림 데이터를 처리 한다.Complex