람다 함수는 한 번 쓰고 말 함수를 편리하게 사용할 수 있도록 해준다.
다음과 같은 함수를 생각해보자.
boolean startsWithCafe(String menu) {
return menu.startsWith("cafe");
}
이름과 타입을 빼고, 매개변수와 결과만 남겨 다음과 같이 사용할 수 있다.
menu -> menu.startsWith("cafe");
Stream을 사용하면 배열이나 컬렉션 등의 데이터를 반복문 없이 간단하게 처리할 수 있다.
다음과 같은 작업을 생각해보자.
List<String> menus = List.of("americano", "cafe latte", "green tea", "cafe mocha");
List<String> result = new ArrayList<>();
for (String menu : menus) { // 메뉴에 있는 요소들 중
if (menu.startsWith("cafe")) { // cafe로 시작하는 것들에 대해
result.add(menu.toUpperCase()); // 대문자로 변환 후 결과 리스트에 추가
}
}
System.out.println(result);
Stream과 Lambda를 활용하면 이 동작을 다음과 같이 작성할 수 있다.
List<String> result = menus.stream() // 메뉴에 있는 요소들 중
.filter(menu -> menu.startsWith("cafe")) // cafe로 시작하는 것만 필터링해서
.map(menu -> menu.toUpperCase()) // 모두 대문자로 변경한 후
.toList(); // 리스트로 변환
System.out.println(result);
Stream은 원본 데이터를 변경하지 않고 새 결과를 돌려준다.
null은 값이 없음을 의미한다. null을 제대로 고려하지 않으면 예상치 못한 곳에서 NullPointerException이 튀어나올 수 있다.
Optional은 null을 안전하게 다루게 해주는 Wrapper 클래스로, 값이 있을 수도 없을 수도 있는 타입을 나타낸다.
null일 수도 있는 값을 Optional.ofNullable로 감싸면 Optional.empty 혹은 Optional[value]을 반환한다.
public class Main {
public static void main(String[] args) {
Map<String, Integer> menu = new HashMap<>();
menu.put("아메리카노", 4500);
menu.put("라떼", 5000);
Optional<Integer> optionalPrice = Optional.ofNullable(menu.get("녹차"));
System.out.println(optionalPrice); // Optional.empty
}
}
Optional로 담긴 값을 꺼낼 때 orElseThrow를 쓸 수 있다. 값이 있으면 값을 꺼내주고, 없으면 예외를 던진다. NullPointerException과 다르게, 프로그래머가 통제할 수 있다.
int price = Optional.ofNullable(menu.get("녹차"))
.orElseThrow(() -> new IllegalArgumentException("판매하지 않는 메뉴입니다: 녹차"));