본문 바로가기
TIL/JAVA

23.05.27

by J1-H00N 2023. 5. 27.

wait()과 notify() 예시

더보기
package sparta_nbc.Syntax.thread.waitingnotify;

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static String[] itemList = {
            "MacBook", "IPhone", "AirPods", "iMac", "Mac mini"
    };
    public static AppleStore appleStore = new AppleStore();
    public static final int MAX_ITEM = 5;

    public static void main(String[] args) {

        // 가게 점원
        Runnable StoreClerk = () -> {
            while (true) {
                // 0 ~ 4 랜덤 int
                int randomItem = (int) (Math.random() * MAX_ITEM);

                // 랜덤한 위치에 재고를 넣는 메서드
                appleStore.restock(itemList[randomItem]);
                try {
                    Thread.sleep(50); // 0.05초 정지
                } catch (InterruptedException ignored) {
                }
            }
        };

        // 고객
        Runnable Customer = () -> {
            while (true) {
                try {
                    Thread.sleep(77); // 0.077초 정지
                } catch (InterruptedException ignored) {
                }

                // 랜덤한 제품 고르기
                int randomItem = (int) (Math.random() * MAX_ITEM);

                // 그 제품을 판매하는 메서드
                appleStore.sale(itemList[randomItem]);
                System.out.println(Thread.currentThread().getName() + " Purchase Item " + itemList[randomItem]);
            }
        };


        new Thread(StoreClerk, "StoreClerk").start();
        new Thread(Customer, "Customer1").start();
        new Thread(Customer, "Customer2").start();

    }
}

class AppleStore {
    private List<String> inventory = new ArrayList<>();

