CHAP11 - Modern-Java-in-Action/Online-Study GitHub Wiki

11์žฅ null ๋Œ€์‹  Optional ํด๋ž˜์Šค

  • Optional Class doc
  • Optional ์žฅ์ : ์ข‹์€ API๋ฅผ ์„ค๊ณ„ + null ํฌ์ธํ„ฐ ์˜ˆ์™ธ ๊ฐ์†Œ

์ด ์žฅ์˜ ๋‚ด์šฉ

  • null ์ฐธ์กฐ์˜ ๋ฌธ์ œ์ ๊ณผ null์„ ๋ฉ€๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ด์œ 
  • null ๋Œ€์‹  Optional : nuII๋กœ ๋ถ€ํ„ฐ ์•ˆ์ „ํ•œ ๋„๋ฉ”์ธ ๋ชจ๋ธ ์žฌ๊ตฌํ˜„ํ•˜๊ธฐ
  • Optional ํ™œ์šฉ : null ํ™•์ธ ์ฝ”๋“œ ์ œ๊ฑฐํ•˜๊ธฐ
  • Optional์— ์ €์žฅ๋œ ๊ฐ’์„ ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ๊ฐ’์ด ์—†์„ ์ˆ˜๋„ ์žˆ๋Š” ์ƒํ™ฉ์„ ๊ณ ๋ คํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ

ํŒฉํŠธ

  • ๋„ํฌ์ธํ„ฐ ์—๋Ÿฌ ๋„ˆ๋ฌด ๋งŽ์ด ๊ฒช์—ˆ๋‹ค.
  • null์„ ๋งŒ๋“  ์‚ฌ๋žŒ๋„ ์ž๋ฐ”์—์„œ null ํฌ์ธํ„ฐ๋ฅผ ๋งŒ๋“  ๊ฒƒ์€ ์‹ค์ˆ˜๋ผ๊ณ  ๋งํ•œ๋‹ค.

null ๋•Œ๋ฌธ์— ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋กœ ์‚ดํŽด๋ณด์ž.

11.1 ๊ฐ’์ด ์—†๋Š” ์ƒํ™ฉ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ๊นŒ?

// Person -> Car -> Insurance
public class Person {
    private Car car;
    public Car getCar() { return car; }
}
public class Car {
    private Insurance insurance;
    public Insurance getInsurance() { return insurance; }
}
public class Insurance {
    private String name;
    public String getName() { return name; }
}
// -----------------------------------
// ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž๋™์ฐจ ๋ณดํ—˜ ์ด๋ฆ„์„ ๊ฐ€์ ธ์˜ค๋ ค๊ณ  ํ–ˆ์„๋•Œ,
// ๋ฌด์Šจ ๋ฌธ์ œ๊ฐ€ ์žˆ์„๊นŒ?
public String getCarInsuranceName(Person person) {
    return person.getCar().getInsurance().getName();
}

๋ณด์ˆ˜์ ์ธ ์ž์„ธ๋กœ NullPointerException ์ค„์ด๊ธฐ

  • deep deoubt (๊นŠ์€ ์˜์‹ฌ): ์•„๋ž˜ if๋ฌธ ๋ฐ˜๋ณต๋œ ํŒจํ„ด
// ์‹œ๋„ 1
// ํ•˜๋‚˜๋ผ๋„ null ์ฐธ์กฐ๊ฐ€ ์žˆ์œผ๋ฉด "Unknown" ๋ฆฌํ„ด 
public String getCarInsuranceName(Person person) {
    if (person != null) {
        Car car = person.getCar();
        if (car != null) {
            Insurance insurance = car.getInsurance();
            if (insurance != null) {
                return insurance.getName();
            }
        }
    }
    return "Unknown";
}

// ---------------------------------------
// ์‹œ๋„ 2
public String getCarInsuranceName(Person person) {
    if (person == null) {
        return "Unknown";
    }
    Car car = person.getCar();
    if (car == null) {
        return "Unknown";
    }
    Insurance insurance = car.getInsurance();
    if (insurance == null) {
        return "Unknown";
    }
    return insurance.getName();
}

