// 열 개 이하의 키/값 쌍을 가진 작은 맵을 만들 경우Map<String,Integer> ageOfFriends =Map.of("Park",20,"Kim",21,"Jeong",25);// 그 이상의 맵 생성 (Map.enty: Map.Entry 객체를 만드는 새로운 팩토리 메서드)importstaticjava.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 BondSystem.out.println(favouriteMovies.getOrDefault("Thibaut","Matrix")); // Matrix
4. Compute Pattern
computeIfAbsent
제공된 키에 해당하는 값이 없으면(값이 없거나 널), 키를 이용해 새 값을 계산하고 맵에 추가
현재 키와 관련된 값이 맵에 존재하며 널이 아닐 때만 새 값을 계산
Map<String,byte[]> dataToHash =newHashMap<>();MessageDigest messageDigest =MessageDigest.getInstance("SHA-256");lines.forEach(line ->dataToHash.computeIfAbsent(line,// 맵에서 찾을 키this::calculateDigest)); // 키가 존재하지 않을 경우 동작privatebyte[] calculateDigest(String key) {returnmessageDigest.digest(key.getBytes(StandardCharsets.UTF_8));}// Map이 여러 값을 저장하는 형태일 경우friendsToMovies.computeIfAbsent("Raphael", name ->newArrayList<>()).add("Star Wars");
computeIfPresent
제공된 키가 존재하면 새 값을 계산하고 맵에 추가
compute
제공된 키로 새 값을 계산하고 맵에 저장
5. Remove Pattern
//제공된 키에 해당하는 맵 항목 제거favouriteMovies.remove(key)//키가 특정한 값과 연관되어 있을 경우에만 항목을 제거하는 오버로드 버전 메서드favouriteMovies.remove(key, value);//특정 조건에 해당하는 항목 삭제favouriteMovies.entrySet().removeIf(entry ->entry.getValue<10);
/* * 명령형 코드 */List<String> dishNames =newArrayList<>();for(Dish dish: menu) {if(dish.getCalories() >300) {dishNames.add(dish.getName()); }}/* * 스트림 API */menu.parallelStream().filter(d ->d.getCalories() >300).map(Dish::getName).collect(toList());
4. 코드 유연성 개선
람다 표현식을 이용해서 동작 파라미터화를 쉽게 구현해보자.
함수형 인터페이스 적용
람다 표현식을 이용하기 위해 함수형 인터페이스를 추가
조건부 연기 실행
// Before : 복잡한 제어 흐름 코드 (상태 노출 및 매번 상태 체크)if (logger.isLoggable(Log.FINER)) {logger.finer("Problem: "+generateDiagnostic());}// After : 가독성과 캡슐화 강화publicvoidlog(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()); // 다른 람다 전달publicstaticStringprocessFile(BufferedReaderProcessor p) throws IOException {try (BufferedReader br =newBufferedReader(new FileReader("Aaron/MJIA/test.txt"))) {returnp.process(br); // 인수로 전달된 BufferedReaderProcessor 실행 }} // IOException을 던질 수 있는 람다의 함수형 인터페이스publicinterfaceBufferedReaderProcessor {Stringprocess(BufferedReader b) throwsIOException;}
@TestpublicvoidtestFilter() 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());