    public void restock(String item) {
        synchronized (this) { // 임계 영역
            while (inventory.size() >= Main.MAX_ITEM) {
                System.out.println(Thread.currentThread().getName() + " Waiting!");
                try {
                    wait(); // 재고가 꽉 차있어서 재입고하지 않고 기다리는 중!
                    Thread.sleep(333);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 재입고
            inventory.add(item);
            notify(); // 재입고 되었음을 고객에게 알려주기
            System.out.println("Inventory 현황: " + inventory.toString());
        }
    }

    public synchronized void sale(String itemName) {
        while (inventory.size() == 0) {
            System.out.println(Thread.currentThread().getName() + " Waiting! 1"); // 아래 wait과 구분하기 위해 넘버링
            try {
                wait(); // 재고가 없기 때문에 고객 대기중
                Thread.sleep(333);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        while (true) {
            // 고객이 주문한 제품이 있는지 확인
            for (int i = 0; i < inventory.size(); i++) {
                if (itemName.equals(inventory.get(i))) { // 리스트 안에 원하는 제품이 있으면
                    inventory.remove(itemName); // 구매
                    notify(); // 제품 하나 팔렸으니 재입고 하라고 알려주기
                    return; // 메서드 종료
                }
            }

            // 고객이 찾는 제품이 없을 경우
            try {
                System.out.println(Thread.currentThread().getName() + " Waiting! 2");
                wait();
                Thread.sleep(333);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

위 코드의 로직 순서 :

1. StoreClerk, Customer1, Customer2 쓰레드 3개 동시 시작

2. Customer 그룹은 0.077초 정지, StoreClerk 그룹은 0.05초 정지이미로 StoreClerk 쓰레드가 먼저 작동

3 - 1. restock 메서드 작동, 제품이 5개 미만이면 Lock 받고 임의의 제품 추가하고 Inventory 현황 출력

3 - 2. 제품이 이미 5개면 Lock 반납하고 임의의 Customer 호출

4. 임의의 Customer는 사고싶은 물건을 정하고 Inventory확인

5 - 1. 원하는 제품이 있으면 Inventory에서 해당 제품 제거하고 StoreClerk 또는 다른 Customer 호출

5 - 1- 1. 다른 Customer 호출하면 4로 이동

5 - 1- 2. StoreClerk가 호출되면 3으로 이동

5 - 2. 원하는 제품이 없으면 현재 쓰레드 이름 + Waiting 출력하고 0.333초 대기

6. 3 ~ 5 반복

7. 제품이 5개가 되면 StoreClerk 대기, 그 안에 Customer가 원하는 제품이 없으면 Customer도 대기

=> 무한 대기 상태(병목 현상)

3개의 쓰레드가 동시에 작동하기에 위와 같이 일렬로 나타낸 건 편의를 위한 큰 흐름일 뿐 실제론 sleep에 맞춰 3개가 각자 따로 움직이다가 notify를 통해 대기 상태에서 강제적으로 작동하는 것이다.

그리고 위와 같이 병목 현상이 일어나는 것은 notify가 "임의의" 쓰레드를 호출하기 때문에 발생하는 현상이다. 따라서 우리는 notify가 어떤 쓰레드를 호출할지 명시할 필요가 있다.

 

위에서처럼 notify가 임의의 쓰레드를 호출하기에 생기는 문제와, 앞서 계속 언급했던 Lock이 무엇인지 알아보자.

 

Synchronized를 이용하면 자동으로 Lock이 걸리고 풀리지만, 같은 메서드 내에서만 Lock을 걸 수 있다는 제약이 있었다.

이를 해결위해 Lock 클래스를 이용한다.

  • ReentrantLock
    • 가장 일반적인 배타 Lock
    • 특정 조건에서 Lock을 풀고, 다시 Lock을 얻어 임계영역으로 재진입 가능
    • methodA는 lock1을 가지고, methodB는 lock2를 가지고 있다. (아래 코드 참고)
    • methodB는 methodA를 호출하므로 lock2를 가진 상태로 methodA에 있는 lock1을 가지려 한다.
    • 하지만 lock1은 이미 methodA에게 있어 methodA는 lock2를 요청하고, methodB는 lock1을 요청하는 데드락 상태가 될 가능성이 있다.
    • 이 상황에서 ReentrantLock을 사용하면 한 쓰레드가 이미 락을 가지고 있더라도 그 락을 유지하며 실행할 수 있기 때문에 데드락이 발생하지 않는다.
    • 즉, ReentrantLock을 사용하면 코드의 유연성이 증가한다.
public class MyClass {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    
    public void methodA() {
        synchronized (lock1) {
            methodB();
        }
    }
    
    public void methodB() {
        synchronized (lock2) {
            // do something
            methodA();
        }
    }
}
  • ReentrantReadWriteLock
    • 읽기를 위한 Lock과 쓰기를 위한 Lock을 따로 제공
    • 일기에는 공유적이고 쓰기(수정)에는 배타적인 Lock이다
    • 일기에 Lock이 걸려있으면 다른 쓰레드들은 Lock을 가진 상태로도 중복으로 Lock을 걸어 읽을 수 있다.
    • 읽기 Lock이 걸려있는 상태에서 쓰기 Lock을 거는 것은 불가능
  • StampedLock
    • 위 ReentrantReadWriteLock에 낙관적인 Lock을 추가한 것
      • 낙관적인 Lock : 데이터를 변경하기 전에는 Lock을 걸지 않는 것
      • 낙관적인 Lock은 데이터를 수정할 때 충돌이 발생할 가능성이 적을 때 적합
      • 읽기 작업을 빠르게 수행하고 쓰기 작업을 수행할 때는 쓰기 작업이 발생하기 전에 이미 변경이 된 경우 다시 읽고 쓰기를 수행하기 때문에 변경이 빈번히 일어나지 않는 경우에는 더 빠른 작업이 가능하다.
    • 낙관적인 읽기 Lock은 쓰기 Lock에 바로 해제되고, 읽기 Lock을 무조건 걸지 않고 쓰기와 읽기 Lock이 충돌할 때만 쓰기 후 읽기 Lock을 건다.

notify가 waiting pool에서 "임의의" 쓰레드를 호출해 문제가 발생하는 것을 해결해 주는 것이 Condition이다.

JDK 5에서 제공하는 Condition 인터페이스는 특정 조건이 만족될 때만 쓰레드를 깨울 수 있으며, ReentrantLock 클래스와 함께 사용된다.

 

Condition은 wait(), notify() 대신 await(), signal()을 사용한다.

↓예시

더보기
private ReentrantLock lock = new ReentrantLock();

// lock으로 condition 생성
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();

private ArrayList<String> tasks = new ArrayList<>();

// 작업 메서드
public void addMethod(String task) {
		lock.lock(); // 임계영역 시작

		try {
			while(tasks.size() >= MAX_TASK) {
					String name = Thread.currentThread().getName();
					System.out.println(name+" is waiting.");
					try {
						condition1.await(); // wait(); condition1 쓰레드를 기다리게 합니다.
						Thread.sleep(500);
					} catch(InterruptedException e) {}	
			}

			tasks.add(task);
			condition2.signal(); // notify();  기다리고 있는 condition2를 깨워줍니다.
			System.out.println("Tasks:" + tasks.toString());
		} finally {
			lock.unlock(); // 임계영역 끝
		}
	}

 

 

자바 8

자바는 시대가 변하면서 빅데이터, AI같은 문제가 화두에 오르고, 기존의 java로서는 해당 기술들을 구현하기 적절치 않은 언어였다. 그래서 병렬처리, 함수형 프로그래밍(ex. 람다식) 등의 기능을 필요로 하게 되었다.

함수형 프로그래밍 - 함수형 프로그램의 의의는 수학의 함수처럼 결과값이 오직 입력값에 영향을 받는 함수, 즉 순수한 함수에 있다. 순수한 함수는 검증이 쉬우며, 성능 최적화가 쉽고, 문제를 해결하기 쉽다.

 

람다

  • 익명 함수를 지칭하는 말 ( () -> {} )
  • 익명 함수는 말 그대로 이름이 없는 함수를 말하며, 일급 객체로 취급받는다.
  • 함수를 값으로 사용할 수 있다.
  • 변수에 대입하기 같은 연산들이 가능해진다.

스트림

  • 간단하게 collection의 반복을 처리해주는 기능이자, 멀티쓰레드 관련 코드를 구현하지 않아도 알아서 병렬로 처리해주는 기능

람다, 스트림 사용 예시

↓사용 전

더보기
package sparta_nbc.Syntax.modernJava.LambdaAndStream;

import java.util.ArrayList;
import java.util.List;

// 주차장 예제
// 티켓 or 파킹머니(주차비) -> 주차 가능
public class LambdaAndStream {
    public static void main(String[] args) {
        // 주차 대상 차량
        ArrayList<Car> carsWantToPark = new ArrayList<>();
        // 주차장
        ArrayList<Car> parkingLot = new ArrayList<>();

        // 5개의 Car instance
        Car car1 = new Car("Benz", "Class E", true, 0);
        Car car2 = new Car("BMW", "Series 7", false, 100);
        Car car3 = new Car("BMW", "X9", false, 0);
        Car car4 = new Car("Audi", "A7", true, 0);
        Car car5 = new Car("Hyundai", "Ionic 6", false, 10000);

        carsWantToPark.add(car1);
        carsWantToPark.add(car2);
        carsWantToPark.add(car3);
        carsWantToPark.add(car4);
        carsWantToPark.add(car5);

        parkingLot.addAll(parkingCarWithTicket(carsWantToPark));
        parkingLot.addAll(parkingCarWithMoney(carsWantToPark));


        for (Car car : parkingLot) {
            System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
        }


    }

    public static List<Car> parkingCarWithTicket(List<Car> carsWantToPark) {
        // return할 Car 선언
        ArrayList<Car> cars = new ArrayList<>();

        // 매개 변수에 있는 car 내부에 ParkingTicket 여부 확인
        for (Car car : carsWantToPark) {
            if (car.hasParkingTicket()) { // 있다면 임시 ArrayList cars에 추가
                cars.add(car);
            }
        }

        return cars;
    }

    public static List<Car> parkingCarWithMoney(List<Car> carsWantToPark) {
        ArrayList<Car> cars = new ArrayList<>();

        // 매개 변수에 있는 car 내부에 ParkingMoney가 1000 초과인지 확인
        for (Car car : carsWantToPark) {
            if (!car.hasParkingTicket() && car.getParkingMoney() > 1000) {
                cars.add(car);
            }
        }

        return cars;
    }
}

class Car {
    private final String company; // 자동차 회사
    private final String model; // 자동차 모델

    private final boolean hasParkingTicket;
    private final int parkingMoney;

    public Car(String company, String model, boolean hasParkingTicket, int parkingMoney) {
        this.company = company;
        this.model = model;
        this.hasParkingTicket = hasParkingTicket;
        this.parkingMoney = parkingMoney;
    }

    public String getCompany() {
        return company;
    }

    public String getModel() {
        return model;
    }

    public boolean hasParkingTicket() {
        return hasParkingTicket;
    }

    public int getParkingMoney() {
        return parkingMoney;
    }
}

↓사용 후

더보기
package sparta_nbc.Syntax.modernJava.LambdaAndStream;

import java.util.ArrayList;
import java.util.List;

// 주차장 예제
// 티켓 or 파킹머니(주차비) -> 주차 가능
public class LambdaAndStream {
    public static void main(String[] args) {
        // 주차 대상 차량
        ArrayList<Car> carsWantToPark = new ArrayList<>();
        // 주차장
        ArrayList<Car> parkingLot = new ArrayList<>();

        // 주말 주차장 : 돈과 티켓 둘 다 있어야함
        ArrayList<Car> weekendParkingLot = new ArrayList<>();

        // 5개의 Car instance
        Car car1 = new Car("Benz", "Class E", true, 0);
        Car car2 = new Car("BMW", "Series 7", false, 100);
        Car car3 = new Car("BMW", "X9", false, 0);
        Car car4 = new Car("Audi", "A7", true, 0);
        Car car5 = new Car("Hyundai", "Ionic 6", false, 10000);

        carsWantToPark.add(car1);
        carsWantToPark.add(car2);
        carsWantToPark.add(car3);
        carsWantToPark.add(car4);
        carsWantToPark.add(car5);

//        parkingLot.addAll(parkingCarWithTicket(carsWantToPark));
        parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket)); // 매개변수로 함수를 지정하는 법 ::~~~

//        parkingLot.addAll(parkingCarWithMoney(carsWantToPark));
        parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));

        // 익명함수 적용
        parkingLot.addAll(parkCars(carsWantToPark, (Car car)->car.hasParkingTicket() && car.getParkingMoney() > 1000));
        // 위와 같이 로직이 간단하면 {}도 생략 가능하다.

        for (Car car : parkingLot) {
            System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
        }


    }

//    public static List<Car> parkingCarWithTicket(List<Car> carsWantToPark) {
//        // return할 Car 선언
//        ArrayList<Car> cars = new ArrayList<>();
//
//        // 매개 변수에 있는 car 내부에 ParkingTicket 여부 확인
//        for (Car car : carsWantToPark) {
//            if (car.hasParkingTicket()) { // 있다면 임시 ArrayList cars에 추가
//                cars.add(car);
//            }
//        }
//
//        return cars;
//    }
//
//    public static List<Car> parkingCarWithMoney(List<Car> carsWantToPark) {
//        ArrayList<Car> cars = new ArrayList<>();
//
//        // 매개 변수에 있는 car 내부에 ParkingMoney가 1000 초과인지 확인
//        for (Car car : carsWantToPark) {
//            if (!car.hasParkingTicket() && car.getParkingMoney() > 1000) {
//                cars.add(car);
//            }
//        }
//
//        return cars;
//    }

    // 위의 두 메서드를 하나로 : 내부 주요 로직을 함수로 전달 받자
    public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
        List<Car> cars = new ArrayList<>();

        for (Car car : carsWantToPark) {
            // 전달된 함수를 사용하여 구현
            if (function.test(car)) {
                cars.add(car);
            }
        }

        return cars;
    }
}

class Car {
    private final String company; // 자동차 회사
    private final String model; // 자동차 모델

    private final boolean hasParkingTicket;
    private final int parkingMoney;

    public Car(String company, String model, boolean hasParkingTicket, int parkingMoney) {
        this.company = company;
        this.model = model;
        this.hasParkingTicket = hasParkingTicket;
        this.parkingMoney = parkingMoney;
    }

    public String getCompany() {
        return company;
    }

    public String getModel() {
        return model;
    }

    public boolean hasParkingTicket() {
        return hasParkingTicket;
    }

    public int getParkingMoney() {
        return parkingMoney;
    }

    public static boolean hasTicket(Car car) {
        return car.hasParkingTicket;
    }

    public static boolean noTicketButMoney(Car car) {
        return !car.hasParkingTicket && car.getParkingMoney() > 1000;
    }
}


// 메서드의 타입을 결정해주는 것 -> 인터페이스
// 인터페이스는 타입 역할을 할 수 있기 때문
// 함수형 인터페이스 : 추상 메서드를 딱 하나만 가지고 있음
//    public exampleMethod (int parameter1, ??? parameterfunction) {
//        parameterfunction
//    }

interface Predicate<T> { // 주차 가능 여부를 검증하는 역할
    boolean test(T t); // 후에 티켓 여부, 주차비 여부를 확인할 것이기 때문에 boolean
}

람다 함수 문법

// 기본적으로 문법은 다음과 같습니다.
(파라미터 값, ...) -> { 함수 몸체 }

// 아래의 함수 두개는 같은 함수입니다.
// 이름 반환타입, return문 여부에 따라 {}까지도 생략이 가능합니다.
public int toLambdaMethod(int x, int y) {
	return x + y;
}

(x, y) -> x + y
아래와 동일
(x, y) -> {
    return x + y;
}

// input이 없으면 아래와 같은 함수도 가능
public int toLambdaMethod2() {
	return 100;
}

() -> 100

// 모든 유형의 함수에 가능합니다.
public void toLambdaMethod3() {
	System.out.println("Hello World");
}

() -> System.out.println("Hello World")

 

스트림 더 자세히

자료구조의 흐름을 객체로서 제공해주고 그 흐름동안 사용할 수 있는 메서드들을 api형태로 제공

 

스트림의 특징

  • 원본의 데이터를 변경하지 않는다.
  • 일회용 -> 한 번 사용한 스트림은 어디에도 남지 않는다.

스트림 사용 예시

더보기
List<Car> benzParkingLot =
	// carsWantToPark의 스트림값을 받아와서
	carsWantToPark.stream()
		// 거기 구현되어 있는 filter()메서드를 사용합니다.
		// filter메서드는 함수를 파라미터로 전달받습니다.
		// 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요.
		// 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다.
		.filter((Car car) -> car.getCompany().equals("Benz"))
		// 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다.
		.toList();
ArrayList<Car> benzParkingLotWithoutStream = new ArrayList<>();

for (Car car : carsWantToPark) {
    if (car.getCompany().equals("Benz")) {
        benzParkingLotWithoutStream.add(car);
    }
}

위 두 코드는 같은 일을 하고 있다.

 

.stream()을 통해 스트림을 받아오고

.firter~~을 통해 스트림을 가공하고

.toList()을 통해 스트림 결과를 만들었다.

  • 스트림 api : 너무 많고 방대해서 아래에서는 많이 쓰이는 위에서 본 filter()와 forEach(), map()만 짚고 넘어간다
    • forEach()
      • 각각의 원소에 넘겨받은 함수를 실행한다.
      • 하지만 반환값에 어떤 작업을 하진 않고, 한다고 해도 무시된다.
    • map()
      • forEach()와는 반대로 반환값을 토대로 값을 변환시키는데 주로 이용된다.

↓forEach() 예시

더보기
List<String> carNames = Arrays.asList("Series 6", "A9", "Ionic 6");

carNames.stream()
    .forEach(System.out::println);

// 결과 
// Series 6
// A9
// Ionic 6

↓map() 예시

더보기
carNames.stream()
	.map(name -> name.toUpperCase()).toList();

// 결과
// ["SERIES 6", "A9", "IONIC 6"]

스트림 api

https://www.baeldung.com/java-8-streams

 

null

null이 나쁜 예시

public class NullIsDanger {
    public static void main(String[] args) {

        SomeDBClient myDB = new SomeDBClient();
        
        String userId = myDB.findUserIdByUsername("HelloWorldMan");

        System.out.println("HelloWorldMan's user Id is : " + userId);
    }
}

class SomeDBClient {

    public String findUserIdByUsername(String username) {
        // ... db에서 찾아오는 로직
				String data = "DB Connection Result";

        if (data != null) {
            return data;
        } else {
            return null;
        } 
    }
    
}

위 코드는 null을 반환할 수 있음에도, null이 반환될 수 있음을 명시하지 않았다. 위에서 만약 null을 반환한다면,

nullPointerException이 발생한다.

 

해결방법1. null이 반환될 수 있음을 명시하고, 메서드 사용자가 조심하기

더보기
public class NullIsDanger {
    public static void main(String[] args) {

        SomeDBClient myDB = new SomeDBClient();

        String userId = myDB.findUserIdByUsernameOrThrowNull("HelloWorldMan");
        // 개선 1: null이 반환 될 수 있음을 인지한 메서드 사용자는, null을 대비합니다.
        if (userId != null) {
            System.out.println("HelloWorldMan's user Id is : " + userId);
        }
    }
}

class SomeDBClient {
    // 개선 1: 이 메서드는 null이 반환 될 수 있음을 명시합니다.
    public String findUserIdByUsernameOrThrowNull(String username) {
        // ... db에서 찾아오는 로직
				String data = "DB Connection Result";

        if (data != null) {
            return data;
        } else {
            return null;
        }
    }

}

하지만 만약 누군가 null체크를 하지 않는다면, 계속해서 오류가 발생할 것이다.

 

해결방법2. 객체를 감싸서 보호하기

더보기
// 개선 2: 결과값을 감싼 객체를 만듭니다.
class SomeObjectForNullableReturn {
    private final String returnValue;
    private final Boolean isSuccess;

    SomeObjectForNullableReturn(String returnValue, Boolean isSuccess) {
        this.returnValue = returnValue;
        this.isSuccess = isSuccess;
    }

    public String getReturnValue() {
        return returnValue;
    }

    public Boolean isSuccess() {
        return isSuccess;
    }
}

public class NullIsDanger {
    public static void main(String[] args) {

        SomeDBClient myDB = new SomeDBClient();

        // 개선 2 : 이제 해당 메서드를 사용하는 유저는, 객체를 리턴받기 때문에 더 자연스럽게 성공여부를 체크하게 됩니다.
        SomeObjectForNullableReturn getData = myDB.findUserIdByUsername("HelloWorldMan");

        if (getData.isSuccess()) {
            System.out.println("HelloWorldMan's user Id is : " + getData.getReturnValue());
        }
    }
}

class SomeDBClient {
    // 개선 2 : 결과값을 감싼 객체를 리턴합니다.
    public SomeObjectForNullableReturn findUserIdByUsername(String username) {
        // ... db에서 찾아오는 로직
        String data = "DB Connection Result";

        if (data != null) {
            return new SomeObjectForNullableReturn(data, true);
        } else {
            return new SomeObjectForNullableReturn(null, false);
        }
    }

}

이러면 null이 반환 됐을 때 isSuccess가 false를 반환해 오류가 발생하지 않고, 사용자는 더 쉽게 이 메서드가 예외가 발생할 수 있다는 것을 알 수 있을 것이다.

 

해결방법3. 해결방법2를 더 발전시키기

더보기
class SomeObjectForNullableReturn<T> {
    private final T returnValue;
    private final Boolean isSuccess;
   
    

    SomeObjectForNullableReturn(T returnValue, Boolean isSuccess) {
        this.returnValue = returnValue;
        this.isSuccess = isSuccess;
    }

    public T getReturnValue() {
        return returnValue;
    }

    public Boolean isSuccess() {
        return isSuccess;
    }
    
}

감싸는 객체를 조금 손보면 이제 예외가 발생할 수 있는 모든 메서드에 사용할 수 있게 된다.

 

해결방법3의 아이디어를 발전시킨 것이 java.util.Optional 객체 입니다.

 

Optional 간단 사용법

  • 값이 null 인 Optional 생성하기
Optional<Car> emptyOptional = Optional.empty();
  • 값이 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.of(new Car());
  • 값이 있을수도 없을수도 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());
  • Optional 객체 사용하기 (값 받아오기)
Optional<String> carName = getCarNameFromDB();
// orElse() 를 통해 값을 받아옵니다, 파라미터로는 null인 경우 반환할 값을 적습니다.
String realCarName = carName.orElse("NoCar");

// 위는 예시코드고 실제는 보통 아래와 같이 사용하겠죠?
String carName = getCarNameFromDB().orElse("NoCar");

// orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있습니다.
// 파라미터로는 없는 경우 실행될 함수를 전달합니다.
Car car = getCarNameFromDB().orElseGet(Car::new);

// 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있습니다.
Car car = getCarNameFromDB()
	.orElseThrow(() -> new CarNotFoundException("NO CAR!)

 

이걸로 5주차 강의를 마쳤다. 내일부턴 개인과제 <키오스크 만들기>를 진행한다.

'TIL > JAVA' 카테고리의 다른 글

23.06.08  (0) 2023.06.08
23.06.06  (0) 2023.06.06
23.05.26  (0) 2023.05.26
23.05.25  (0) 2023.05.25
23.05.24  (0) 2023.05.24