์งง๊ฒŒ๋งํ•ด, null๋กœ ๊ฐ’์ด ์—†๋‹ค๋Š” ์‚ฌ์‹ค์„ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์•„๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์–ธ์–ด๋Š” null ๋Œ€์‹  ๋ฌด์–ผ ์‚ฌ์šฉํ•˜๋‚˜?

  • ๋‹ค๋ฅธ ์–ธ์–ด null ๋ฌธ์ œ ํ•ด๊ฒฐ ์˜ˆ์‹œ
    • ํ•˜์Šค์ผˆ: Maybe
    • ์Šค์นผ๋ผ: Option[T]
    • ๊ทธ๋ฃจ๋น„: ์•ˆ์ „ ๋‚ด๋น„๊ฒŒ์ด์…˜ ์—ฐ์‚ฐ์ž(?.)
// ๊ทธ๋ฃจ๋น„ ์˜ˆ์‹œ
def carInsuranceName = person?.car?.insurance?.name

11.2 Optional ํด๋ž˜์Šค ์†Œ๊ฐœ

  • ์ž๋ฐ”์—์„ , java.util.Optional
  • Optional ํด๋ž˜์Šค๊ฐ€ ๊ฐ์ฒด๊ฐ€ ์žˆ๋“  null์ด๋“  ๊ฐ์‹ธ์ค€๋‹ค.
// BEFORE
public class Person {
    private Car car;
    public Car getCar() { return car; }
}
public class Car {
    private Insurance insurance;
    public Insurance getInsurance() { return insurance; }
}
public class Insurance {
    private String name;
    public String getName() { return name; }
}
// ---------------------------------------------------------
// AFTER
public class Person2 {
    private Optional<Car> car; 
    public Optional<Car> getCar() { return car; }
}
public class Car2 {
    private Optional<Insurance> insurance;
    public Optional<Insurance> getInsurance() { return insurance; }
}
public class Insurance2 {
    private String name;
    public String getName() { return name; } // ๋ณดํ—˜ ๊ฐ์ฒด ์กด์žฌ์‹œ ์ด๋ฆ„์ด ๋ฐ˜๋“œ์‹œ ์žˆ์–ด์•ผํ•จ์œผ๋กœ, ๋ฆฌํ„ดํ˜•์ด Optional์ด ์•„๋‹ˆ๋‹ค.
}

๋ชจ๋“  null ์ฐธ์กฐ๋ฅผ Optional๋กœ ๋Œ€์น˜ํ•˜๋Š” ๊ฒƒ ์€ ๋ฐ”๋žŒ์งํ•˜์ง€ ์•Š๋‹ค. Optional์˜ ์—ญํ• ์€ ๋” ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด API๋ฅผ ์„ค๊ณ„ํ•˜๋„๋ก ๋•๋Š” ๊ฒƒ์ด๋‹ค. ์ฆ‰, ๋ฉ”์„œ๋“œ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋งŒ ๋ณด๊ณ ๋„ ์„ ํƒํ˜•๊ฐ’์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

11.3 Optional ์ ์šฉ ํŒจํ„ด

Optional ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ

Optional๋กœ ๊ฐ์‹ผ ๊ฐ’์„ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ? ์ผ๋‹จ, Optional ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

// 1 ๋นˆ Optional ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ
Optional<Car2> optCar1 = Optional.empty();

// 2 null์ด ์•„๋‹Œ ๊ฐ’์œผ๋กœ Optional ๋งŒ๋“ค๊ธฐ
// null ์ด ์•„๋‹˜์„ ํ™•์‹คํ•  ๊ฒฝ์šฐ 
Optional<Car2> optCar2 = Optional.of(car);

// 3 null๊ฐ’์œผ๋กœ Optional ๋งŒ๋“ค๊ธฐ
Optional<Ca2r> optCar3 = Optional.ofNullable(car);

