
저자: 라울-게이브리얼 우르마 , 마리오 푸스코 , 앨런 마이크로프트
도서명: 모던 자바 인 액션
출판사: 한빛미디어

이펙티브 자바 3/E의 아이템 54는 null 대신, 빈 컬렉션이나 배열을 리턴할 것을 권장한다.
하나는 Null Object 디자인 패턴이다. (가장 좋은 방법은 하나의 불변 객체로 만드는 것이다.)
public Employee getByName(String name) {
int id = database.find(name);
if (id == 0) {
return Employee.NOBODY;
}
return Employee(id);
}
또 하나는 객체를 돌려줄 수 없는 경우에 예외를 던지고 fail-fast하는 방법이다.
public Employee getByName(String name) {
int id = database.find(name);
if (id == 0) {
throw new EmployeeNotFoundException(name);
}
return Employee(id);
}
입력으로 객체를 받은 경우는 항상 그것이 NULL 아닌지, 또한 유효한 객체 참조 여부를 확인해야한다.
따라서 if/then/else 분기에 코드가 오염되어 버린다.
메소드의 목적을 분명하게 전달하기 위해서 getByName()은 getByNameOrNullIfNotFound()라고 명명되어져야 한다.
이와 같은 이름을 가진 객체 또는 NULL을 반환하는 모든 함수가 있어야 된다.
그렇지 않다면 누군가가 모호한 코드를 읽게 된다. 그래서 코드의 의도를 명확하게 하기 위해 함수에 긴 이름을 붙이게 한다.
null을 받은 클라이언트 입장에서는 한참 뒤에 객체가 null이었다는 사실을 알게 되는 경우가 생긴다.
객체를 반환받았을 때, 사실 null이었음에도 그 사실을 인지하지 못한 채 로직이 계속 진행되다 그 객체를 사용하려는 때가 되서야 Null Pointer Exception을 받아보게 된다.
https://zorba91.tistory.com/339
값이 있으면 Optional 클래스는 값을 감싼다.
값이 없으면 Optional.empty 메서드로 Optional 을 반환한다.
Optional.empty는 Optional의 특별한 싱글턴 인스턴스를 반환하는 정적 팩토리 메서드다.
모든 null 참조를 Optional로 대치하는 것은 바람직 하지 않다.
Optional<Car> optCar = Optional.empty();
Optional<Car> optCar = Optional.of(car);
car가 null이라면 즉시 NullPointerException이 발생한다.(Optional을 사용하지 않았다면 car의 프로퍼티에 접근하려 할 때 에러가 발생했을 것이다.)
Optional<Car> optCar = Optional.ofNullable(car);
car가 null이면 빈 Optional 객체가 반환된다.
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
여기서 Optional 객체를 최대 요소의 개수가 한 개 이하인 데이터 컬렉션으로 생각할 수 있다.
Optional이 비어있으면 아무 일도 일어나지 않는다.
Optional<Person> optPerson = Optional.of(person);
Optional<String> name = optPerson.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName);
변수 optPeople의 평식은 Optional이므로 map 메서드를 호출할 수 있다. 하지만 getCar는 Optional 형식의 객체를 반환한다.
즉 map 연산의 결과는 Optional<Optional> 형식의 객체이기 때문에 컴파일되지 않는다.
flatMap 메서드를 사용하면 하나의 Optional로 변환 가능하다.
래핑된 값이 있으면 해당 값을 반환하고 값이 없으면 NoSuchElementException을 발생시킨다.
Optional에 값이 없을 때만 Supplier가 실행된다.
Optional이 비어있을 때 예외를 발생시킨다.
값이 존재할 때 인수로 넘겨준 동작을 실행할 수 있다.
값이 없으면 아무 일도 일어나지 않는다.
Optional이 비었을 때 실행할 수 있는 Runnable을 인수로 받는다.
public Insurance findCheapestInsurance(Person person, Car car) {
// 보험회사가 제공하는 서비스 조회
// 모든 결과 데이터 비교
return cheapestCompany;
}
// isPresent 이용해 구현
public Optional<Insurance> nullSafeFindCheapestInsurance(
Optional<Person> person, Optional<Car> car) {
if(person.isPresent() && car.isPresent()){
return Optional.of(findCheapestInsurance(person.get(), car.get()));
} else {
return Optional.empty();
}
}
// 조건문을 사용하지 않고 map과 flapMap으로 구현
public Optional<Insurance> nullSafeFindCheapestInsurance(
Optional<Person> person, Optional<Car> car) {
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p,c)));
}
Optional에서 map과 flatMap은 Optional이 비어있다면 empty Optional을 반환한다.
두 Optional에 대한 연산을 map과 flatMap을 적절히 활용하여 수행할 수 있다.
// 조건문으로 단순 null 체크
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equal(insurance.getName())) {
System.out.println("ok");
}
// Optional 객체에 filter 메서드 사용
Optional<Insurance> optInsurance = ...;
optInsurance.filter(ins -> "CambridgeInsurance".equals(insurance.getName()))
.ifPresent(x -> System.out.println("ok"););
Object value = map.get("key");
Map<String, Object> 형식의 맵에 key로 접근시 문자열 key에 해당하는 값이 없으면 null 반환될 것이다.
Optional<Object> value = Optional.ofNullable(map.get("key"));
map에서 반환하는 값을 Optional로 감싸서 이를 개선할 수 있다.
public static Optional<Integer> stringToInt(String s) {
try{
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
reutn Optional.empty(); // 예외 발생시 빈 Optional 반환
}
}
문자열을 정수로 바꾸지 못해 발생한 NumberFormatException 예외를 Optional 로 처리하기
스트림처럼 Optional도 기본형 특화 클래스 OptionalInt, OptionalLong, OptionalDouble 등을 제공한다.
하지만 Optional의 최대 요소 수는 한개 이므로 기본형 특화 클래스로 성능을 개선할 수 없다.
또한 기본형특화 클래스에는 map, filter 등희 메서드를 지원하지 않으므로 사용을 권장하지 않는다.
Properties props = new Properties();
props.setProperty("a", "5");
props.setProperty("b", "true");
props.setProperty("c", "-3");
// 메서드 시그니처
// Properties를 읽어 값을 초 단위 duration으로 해석.
public int readDuration(Properties props, String name)
// 지속 시간은 양수여야 하므로 문자열이 양의 정수이면 해당값 반환, 그 외에는 0 반환.
assertEqual(5, readDuration(param, "a"));
assertEqual(0, readDuration(param, "b"));
assertEqual(0, readDuration(param, "c"));
assertEqual(0, readDuration(param, "d"));
// 프로퍼티에서 지속 시간을 읽는 명령형 코드
public int readDuration(Properties props, String name) {
String value = props.getProperty(name);
if(value != null) {
try {
int i = Integer.parseInt(value);
if (i>0) {
return i;
}
}catch (NumberFormatException nfe) { }
}
return 0;
}
// Optional로 프로퍼티 지속시간 읽기
// 이전 예제에서 만든 OptionalUtility 활용해 Optional<Integer> 반환
public int readDuration(Properties props, String name) {
return Optioinal.ofNullable(props.getProperty(name))
.flatMap(OptionalUtility::stringToInt)
.filter(x -> x > 0)
.orElse(0)
}