4_스트림 소개
스트림은 자바 8 API에 새로 추가된 기능이다. 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다.
스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
stream()을 parallelStream()으로 바꾸면 코드를 멀티코어 아키텍처에서 병렬로 실행할 수 있다.
스트림은 제어 블록 없이 선언형으로 코드를 구현할 수 있다. 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있다.
스트림은 '데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소'로 정의 가능하다.
스트림에서는 파이프라이닝 덕분에 게으름, 쇼트서킷같은 최적화를 얻을 수 있다.
데이터를 언제 계산하느냐가 컬렉션과 스트림의 가장 큰 차이다. 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조다. 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다.(컬렉션에 요소를 추가하거나 컬렉션의 요소를 삭제할 수 있다. 이런 연산을 수행할 때마다 컬렉션의 모든 요소를 메모리에 저장해야 하며 컬렉션에 추가하려는 요소는 미리 계산되어야 한다)
반면 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조다.(스트림에 요소를 추가하거나 스트림에서 요소를 제거할 수 없다) 사용자가 요청하는 값만 스트림에서 추출한다는 것이 핵심이다. 사용자 입장에서는 이러한 변화를 알 수 없다. 결과적으로 스트림은 생산자와 소비자 관계를 형성한다. 또한 스트림은 게으르게 만들어지는 컬렉션과 같다. 즉, 사용자가 데이터를 요청할 때만 값을 계산한다.
반면 컬렉션은 적극적으로 생성된다. 스트림은 무제한의 소수를 포함하는 스트림을 만들 수 있지만, 컬렉션은 불가능할 것이다.(모든 파일이 들어가 있는 영화 CD, 인터넷 스트리밍은 스트림 개념)
1번만 탐색할 수 있으며, 탐색된 스트림의 요소는 소비된다.
List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);//스트림이 소비되어서 IllegalStateException 던져짐
컬렉션 : 외부 반복, 스트림 : 내부 반복
내부 반복은 작업을 투명하게 병렬로 처리하거나 더 최적화된 다양한 순서로 처리할 수 있다. 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택한다. for-each를 이용하는 외부 반복에서는 병렬성을 스스로 관리해야 한다.
연결할 수 있는 스트림 연산을 중간 연산, 닫는 연산을 최종 연산이라고 한다. 중간 연산은 단말 연산을 실행하기 전까지는 아무 연산도 수행하지 않는다. lazy하게 동작한다. 최종 연산으로 한 번에 처리하기 때문이다.
찰리
맥북 프로가 사고싶은 찰리는 배민 커넥트를 시작하는데
찰리의 배달을 받는 조건은 다음과 같습니다.
- 배달장소가 “요기요“인 주문은 받지 않습니다.
- 거리가 2000 미만인 주문만 받습니다.
추가로 다음의 정보도 확인하려합니다.
- 전체 배달목록 중 “우테코“가 포함되는 장소에서 주문한 갯수
아래와 같이 Iterator 객체를 사용해서 배달요청을 필터링하는 코드를 만들었습니다. 하지만 다음날 제이슨의 강의에서 Stream API를 배우고 코드를 리팩토링 하기로 결심합니다.
아래의 main 메서드 안에있는 코드에 Stream API를 적용시켜주세요!
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;public class StreamEx {
public static void main(String[] args) { List<WoowahanOrder> deliveries = Arrays.asList(
new WoowahanOrder("짜장면2", "우테코 루터회관", 1500),
new WoowahanOrder("교촌허니콤보", "우테코 백엔드 강의실", 1999),
new WoowahanOrder("햄버거", "우테코 프론트엔드 강의실", 2000),
new WoowahanOrder("신호등 치킨", "요기요 본사", 525),
new WoowahanOrder("민트 초코 피자", "요기요테크코스", 1525)
); List<WoowahanOrder> pickedOrders = new ArrayList<>();
Iterator<WoowahanOrder> iterator = deliveries.iterator();
while (iterator.hasNext()) {
WoowahanOrder order = iterator.next();
if (!order.getArrivalAddress().contains("요기요") && order.getDistance() < 2000) {
pickedOrders.add(order);
}
} Iterator<WoowahanOrder> iterator2 = deliveries.iterator();
int count = 0;
while (iterator2.hasNext()) {
WoowahanOrder order = iterator2.next();
if (order.getArrivalAddress().equals("우테코")) {
count++;
}
} System.out.println(pickedOrders.toString());
System.out.println("우테코에서 요청한 주문 : " + count);
}
}class WoowahanOrder {
String food;
String arrivalAddress;
int distance; public WoowahanOrder(String food, String arrivalAddress, int distance) {
this.food = food;
this.arrivalAddress = arrivalAddress;
this.distance = distance;
} public String getFood() {
return food;
} public String getArrivalAddress() {
return arrivalAddress;
} public int getDistance() {
return distance;
} @Override
public String toString() {
return "WoowahanOrder{" +
"food='" + food + '\'' +
", arrivalAddress='" + arrivalAddress + '\'' +
'}';
}
}
답안
System.out.println(deliveries.stream()
.filter(wo -> !wo.getArrivalAddress().contains("요기요"))
.filter(wo -> wo.getDistance() < 2000)
.collect(toList())
.toString());
System.out.println("우테코에서 요청한 주문 :" + deliveries.stream()
.filter(wo -> wo.getArrivalAddress().contains("우테코"))
.count());
손너잘
손너잘은 숫자를 정렬하고자 한다. 이번 장에서 스트림을 배운 손너잘은 스트림을 이용해 이 문제를 해결해 보고자 했다.
스트림을 처음 배운 손너잘을 각 스트림의 사용법을 제대로 확인하고자 아래와 같은 소스를 짜고, 그 로그를 확인하기 위해 i를 프린트 시켰다.
그런데 이게 무슨일인가?? 창에는 아무 print도 찍히지 않았다;;;; 이러한 현상이 발생한 이유를 최대한 자세히 서술해 보시오.
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,6,2,7,4); Stream<Integer> stream = list.stream();
Stream<Integer> sorted = stream.sorted();
IntStream intStream = sorted.mapToInt(i -> {
System.out.println(i);
return i;
});
}
답안
스트림은 lazy loading을 하기 때문에 최종 연산이 이루어지기 전에 미리 수행되지 않는다. lazy loading을 통해 쇼트서킷이나 루프퓨전 같은 효과를 얻을 수 있다.
나봄
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2); integers.stream()
.peek(i -> System.out.println("peek : " + i))
.forEach(i -> System.out.println("foreach : "+ i));
}
위의 코드는 과연 어떤 순으로 시작이 될까요!
- peek :1, peek :2, foreach :1, foreach :2 순으로 찍힌다.
- peek :1, foreach :1, peek :2, foreach :2 순으로 찍힌다.
답안
2번
웨지
웨지는 맥x날드 알바를 다년간 한적이 있습니당ㅎㅎ
여러 메뉴를 다양한 방식으로 디스플레이 해야하는데, 매니저의 요구사항을 받아 스트림으로 만들어 놓으려고 해요. 상황에 알맞은 중간 연산을 맵핑해주세요!
일단 메뉴를 전부 줄게, 다 적어나봐..
1. 다이어트 메뉴를 만들어야해, 500칼로리가 안 되는 메뉴들만 뽑아봐
2. 그중 가격이 가장 비싼걸 찾아야 하거든?
3. 3개까지만.
4. 그 메뉴들의 가격 목록을 만들어줘
---
a. map()
b. limit()
c. sorted()
d. filter()
답안
d-c-b-a, 2번의 경우에는 max
메서드를 이용하면 더 쉽게 구할 수 있음
라이언
뭐라고 출력할지 적으시오, 또한 해당 코드에서 쇼트서킷과 루프퓨전에 연관지어 서술하시오.
List<String> result = Stream.of("우테코", "짱짱", "모두들 다같이", "루터에서 열심히", "스터디 합시다", "화이팅 화이팅")
.filter(s -> {
System.out.println("filtering " + s);
return s.length()>=4;
})
.map(s -> {
System.out.println("mapping " + s);
return s;
})
.limit(3)
.collect(Collectors.toList());
System.out.println("결과:");
result.forEach(System.out::println);
답안
filtering 우테코
filtering 짱짱
filtering 모두들 다같이
mapping 모두들 다같이
filtering 루터에서 열심히
mapping 루터에서 열심히
filtering 스터디 합시다
mapping 스터디 합시다
결과:
모두들 다같이
루터에서 열심히
스터디 합시다
중간곰
아래 두 코드는 Integer list를 정렬, 중복제거 하는 코드이다.
자신이라면 둘 중 어떤 코드를 선택할지, 어떤 차이점이 있을지 생각해보자. (차이가 없을 수도 있다.)
numbers.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
numbers.stream()
.sorted()
.distinct()
.collect(Collectors.toList());
답안
distinct()
를 먼저하는 쪽이 좋다. 자세한 답변은 이곳에 정리해놨다. 링크
완태
4장 문제
어떤 부분이 잘못됬을까요? 알아맞춰보세요!
int count = menu.stream()
.filter(d -> d.getPrice() > 10000)
.distinct()
.limit(3)
.count();
답안
count()
는 리턴 타입이 long 이다.
다니
공백을 채워주세요!
- 스트림 API의 특징에는 __, __, ____가 있다.
- 스트림은 __, __, ____으로 이루어진다.
- 자바 컬렉션은 __, 스트림은 __을 사용한다.
- 중간 연산은 파이프라인으로 구성되어 최종 연산에서 한 번에 처리된다. 이를 ____ 계산된다고 한다.
- 중간 연산은 __을 반환하고, 최종 연산은 __을 반환한다.
- 중간 연산의 예시로는 __가 있고, 최종 연산의 예시로는 __가 있다.
답안
- 선언형, 조립가능, 병렬화
- 데이터소스, 중간연산, 최종연산
- 외부반복, 내부반복
- 게으르게
- 스트림, 결과
- map, count
검프
검프는 사람들의 이름의 글자수가 궁금하다.
stream을 사용하여 사람들의 이름의 글자수를 List에 저장하기로 한다.
글자수의 2배는 어떤 값일까?
라는 생각이 문득 든 검프, 다시 stream을 사용하여 글자수에 2배를 한 값을 List에 저장하기로한다.
문제, 현재 코드는 개발자를 힘들게한다. 그 이유를 찾고, 각자 리팩토링 해보자!
public class Application {
public static void main(String[] args) {
List<String> names = Arrays.asList("손너잘", "중간곰", "찰리", "웨지");
Stream<String> stream = names.stream(); List<Integer> nameLengths = stream.map(map -> map.length())
.collect(Collectors.toList());
System.out.println("nameLengths = " + nameLengths); List<Integer> doubleNameLengths = stream.map(map -> map.length() * 2)
.collect(Collectors.toList());
System.out.println("doubleNameLengths = " + doubleNameLengths);
}
}
답안
public class Solution {
public static void main(String[] args) {
List<String> names = Arrays.asList("손너잘", "중간곰", "찰리", "웨지");
List<Integer> nameLengths = toIntegersByStrings(names, String::length);
System.out.println("nameLengths = " + nameLengths); List<Integer> doubleNameLengths = toIntegersByStrings(names, name -> name.length() * 2);
System.out.println("doubleNameLengths = " + doubleNameLengths);
} private static List<Integer> toIntegersByStrings(List<String> names, Function<String, Integer> nameMapper) {
return names.stream()
.map(nameMapper)
.collect(toList());
}
}
스트림은 재사용할 수 없다.
파즈
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
long count = list.stream().peek(System.out::println).count();
System.out.println(count);
출력결과를 작성해보시오.
답안
5, count()
는 자바 9부터 중간연산이 결과에 영향을 미치지 않는 경우에 중간연산을 뛰어넘는다.
김김
현대 자바 개발자가 stream을 써야하는 이유는 단순히 함수형 언어처럼 코드를 작성함으로써 얻을 수 있는 가독성 뿐만 아니라 성능에도 지대한 영향을 미치기 때문입니다. 스트림을 사용함으로써 얻을 수 있는 성능상의 이점을 다음과 같이 2가지 키워드로 정리해 보았습니다. 빈칸 ①, ②, ③을 채워주세요! (한글이 아니어도 되고, 책에서 번역된 용어와 토씨하나 같을 필요 역시 없습니다.)A. *__①: 멀티 코어 컴퓨터가 대세가 되어감에 따라 멀티 쓰레딩을 활용한 프로그래밍은 더욱 중요해졌고, 자바 8 이후 개발자는 parallelStream을 활용하여 쉽게 *①을 얻을 수 있게 되었습니다. 물론 그 이전에도 synchronized 키워드를 통해 코드를 작성하면 *①__을 얻을 수 있었으나 꽤 악랄한 난이도를 자랑했다고 합니다.B. 최적화: '모던 자바 인 액션'에서는 스트림을 이용한 최적화 원리로 __*②__ evaluation과 ③ evaluation을 제시하고 있습니다.
B-② evaluation: ② evaluation란 회로 이론에서 유래한 용어입니다. 연산의 중간에 이미 확실하게 결과를 알 수 있는 시점에 도달하면 남은 연산을 하지 않고 답을 내버리는 방식을 뜻하는 것으로, 이런 프로그램의 흐름이 마치 회로에서 합선이 발생했을 때 전류가 합선에 의한 지름길을 따라 흐르는 것과 같다하여 지어졌습니다.
B-③ evaluation: ③evaluation이란 해당 연산이 필요시에만 수행되는 방식을 뜻하는데, 마치 필요할 때가 되어서야 실행하는 모습이 무엇이든 미리 해두려고 하는 부지런한 성격과는 반대된다는 의미에서 지어졌습니다.
답안
- 병렬화
- 쇼트서킷
- 레이지 로딩
'공부 기록들 > 우테코' 카테고리의 다른 글
[모던 자바 인 액션 스터디] 6장 스트림 활용 (0) | 2021.03.21 |
---|---|
[모던 자바 인 액션 스터디] 5장 스트림 활용 (0) | 2021.03.14 |
순차적 스트림, 병렬 스트림 그리고 findAny와 findFirst 에 대해 (0) | 2021.03.07 |
함수형 인터페이스를 이용할 때 착각했던 점 (0) | 2021.03.05 |
로또 구현 피드백 정리 (0) | 2021.03.01 |