์ฃผ์˜: get() ์„ ์ด์šฉํ•˜์—ฌ Optional์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š”๋ฐ, Optional์ด ๋น„์–ด์žˆ์œผ๋ฉด ํ˜ธ์ถœ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ฆ‰, Optional ์‚ฌ์šฉ์‹œ ๊ฒฐ๊ตญ null์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ๊ฒช์„ ์ˆ˜ ์žˆ๋‹ค.

map์œผ๋กœ Optional์˜ ๊ฐ’์„ ์ถ”์ถœํ•˜๊ณ  ๋ณ€ํ™˜ํ•˜๊ธฐ

// BEFORE
String name = null;
if(insurance != null){
    name = insurance.getName();
}

// AFTER
// ๋ณดํ—˜ ๊ฐ์ฒด๋ฅผ Optional๋กœ ๋งŒ๋“ค๊ณ , 
// Optional๋กœ ๊ฐ์‹ธ์ง„ ๊ฐ์ฒด์˜ getName์„ ์‹คํ–‰ํ•œ๋‹ค. 
// ๋ณดํ—˜ ๊ฐ์ฒด๊ฐ€ ๋„์ด๋ฉด name์—๋Š” Optional empty๊ฐ€ ๋“ค์–ด๊ฐ€๊ณ  
// ๊ฐ’์ด ์žˆ์„ ๊ฒฝ์šฐ name์—๋Š” ๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค. 
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
// ------------------------------------
// ๋ณดํ—˜ ์ด๋ฆ„: aaaa๋กœ
Insurance insurance1 = new Insurance("aaaa"); 
Optional<Insurance> optInsurance1 = Optional.ofNullable(insurance1);
Optional<String> ๋ณดํ—˜1 = optInsurance1.map(Insurance::getName);

// // ๋ณดํ—˜ ์ด๋ฆ„: ์ง€์ • x
Insurance insurance2 = new Insurance();
Optional<Insurance> optInsurance2 = Optional.ofNullable(insurance2);
Optional<String> ๋ณดํ—˜2 = optInsurance2.map(Insurance::getName);

System.out.println(๋ณดํ—˜1); // ๊ฒฐ๊ณผ: Optional[aaaa]
System.out.println(๋ณดํ—˜2); // ๊ฒฐ๊ณผ: Optional.empty

flatMap์œผ๋กœ Optional ๊ฐ์ฒด ์—ฐ๊ฒฐ

  • ์ŠคํŠธ๋ฆผ์˜ flatMap ๋ฉ”์„œ๋“œ์™€ Optional์˜ flatMap ๋ฉ”์„œ๋“œ๋Š” ์œ ์‚ฌํ•˜๋‹ค.
  • map๊ณผ flatmap ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์ฐจ์ด

![image-20220123111801353](D:\0 Google Drive\03 ์Šคํ„ฐ๋”” ๋ชจ์ž„\๋ชจ๋˜์ž๋ฐ”\5์ฃผ์ฐจ\11์žฅ.assets\image-20220123111801353.png)

![image-20220123111743582](D:\0 Google Drive\03 ์Šคํ„ฐ๋”” ๋ชจ์ž„\๋ชจ๋˜์ž๋ฐ”\5์ฃผ์ฐจ\11์žฅ.assets\image-20220123111743582.png)

// ์ปดํŒŒ์ผ ์—๋Ÿฌ ์˜ˆ์‹œ
// map -> map -> map ์„ ํ˜ธ์ถœํ•˜๋Š”๋ฐ, Optional<Optional<Optional<Car>>> ์ด๋ ‡๊ฒŒ ์ค‘์ฒฉ๋œ๋‹ค
Optional<Person2> optPerson = Optional.of(person2);
Optional<String> name =
    optPerson.map(Person2::getCar)
    .map(Car2::getInsurance)
    .map(Insurance2::getName);

