public class Deck {
private final Deque<Card> value;
public Deck(final Deque<Card> cards) {
this.value = cards;
}
public Card draw() {
Card card = this.value.pollLast();
// pollLast()의 특징(큐가 비워있으면 null 반환)을 알지 못한 경우에는 null 예외를 만나게 된다.
// 확인하지 인지 못하여 예외 처리를 미리 하지 못함
if(card == null) {
throw new IllegalStateException("카드덱에 카드가 없습니다!");
}
return card;
}
}
deque의 메서드 중 pollLast() : 큐의 마지막 요소 추출
the tail of this deque, or null if this deque is empty
- queue가 비워있는 상태면 null을 반환한다.
Deque의 pollLast() 메서드의 주석을 보기 전까지 null 반환하는지 여부를 몰라 나중에 null인 경우를 만나고 나서야 예외 처리를 함
public class Person {
private final String name;
// 1. 사람 안에 자동차
private final Car car;
public Person(final String name, final Car car) {
this.name = name;
this.car = car;
}
public String getGetCarInsuranceName() {
return car.getInsurance()
.getInfo()
.getName();
}
static class Car {
// 2. 자동차 안에 보험
private final Insurance insurance;
public Car(final Insurance insurance) {
this.insurance = insurance;
}
public Insurance getInsurance() {
return insurance;
}
}
static class Insurance {
// 3. 보험 안에 정보
private final Info info;
public Insurance(final Info info) {
this.info = info;
}
public Info getInfo() {
return info;
}
}
static class Info {
// 4. 정보 안에 이름
final String name;
public Info(final String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}
public String getGetCarInsuranceName() {
return car.getInsurance()
.getInfo()
.getName();
@Test
@DisplayName("1번 id에 있는 회원의 이름은 조이썬이고, 자동차 보험명은 삼성 자동차 보험 이다.")
void test() {
Person person = repository.findById(1);
assertThat(person.getName()).isEqualTo("조이썬");
assertThat(person.getGetCarInsuranceName()).isEqualTo("삼성 자동차 보험");
}
"this.car " is null -> NullPointerException 발생
- 문제 상황 : 조이썬은 차가 없어서 null을 넣어둔듯
persons.add(new Person("조이썬", null));
@Test
@DisplayName("조이썬의 차는 벤츠이고, 자동차 보험명은 현대 자동차 보험 이다.")
void test2() {
Person person = repository.findByName("조이썬");
assertThat(person.getCarName()).isEqualTo("벤츠");
assertThat(person.getGetCarInsuranceName()).isEqualTo("현대 자동차 보험");
}
Cannot Invoke "Insurance.getName() " because the return value of "Car.getInsurance()" is null
- 문제 상황 : 차가 있지만 보험이 null이기 때문에 NullPointerException 발생
public String getCarInsuranceName() {
if (car != null) {
if (car.getInsurance() != null) {
if (car.getInsurance().getInfo() != null) {
return car.getInsurance().getInfo().getName();
}
throw new IllegalStateExceptipn("보험 정보가 없습니다");
}
throw new IllegalStateExceptipn("보험이 없습니다");
}
throw new IllegalStateExceptipn("차가 없습니다!");
}
Java 8에서 도입되어 값을 감싸는 래퍼 클래스
Cat -> Optional<Cat>
고양이가 있으면? -> 고양이를 꺼내서 밥을 준다
고양이가 없으면? -> 고양이가 없다고 경고(예외) 한다 or 새로운 고양이를 가져온다.
메소드 | 의미 |
---|---|
of(T t) | 값이 null이 될 수 없는 Optional 생성, null이 들어오면 NullPointerException 발생 |
ofNullable(T t) | 값이 null일 수 있는 Optional 생성 |
empty() | 값이 없는 Optional 생성 |
@Test
void ofWhenNpeTest() {
Person person = null;
Assertions.assertThatThrownBy(() -> {
Optional.of(person);
})
.isInstanceOf(NullPointerException.class);
}
@Test
void ofWhenNpeTest() {
Person person = null;
assertThatCode(() -> {
Optional.ofNullable(person);
}).doseNotThrowAnyException();
assertThatThrownBy(() -> person.getCarName())
.isInstanceOf(NullPointerException.class);
}
메소드 | 의미 |
---|---|
get() | 값을 반환, 존재하지 않으면 NoSuchElementException 발생 |
isPresent() / isEmpty() | 값이 존재하면, 존재하지않으면 참을 반환, 그렇지 않으면 거짓을 반환 |
ifPresent(Consumer<? super T> action) | 값이 존재하면 주어진 동작을 값과 함께 실행 |
orElse(T other) | 값이 존재하면 해당 값 반환, 존재하지 않으면 다른 값 반환 |
orElseGet(Supplier<? extends T> supplier) | 값이 존재하면 해당 값 반환, 존재하지 않으면 제공된 함수로 생성된 값 반환 |
orElseThrow() (Supplier<? extends X> exceptionSupplier) | 값이 존재하면 해당 값 반환, 존재하지 않으면 예외 발생 |
public class Deck {
private final Deque<Card> value;
public Deck(final Deque<Card> cards) {
this.value = cards;
}
public Optional<Card> draw() {
// null이 될 수도 있다고 반환
return Optional.ofNullable(this.value.pollLast());
}
}
deck.draw()
.orElseThrow(() -> new IllegalStateException("카드가 없네?"));
public Card draw() {
Card card = this.value.pollLast();
if(card == null) {
throw new IllegalStateException("카드덱에 카드가 없습니다!");
}
return card;
}
@Test
void avoidPattern() {
final Optional<Person> optionalPerson = personMemoryRepository.findById(0);
if(optionalPerson.isPresent()) {
final Person person = optionalPerson.get();
assertThat(person.getName()).isEqualTo("제우스");
}
}
@Test
void avoidPattern() {
personMemoryRepository.findById(0)
.ifPresent(person ->
assertThat(person.getName()
.isEqualTo("제우스"));
@Test
void avoidPattern() {
final int id = 0;
final Person person = personMemoryRepository.findById(id)
.orElseThrow(() -> new NotExistPersonException(id));
public Post create(int humanId, String content) {
Optional<String> optionalName = getHumanNameById(humanId);
String name = optionalName.orElse(randomName());
return new Post(name, content);
}
private String randomName() {
log.info("random 이름이 생성되었습니다");
return "randomName";
}
public Post create(int humanId, String content) {
Optional<String> optionalName = getHumanNameById(humanId);
String name = optionalName.orElse(DEFAULT_NAME);
return new Post(name, content);
}
private String randomName() {
log.info("random 이름이 생성되었습니다");
return "randomName";
}
public Post create(int humanId, String content) {
Optional<String> optionalName = getHumanNameById(humanId);
String name = optionalName.orElseGet(this::randomName);
return new Post(name, content);
}
private String randomName() {
log.info("random 이름이 생성되었습니다");
return "randomName";
}
public class OptionalPerson {
private final String name;
// car가 있을수도 없을수도 있으니 Optional 매핑
private final Optional<OptionalCar> car;
public OptionalPerson(String name, Optional<OptionalCar> car) {
this.name = name;
this.car = car;
}
public Optional<OptionalCar> getCar() {
return car;
}
public String getCarName() {
return car.orElseThrow( () -> new IllegalStateException("차가 없습니다.") )
.getName();
@Test
void avoidPattern() {
OptionalPerson person = new OptionalPerson("제리",
Optional.ofNullable(new OptionalCar("스타렉스", Optional.ofNullable(null))));
}
public class Person {
private final String name;
private final Car car; // null 일 수 있음
public Person(final String name, final Car car) {
this.name = name;
this.car = car;
}
public Optional<Car> getCar() {
return Optional.ofNullable(car);
}
}
public class Person {
private final String name;
private final Car car; // Null 일 수 있음
public Person(final String name, final Car car) {
this.name = name;
this.car = car;
}
public Person(String name) {
this.name = name;
this.car = null;
}
public Optional<Car> getCar() {
return Optional.ofNullable(car);
}
public String getCarInsuranceName() {
Optional<Car> car = getCar();
if(car.isPresent()) {
Optional<Insurance> insurance = car.get().getInsurance();
if(insurance.isPresent()) {
Optional<Info> info = insurance.get().getInfo();
if(info.isPresent()) {
return info.get().getName();
}
}
}
throw new IllegalStateException("조건에 충족하지 못합니다.");
}
public String getGetCarInsuranceName() {
return Optional.ofNullable(car)
.flatMap(Car::getInsurance)
.flatMap(Insurance::getInfo)
.map(Info::name)
.orElseThrow( () -> new IllegalArgumentException("조건에 충족하지 못합니다") );
둘 다 Optional을 통해 값을 변환하는 경우 사용
Stream API와 Method Chaining 역시 가능
public int findJockerCardValue() {
return value.stream()
.filter(Card::isJoker)
.findFirst()
.map(card -> card.value())
.orElseThrow( () -> new IllegalStateException("조커 카드가 없습니다") );