일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 커링
- @EnableScheduling
- @configuration
- 가장 큰 수
- @Getter
- K번째수
- 정렬
- 다리를 지나는 트럭
- 검색 기능 확장
- 프로그래머스
- @Data
- 쿠버네티스
- 영속 자료구조
- 모던 자바 인 액션
- @Setter
- 기능개발
- 스프링 스케쥴러
- 스택/큐
- 코딩 테스트
- H-index
- 해시
- 고차원 함수
- 전화번호 목록
- 알고리즘
- kubenetes
- 루씬 인 액션
- 롬복 어노테이션
- Java
- 크론 표현식
- 완주하지 못한 선수
- Today
- Total
Today I Learned
[모던 자바 인 액션] 5장. 스트림 활용 (2) 본문
5.6 실전 연습
지금까지 배운 내용을 이용해서 아래 질문들을 해결해보자.
- 2011년에 일어난 모든 트랜잭션을 찾아 오름차순으로 정렬하시오.
- 거래자가 근무하는 모든 도시를 중복 없이 나열하시오.
- 케임브리지에서 근무하는 모든 거래자를 찾아서 이름순으로 정렬하시오.
- 모든 거래자의 이름을 알파벳 순으로 정렬해서 반환하시오.
- 밀라노에 거래자가 있는가?
- 케임브리지에 거주하는 거래자의 모든 트랜잭션값을 출력하시오.
- 전체 트랜잭션 중 최댓값은 얼마인가?
- 전체 트랜잭션 중 최솟값은 얼마인가?
5.6.1 거래자와 트랜잭션
다음과 같이 Trader 리스트와 Transaction 리스트를 이용한다.
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
public class Trader {
private String name;
private String city;
public Trader(String n, String c) {
name = n;
city = c;
}
public String getName() {
return name;
}
public String getCity() {
return city;
}
}
public class Transaction {
private Trader trader;
private int year;
private int value;
public Transaction(Trader trader, int year, int value) {
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader() {
return trader;
}
public int getYear() {
return year;
}
public int getValue() {
return value;
}
}
5.6.2 실전 연습 정답
public static void practice1(List<Transaction> transactions) {
List<Transaction> result = transactions.stream()
.filter(transaction -> 2011 == transaction.getYear())
.sorted(Comparator.comparing(Transaction::getValue))
.collect(Collectors.toList());
System.out.println(result);
}
public static void practice2(List<Transaction> transactions) {
List<String> result = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getCity)
.distinct().collect(Collectors.toList());
System.out.println(result);
}
public static void practice3(List<Transaction> transactions) {
List<Trader> result = transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> "Cambridge".equals(trader.getCity()))
.sorted(Comparator.comparing(Trader::getName))
.collect(toList());
System.out.println(result);
}
public static void practice4(List<Transaction> transactions) {
String result = transactions.stream()
.map(Transaction::getTrader)
.map(Trader::getName)
.distinct()
.sorted()
.collect(Collectors.joining(","));
System.out.println(result);
}
public static void practice5(List<Transaction> transactions) {
boolean result = transactions.stream()
.anyMatch(transaction -> "Milan".equals(transaction.getTrader().getCity()));
System.out.println(result);
}
public static void practice6(List<Transaction> transactions) {
List<Transaction> result = transactions.stream()
.filter(transaction -> "Cambridge".equals(transaction.getTrader().getCity()))
.collect(toList());
System.out.println(result);
}
public static void practice7(List<Transaction> transactions) {
Optional<Transaction> result = transactions.stream()
.max(Comparator.comparing(Transaction::getValue));
System.out.println(result.get().getValue());
}
public static void practice8(List<Transaction> transactions) {
Optional<Transaction> result = transactions.stream()
.min(Comparator.comparing(Transaction::getValue));
System.out.println(result.get().getValue());
}
5.7 숫자형 스트림
reduce 메서드로 스트림 요소의 합을 구하는 방식은 다음과 같다.
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
하지만 위 코드에선 합계를 계산하기 전에 Integer를 기본형으로 언박싱해야한다.
5.7.1 기본형 특화 스트림
스트림 API는 박싱 비용을 피할 수 있도록 IntStream, DoubleStream, LoingStream을 제공한다.
각각의 인터페이스는 sum, max와 같은 숫자 관련 리듀싱 연산 수행 메서드를 제공하며, 다시 객체 스트림으로 복원할 수 있는 기능도 제공한다.
숫자 스트림으로 매핑
int calories = menu.stream().mapToInt(Dish::getCalories).sum();
mapToInt 메서드는 각 요리에서 모든 칼로리(Interger형식)를 추출한 다음에 IntStream을(Stream<Integer>가 아님) 반환한다. 스트림이 비어있으면 sum은 기본값 0을 반환한다.
객체 스트림으로 복원하기
boxed 메서드를 이용해서 특화 스트림을 일반 스트림으로 변환할 수 있다.
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
기본값: OptionalInt
Optional을 Integer, String 등의 참조 형식으로 파라미터화할 수 있으며, OptionalInt, OptionalDouble, OptionalLoing 세 가지 기본형 특화 스트림 버전도 제공한다.
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
5.7.2 숫자 범위
IntStream과 LongStream에서는 range와 rangeClosed라는 두 가지 정적 메서드를 제공한다. 두 메서드 모두 시작값과 종료값을 인수로 가진다.
range 메서드는 시작값과 종료값이 결과에 포함되지 않는 반면, rangeClosed는 시작값과 종료값이 결과에 포함된다.
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
5.7.3 숫자 스트림 활용 : 피라고라스 수
한 단계씩 문제를 해결해가며 숫자 스트림과 스트림 연산을 활용해 '피타고라스 수' 스트림을 만들어보자.
피타고라스 수
a x a + b x b = c x c
세 수 표현하기
int 배열을 먼저 정의하자. 예를 들어 (3,4,5)를 new int[] {3, 4, 5}로 표현할 수 있다.
좋은 필터링 조합
피타고라스 정의를 만족하는 정수 조합을 찾아보자.
a와 b 두 수가 있을때 c가 정수인지 확인하기 위해 다음 코드를 사용할 수 있다.
Math.sqrt(a*a + b*b) % 1 == 0;
집합 생성
정수 조합을 필터링한 후에 map을 이용해 각 요소를 피타고라스 수로 변환할 수 있다.
stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.map(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)});
b값 생성
Stream.rangeClosed로 b 값의 범위를 생성해보자.
//case1
IntStream.rangeClosed(1, 100)
.stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.boxed()
.map(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)});
//case2
IntStream.rangeClosed(1, 100)
.stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)});
IntStream의 map은 스트림의 각 요소로 int가 반환될 것을 기대한다. 따라서 boxed 메서드로 Stream<Integer>형태로 변환한 후에 map 메서드를 사용하거나, IntStream의 mapToObj 메서드를 사용해야한다.
a값 생성
마지막으로 a값을 생성한다.
Stream<int[]> pythagoreanTriples = IntStream.rangeClose(1, 100).boxed()
.flatMap(a -> IntStream.rangeClose(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b -> new int[]{a b, (int)Math.sqrt(a * a + b * b)})
스트림 a의 값을 매핑하면 스트림의 스트림이 만들어지므로 flatMap 메서드를 이용해 평준화한다.
중복된 수를 방지하기위해 b 값의 범위는 (a, 100)으로 변경한다.
개선할 점?
현재 코드에서는 제곱근을 두번 계산한다. 원하는 조건의 결과만 필터링하도록 수정해보자.
Stream<int[]> pythagoreanTriples = IntStream.rangeClose(1, 100).boxed()
.flatMap(a -> IntStream.rangeClose(a, 100)
.mapToObj(b -> new int[]{a b, (int)Math.sqrt(a * a + b * b)})
.filter(t -> t[2] % 1 == 0));
5.8 스트림 만들기
5.8.1 값으로 스트림 만들기
임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해서 스트림을 만들 수 있다.
Stream<String> stream = Stream.of("Modern", "Java", "In", "Action");
empty 메서드를 이용해서 스트림을 비울 수 있다.
Stream<String> emptyStream = Stream.empty();
5.8.2 null이 될 수 있는 객체로 스트림 만들기
ofNullable 메서드로 null이 될 수 있는 객체를 포함하는 스트림을 만들 수 있다.
이 패턴은 flatMap과 함께 사용하는 상황에서 유용하다.
Stream<String> stream = Stream.ofNullable(System.getProperty("home"));
Stream<String> values = Stream.of("config", "home", "user")
.flatMap(key -> Stream.ofNullable(System.getProperty(key)));
5.8.3 배열로 스트림 만들기
배열을 인수로 받는 정적 메서드 Arrays.stream을 이용해서 스트림을 만들 수 있다.
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
5.8.4 파일로 스트림 만들기
파일을 처리하는 등의 I/O 연산에 사용하는 자바의 NIO API(비블록 I/O)도 스트림 API를 활용할 수 있다.
Files.lines로 파일의 각 행 요소를 반환하는 스트림을 얻을 수 있다.
Stream 인터페이스는 AutoCloseable 인터페이스를 구현하므로, try 블록 내의 자원은 자동으로 관리된다.
long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
//
}
5.8.5 함수로 무한 스트림 만들기
Stream.iterate와 Stream.generate를 이용해서 함수에서 스트림을 만들 수 있다.
두 연산을 이용하면 무한 스트림, 즉 고정된 컬렉션에서 고정된 크기로 스트림을 만들었던 것과 달리 크기가 고정되지 않은 스트림을 만들 수 있다.
iterate 메서드
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
iterate 메서드는 초깃값과 람다를 인수로 받아서 새로운 값을 끊임업이 생산할 수 있다.
예제에서는 람다 n -> n+2, 즉 이전 결과에 2를 더한 값을 반환한다.
iterate는 요청할 때마다 값을 생산할 수 있으며 끝이 없으므로 무한 스트림을 만든다. 이러한 스트림을 언바운드 스트림이라고 표현한다.
iterate 메서드는 프레디케이트를 지원한다. 두 번째 인수로 프레디 케이트를 받아 작업 중단의 기준으로 사용한다.
0에서 시작해서 100보다 크면 숫자 생성을 중단하는 코드를 다음처럼 구현할 수 있다.
IntStream.iterate(0, n -> n < 100, n -> n + 4)
.forEach(System.out::println);
filter로도 같은 결과를 얻을수 있다고 생각할 수 있지만, filter 메서드는 언제 이 작업을 중단해야 하는지 알 수 없다.
이럴 때에는 talkWhile 메서드를 사용해야 한다.
// 불가
IntStream.iterate(0, n -> n + 4)
.filter(n -> n < 100)
.forEach(System.out::println);
// 가능
IntStream.iterate(0, n -> n + 4)
.talkWhile(n -> n < 100)
.forEach(System.out::println);
generate 메서드
iterate와 달리 generate는 생산된 각 값을 연속적으로 계산하지 않으며, Supplier<T>를 인수로 받아서 새로운 값을 생산한다.
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
위 코드는 0에서 1 사이의 임의의 더블 숫자 5개를 만든다.
'JAVA & Spring > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집 (2) (0) | 2021.07.28 |
---|---|
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집 (1) (1) | 2021.07.22 |
[모던 자바 인 액션] 5장. 스트림 활용 (1) (0) | 2021.07.07 |
[모던 자바 인 액션] 4장. 스트림 소개 (0) | 2021.06.27 |
[모던 자바 인 액션] 3장. 람다 표현식 (0) | 2021.06.16 |