// ์ŠคํŠธ๋ฆผ์˜ flatMap๊ณผ ๋น„์Šทํ•˜๊ฒŒ, ์˜ต์…”๋„์—์„œ flatMap ์‚ฌ์šฉํ•˜๋ฉด ์ค‘์ฒฉ์ด ํ’€๋ฆฌ๋ฉด์„œ ์ผ์ฐจ์›์œผ๋กœ ๋งŒ๋“ค์–ด์ง„๋‹ค. 

Optional๋กœ ์ž๋™์ฐจ์˜ ๋ณดํ—˜ํšŒ์‚ฌ ์ด๋ฆ„ ์ฐพ๊ธฐ

// null ์ฒดํฌํ•˜๋Š” if๋ฌธ ์—†์ด ์ฝ”๋“œ ๊ฐ„๊ฒฐ
public String getCarInsuranceName(Optional<Person> person) {
    return person.flatMap(Person::getCar) //  Optional์„ ์ด์šฉํ•œ ์ฐธ์กฐ ์ฒด์ธ
        .flatMap(Car::getInsurance)
        .map(Insurance::getName)
        .orElse("Unknown"); //  ๊ฐ’์ด ์—†๋‹ค๋ฉด
}
// ------------------------------------------
// ์‹คํ–‰ ์˜ˆ์‹œ
// ์ฐจ ์—†๋Š” ์‚ฌ๋žŒ ์ƒ์„ฑ 
Person2 person = new Person2(Optional.empty()); 
Optional<Person2> optPerson = Optional.of(person);
System.out.println(getCarInsuranceName3(optPerson)); 
// ๊ฒฐ๊ณผ: Unknown
  • ํ˜ธ์ถœ์˜ ๋…ผ๋ฆฌ์  ๊ณผ์ •
    • 1๋‹จ๊ณ„: Person์— Function์„ ์ ์šฉ (getCar ๋ฉ”์„œ๋“œ๊ฐ€ ํŽ‘์…˜)
      • flatmap ์‚ฌ์šฉ ์ด์œ : Optional์ด ๊ณ„์† ์ค‘์ฒฉ๋จ์œผ๋กœ ํ‰์ค€ํ™” ํ•œ๋‹ค.
        • ํ‰์ค€ํ™” ๊ณผ์ •: ๋‘ Optional์„ ํ•ฉ์น˜๋Š” ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ ๋‘˜ ์ค‘ ํ•˜๋‚˜๋ผ๋„ null์ด๋ฉด ๋นˆ Optional์„ ์ƒ์„ฑ
    • 2๋‹จ๊ณ„: 1๋‹จ๊ณ„์™€ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•˜๊ณ , Optional ๋ฆฌํ„ด
    • 3๋‹จ๊ณ„: ์ŠคํŠธ๋ง ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, flatmap ์‚ฌ์šฉ ํ•„์š” x

๋„๋ฉ”์ธ ๋ชจ๋ธ์— Optional์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง๋ ฌํ™”ํ•  ์ˆ˜ ์—†์ง€๋งŒ, ์ด๋Ÿฐ ๋‹จ์ ์—๋„ ์ €์ž Optional์„ ์‚ฌ์šฉํ•ด์„œ ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•˜๋‹ค๊ณ  ๋งํ•œ๋‹ค.

Optional ์ŠคํŠธ๋ฆผ ์กฐ์ž‘

  • ์ž๋ฐ”9, Optional์— stream() ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€
  • flatMap(Optional::stream): Stream<Optional>์„ ํ˜„์žฌ ์ด๋ฆ„์„ ํฌํ•จํ•˜๋Š” Stream์œผ๋กœ ๋ณ€ํ™˜
// ์‚ฌ๋žŒ๋“ค์ด ๊ฐ€์ž…ํ•œ ๋ณดํ—˜ํšŒ์‚ฌ ์ด๋ฆ„๋“ค์„ ๋ฆฌํ„ด
 public static Set<String> getCarInsuranceNames(List<Person2> persons) {
     return persons.stream()
         .map(Person2::getCar) // ์–ด๋–ค ์‚ฌ๋žŒ์€ ์ž๋™์ฐจ๋ฅผ ๊ฐ€์ง€์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๋‹ค.
         .map(optCar -> optCar.flatMap(Car2::getInsurance))
         .map(optIns -> optIns.map(Insurance2::getName))
         .flatMap(Optional::stream)
         .collect(Collectors.toSet()); // ์ค‘๋ณต ์ œ๊ฑฐ ๊ธฐ๋Šฅ๋„
 }

