// 열 개 이하의 키/값 쌍을 가진 작은 맵을 만들 경우
Map<String, Integer> ageOfFriends = Map.of("Park", 20, "Kim", 21, "Jeong", 25);
// 그 이상의 맵 생성 (Map.enty: Map.Entry 객체를 만드는 새로운 팩토리 메서드)
import static java.util.Map.entry;
Map<String, String> ageOfFriends = Map.ofEntries(
entry("Park", 20),
entry("Kim", 21),
entry("Jeong", 25));
리스트와 집합 처리
기존 컬렉션 객체와 Iterator 객체를 혼용한 삭제, 수정은 쉽게 문제를 일으켰었다.
그래서, java8에서는 List, Set Interface 에 새로운 메서드가 추가되었다. (기존 컬렉션 자체를 수정)
찾으려는 키가 존재하지 않으면 기본값을 반환 getOrDefault(key, default)
Map<String, String> favouriteMovies = Map.ofEntries(
entry("Raphael", "Star Wars"),
entry("Olivia", "James Bond"));
System.out.println(favouriteMovies.getOrDefault("Olivia", "Matrix")); // James Bond
System.out.println(favouriteMovies.getOrDefault("Thibaut", "Matrix")); // Matrix
4. Compute Pattern
computeIfAbsent
제공된 키에 해당하는 값이 없으면(값이 없거나 널), 키를 이용해 새 값을 계산하고 맵에 추가
현재 키와 관련된 값이 맵에 존재하며 널이 아닐 때만 새 값을 계산
Map<String, byte[]> dataToHash = new HashMap<>();
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
lines.forEach(line ->
dataToHash.computeIfAbsent(line, // 맵에서 찾을 키
this::calculateDigest)); // 키가 존재하지 않을 경우 동작
private byte[] calculateDigest(String key) {
return messageDigest.digest(key.getBytes(StandardCharsets.UTF_8));
}
// Map이 여러 값을 저장하는 형태일 경우
friendsToMovies.computeIfAbsent("Raphael", name -> new ArrayList<>())
.add("Star Wars");
computeIfPresent
제공된 키가 존재하면 새 값을 계산하고 맵에 추가
compute
제공된 키로 새 값을 계산하고 맵에 저장
5. Remove Pattern
//제공된 키에 해당하는 맵 항목 제거
favouriteMovies.remove(key)
//키가 특정한 값과 연관되어 있을 경우에만 항목을 제거하는 오버로드 버전 메서드
favouriteMovies.remove(key, value);
//특정 조건에 해당하는 항목 삭제
favouriteMovies.entrySet().removeIf(entry -> entry.getValue < 10);
// Before : 복잡한 제어 흐름 코드 (상태 노출 및 매번 상태 체크)
if (logger.isLoggable(Log.FINER)) {
logger.finer("Problem: " + generateDiagnostic());
}
// After : 가독성과 캡슐화 강화
public void log(Level level, Supplier<String> msgSupplier) {
if(logger.isLoggable(level)) {
log(level, msgSupplier.get());
}
}
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
실행 어라운드
준비, 종료 과정을 처리하는 로직을 재사용하여 코드 중복 줄이기
String oneLine = processFile((BufferedReader b) -> b.readLine()); // 람다 전달
String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine()); // 다른 람다 전달
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("Aaron/MJIA/test.txt"))) {
return p.process(br); // 인수로 전달된 BufferedReaderProcessor 실행
}
} // IOException을 던질 수 있는 람다의 함수형 인터페이스
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
람다 테스팅
Example
public class Point {
private final int x;
private final int y;
public final static Comparator<Point> compareByXAndThenY =
comparing(Point::getX).thenComparing(Point::getY);
private Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
public Point moveRightBy(int x) {
return new Point(this.x + x, this.y);
}
}
@Test
public void testMoveRightBy() throws Exception {
Point p1 = new Point(5, 5);
Point p2 = p1.moveRightBy(10);
assertEquals(15, p2.getX());
assertEquals(5, p2.getY());
}
1. 보이는 람다 표현식의 동작 테스팅
람다는 익명이므로 테스트 코드 이름을 호출할 수 없다.
필요 시 람다를 필드에 저장해서 재사용하거나, 람다 로직 테스트가 가능하다.
람다 표현식은 함수형 인터페이스의 인스턴스를 생성하므로 인스턴스의 동작으로 람다 표현식을 테스트할 수 있다
public class Point {
//...
public final static Comparator<Point> compareByXAndThenY =
comparing(Point::getX).thenComparing(Point::getY);
//...
}
@Test
public void testComparingTwoPoints() throws Exception {
Point p1 = new Point(10, 15);
Point p2 = new Point(10, 25);
int result = Point.compareByXAndThenY.compare(p1, p2);
assertTrue(result < 0);
}
2. 람다를 사용하는 메서드의 동작에 집중하자
람다의 목표는 정해진 동작을 다른 메서드에서 사용할 수 있도록, 하나의 조각으로 캡슐화 하는 것
세부 구현을 포함하는 람다 표현식을 공개하지 말아야 한다.
public static List<Point> moveAllPointsRightBy(List<Point> points, int x) {
return points.stream()
.map(p -> new Point(p.getX() + x, p.getY()))
.collect(toList());
}
// 람다 표현식을 메서드 참조로 바꾼 예시
public class Dish {
//...
public CaloricLevel getCaloricLevel() {
if (dish.getCalories() <= 400) { return CaloricLevel.DIET; }
else if (dish.getCalories() <= 700) { return CaloricLevel.NORMAL; }
else { return CaloricLevel.FAT; }
}
}
Map<CaloricLevel, List<Dish> dishesByCaloricLevel =
menu.stream().collect(groupingBy(Dish::getCaloricLevel));
4. 고차원 함수 테스팅
메서드가 함수를 인수로 받을 경우, 다른 람다로 메서드 동작을 테스트
@Test
public void testFilter() throws Exception {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> even = filter(numbers, i -> i % 2 == 0);
List<Integer> smallerThanThree = filter(numbers, i -> i < 3);
assertEquals(Arrays.asList(2, 4), even);
assertEquals(Arrays.asList(1, 2), smallerThanThree);
}
다른 함수를 반환하는 메서드의 경우, 함수형 인터페이스의 인스턴스로 간주하고 함수의 동작을 테스트
디버깅
1. 스택 트레이스 확인하기
스택 트레이스를 통해 프로그램이 어디서, 어떻게 멈추게 되었는지 살펴보자.
다만, 람다 표현식은 이름이 없어서 복잡한 스택 트레이스가 생성된다.
메서드 참조를 사용해도 스택 트레이스에서는 메서드명이 나타나지 않는다.
따라서, 람다 표현식과 관련한 스택 트레이스는 이해하기 어려울 수 있다.
2. 정보 로깅
peek 스트림 연산을 활용하여 스트림 파이프라인에 적용된 각 연산이 어떤 결과를 도출하는지 확인할 수 있다.
List<Integer> result =
Stream.of(2, 3, 4, 5) //or numbers.stream()
.peek(x -> System.out.println("from stream: " + x)) // 소스에서 처음 소비한 요소
.map(x -> x + 17)
.peek(x -> System.out.println("after map: " + x)) // map 동작 실행 결과
.filter(x -> x % 2 == 0)
.peek(x -> System.out.println("after filter: " + x)) // filter 동작 후 선택된 숫자
.limit(3)
.peek(x -> System.out.println("after limit: " + x)) // limit 동작 후 선택된 숫자
.collect(toList());