// ------------------------------------------------
System.out.println(getCarInsuranceNames(personList)); // [a, b, c]

// ์ด 7๋ช… ์ค‘์—, ์ฐจ ์žˆ๋Š” 4๋ช… ์ค‘ 1๋ช…๋งŒ ๋นผ๊ณ  ๋ณดํ—˜์„ ๋“ค์—ˆ๊ณ  (๋ณดํ—˜๋ช…: a,b,c), ๋‹ค๋ฅธ 3๋ช…์€ ์ฐจ๊ฐ€ ์—†๋‹ค.
 static List<Person2> personList = List.of(
     new Person2(Optional.of(new Car2(Optional.of(new Insurance2("a"))))),
     new Person2(Optional.of(new Car2(Optional.of(new Insurance2("b"))))),
     new Person2(Optional.of(new Car2(Optional.of(new Insurance2("c"))))),
     new Person2(Optional.of(new Car2(Optional.empty())))),
     new Person2(Optional.empty()),
     new Person2(Optional.empty()),
     new Person2(Optional.empty())
 );

Optional์—์„œ stream ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, Optional์˜ isPresent ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ€๋ฉฐ, ์ง์ ‘ ์ฒ˜๋ฆฌํ•ด์ค˜์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์žˆ์—ˆ์„๊ฒƒ์ด๋‹ค. (์˜ต์…”๋„์— ์ŠคํŠธ๋ฆผ ์ƒ๊ฒจ์„œ ์ข‹๋‹ค!)

๋””ํดํŠธ ์•ก์…˜๊ณผ Optional ์–ธ๋žฉ

  • Optional unwrap ๋ฐฉ๋ฒ•๋“ค
    • orElse("๋””ํดํŠธ๊ฐ’"): ๊ฐ’์ด ์—†์„๋•Œ ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ณต.
    • get(): ๊ฐ’ ๋ฐ”๋กœ ์ œ๊ณต. ๊ฐ’์ด ์—†๋‹ค๋ฉด, NoSuchElementException. ๊ทธ๋Ÿฌ๋‹ˆ ์กฐ์‹ฌ ์‚ฌ์šฉ.
    • orElseGet(): ๊ฐ’์ด ์—†์„๋•Œ๋งŒ, Supplier๊ฐ€ ์‹คํ–‰. Optional์ด ๋น„์–ด์žˆ์„ ๋•Œ๋งŒ ๊ธฐ๋ณธ๊ฐ’์„ ์ƒ์„ฑํ•˜๊ณ  ์‹ถ์„๋•Œ ์‚ฌ์šฉ.
    • orElseThrow(): get๊ณผ ๋น„์Šท
    • ifPresent(): ๊ฐ’์ด ์กด์žฌํ•  ๋•Œ ์ธ์ˆ˜๋กœ ๋„˜๊ฒจ์ค€ ๋™์ž‘์„ ์‹คํ–‰. ์—†์œผ๋ฉด ์‹คํ–‰ x.
    • ifPresentOrElse(): Optional์ด ๋น„์—ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” Runnable์„ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค๋Š” ์ ๋งŒ ifPresent์™€ ๋‹ค๋ฅด๋‹ค.
// ifPresentOrElse ์˜ˆ์‹œ
Optional<Integer> op = Optional.empty();
try {
    op.ifPresentOrElse(
        (value) -> {
            System.out.println( "Value is present, its: " + value);
        },
        () -> {
            System.out.println("Value is empty");
        });
} catch (Exception e) {
    System.out.println(e);
}
// ๊ฒฐ๊ณผ: Value is empty

๋‘ Optional ํ•ฉ์น˜๊ธฐ

๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฐ€์ •: ๊ฐ€์žฅ ๋ณดํ—˜๋ฃŒ ์ €๋ ดํ•œ ํšŒ์‚ฌ ์ฐพ๊ธฐ

// ์‚ฌ๋žŒ์— ๋”ฐ๋ผ ๋ณดํ—˜๋ฃŒ ๋‹ค๋ฅผํ…Œ๋‹ˆ,
// ๊ฐ€์žฅ ์ €๋ ดํ•œ ๋ณดํ—˜๋ฃŒ ์ฐพ๊ธฐ
public Insurance findCheapestInsurance(Person person, Car car) {
    // ๋กœ์ง: ๋‹ค์–‘ํ•œ ๋ณดํ—˜ํšŒ์‚ฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค ์กฐํšŒ ํ›„ ๋ชจ๋“  ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ ๋น„๊ต
    return cheapestCompany;
}

Optional๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฒ„์ „์„ ๊ตฌํ˜„ํ•ด์•ผ ํ• ๋•Œ

// ์•„๋ž˜ ๊ตฌํ˜„ ์ฝ”๋“œ๋Š” null ํ™•์ธ ์ฝ”๋“œ์™€ ํฌ๊ฒŒ ๋‹ค๋ฅธ ์ ์ด ์—†๋‹ค.
// ํ€ด์ฆˆ: ๊ฐœ์„ ํ•ด ๋ณด์ž
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();
    }
}

ํ€ด์ฆˆ

Optional ์–ธ๋žฉํ•˜์ง€ ์•Š๊ณ  ๋‘ Optional ํ•ฉ์น˜๊ธฐ

์ •๋‹ต

public Optional<Insurance> nullSafeFindCheapestInsurance(
    Optional<Person> person, Optional<Car> car) {
    return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}

ํ•„ํ„ฐ๋ง

๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœํ•ด์„œ ์–ด๋–ค ํ”„๋กœํผํ‹ฐ๋ฅผ ํ™•์ธํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค.

// ๋ณดํ—˜ํšŒ์‚ฌ ์ด๋ฆ„ CambridgeInsurance์ธ์ง€ ํ™•์ธ 
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
    System.out.println("ok");
}

filter ์‚ฌ์šฉ: ์กฐ๊ฑด์— ๋งž์œผ๋ฉด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋นˆ Optional ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜

// Optional์œผ๋กœ ์žฌ๊ตฌํ˜„ 
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance ->
                    "CambridgeInsurance".equals(insurance.getName()))
    .ifPresent(x -> System.out.println("ok"));]

ํ€ด์ฆˆ

Optional filtering

ํŠน์ • ๋‚˜์ด ์ด์ƒ ๋˜๋Š” ์‚ฌ๋žŒ๋“ค์ด ๊ฐ€์ž…ํ•œ ๋ณดํ—˜์„ ์ฐพ์•„์™€ ๋ณด์ž. (minAge ์ด์ƒ ์ผ๋•Œ๋งŒ)

public String getCarInsuranceName(Optional<Person> person, int minAge)

์ •๋‹ต

public String getCarInsuranceName(Optional<Person> person, int minAge) {
    return person.filter(p -> p.getAge() >= minAge)
        .flatMap(Person::getCar)
        .flatMap(Car::getInsurance)
        .map(Insurance::getName)
        .orElse("Unknown");
}

11.4 Optional์„ ์‚ฌ์šฉํ•œ ์‹ค์šฉ ์˜ˆ์ œ

๊ธฐ์กด์˜ ์ฝ”๋“œ๋ฅผ ์žฌ๊ตฌํ˜„ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋˜๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋ฒ•์„ ํ™•์ธํ•ด๋ณด์ž.

์ž ์žฌ์ ์œผ๋กœ null์ด ๋  ์ˆ˜ ์žˆ๋Š” ๋Œ€์ƒ์„ Optional๋กœ ๊ฐ์‹ธ๊ธฐ

Object value = map.get("key");
// ------------------------------------
// get์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๊ณ ์น  ์ˆœ ์—†์ง€๋งŒ, ๋ฐ›์€๊ฐ’์„ ๊ฐ์Œ€ ์ˆ˜ ์žˆ๋‹ค. 
Optional<Object> value = Optional.ofNullable(map.get("key"));

์˜ˆ์™ธ์™€ Optional ํด๋ž˜์Šค

  • Integer.parseInt()๋Š” ๊ฐ’์„ ์ œ๊ณตํ•  ์ˆ˜ ์—†์„ ๋•Œ null์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
    • e.g. Integer.parseInt("hello") -> NumberFormatException ์—๋Ÿฌ
// ์ด์™€ ๊ฐ™์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅด ๋งŽ์ด ๋งŒ๋“ค์ž!
public static Optional<Integer> stringToInt(String s) {
    try {
        return Optional.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

// stringToInt("hello") -> ์—๋Ÿฌ ๋‚˜์ง€ ์•Š๋Š”๋‹ค. ์˜ต์…”๋„์˜ empty๋ฅผ ๋ฐ›๋Š”๋‹ค.

๊ธฐ๋ณธํ˜• Optional์„ ์‚ฌ์šฉํ•˜์ง€ ๋ง์•„์•ผ ํ•˜๋Š” ์ด์œ 

  • Optionallnt, OptionalLong, OptionalDouble์€ ์‚ฌ์šฉ ์ž์ œํ•˜๊ธฐ.
    • map, flatMap,filter ๋“ฑ์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • ๊ธฐ๋ณธํ˜•์€ ๊ธฐ์กด Optional ํ˜ผ์šฉ ์‚ฌ์šฉ ๋ถˆ๊ฐ€
      • ์˜ˆ, OptionalInt๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด ์ด๋ฅผ ๋‹ค๋ฅธ Optional ์˜ flatMap์— ๋ฉ”์„œ๋“œ ์ฐธ์กฐ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์—†๋‹ค.

์‘์šฉ

์‹ค์ œ ์—…๋ฌด์—์„œ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๋ณด์ž.

// ํ”„๋กœ๊ทธ๋žจ ์„ค์ • ์ธ์ˆ˜๋กœ ํ”„๋กœํผํ‹ฐ ์ „๋‹ฌ ์˜ˆ์ œ 
// ํ‚ค๋ฅผ ์ฃผ๊ณ  ์•ˆ์— ๊ฐ’์ด ์–‘์ˆ˜์ธ๊ฒƒ๋งŒ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค์ž. otherwise 0
@Test
public void testMap() {
    Properties props = new Properties();
    props.setProperty("a", "5");
    props.setProperty("b", "true");
    props.setProperty("c", "-3");

    assertEquals(5, readDurationImperative(props, "a"));
    assertEquals(0, readDurationImperative(props, "b"));
    assertEquals(0, readDurationImperative(props, "c"));
    assertEquals(0, readDurationImperative(props, "d"));
}

public static int readDurationImperative(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๋กœ ํ”„๋กœํผํ‹ฐ์—์„œ ์ง€์† ์‹œ๊ฐ„ ์ฝ๊ธฐ

@Test
public void testMap() {
    Properties props = new Properties();
    props.setProperty("a", "5");
    props.setProperty("b", "true");
    props.setProperty("c", "-3");

    assertEquals(5, readDurationWithOptional(props, "a"));
    assertEquals(0, readDurationWithOptional(props, "b"));
    assertEquals(0, readDurationWithOptional(props, "c"));
    assertEquals(0, readDurationWithOptional(props, "d"));
}

public static int readDurationWithOptional(Properties props, String name) {
    return Optional.ofNullable(props.getProperty(name))
        .flatMap(OptionalUtility::stringToInt)
        .filter(i -> i > 0)
        .orElse(0);
}
โš ๏ธ **GitHub.com Fallback** โš ๏ธ