[학습 목표]
- 프로세스 : 운영체제로부터 자원을 할당받는 작업의 단위
- 쓰레드 : 프로세스가 할당받은 자원을 이용하는 실행의 단위
실행 중인 프로그램
프로세스내에서 일하는 일꾼(코드실행의 흐름)
[Java Thread]
JVM 프로세스 안에서 실행되는 쓰레드
생성과 함께 Java Main 쓰레드부터 실행!!
Java는 메인 쓰레드가 main() 메서드를 실행시키면서 시작이 됩니다.
프로세스 안에 하나의 쓰레드만 실행
Java=> main()메서드만 실행시킴->싱글 스레드
메인 쓰레드 종료되면 JVM 같이 종료
프로세스 안에서 여러 개의 쓰레드가 실행되는 것!
Java프로그램은 메인쓰레드외에 다른 작업 쓰레드를 생성(메인 쓰레드 안에서)
=> 안정적으로 컨트롤가능한 기법 사용해서 문제없이 효율적인 병렬,동시 처리 가능
(실제로 Java에서 어떻게 구현??)
1.Thread 클래스 상속받아서 run() 오버라이딩-> main에서 start()
//1. Thread 클래스 이용(상속)
public class TestThread extends Thread{
@Override
public void run() {
//실제 우리가 쓰레드에서 수행할 작업!
for(int i=0;i<100;i++){
System.out.print("*");
}
}
}
public class Main {
//메인 스레드 구동
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
}
}
public class TestRunnable implements Runnable{
@Override
public void run() {
//쓰레드에서 수행할 작업 정의!
for(int i=0;i<100;i++){
System.out.print("$");
}
}
}
public class Main {
//메인 스레드 구동
public static void main(String[] args) {
Runnable run= new TestRunnable();
Thread thread=new Thread(run);
thread.start();
}
}
public class Main {
public static void main(String[] args) {
//람다식 (익명 합수) -> run에 적었던 내용 {} 안에 적어준다!
Runnable task = () -> {
int sum = 0;
for (int i = 0; i < 50; i++) {
sum += i;
System.out.println(sum);
}
System.out.println(Thread.currentThread().getName() + " 최종 합 : " + sum);
};
Thread thread1 = new Thread(task);//runnabale 객체
thread1.setName("thread1");//쓰레드의 이름 정의
Thread thread2 = new Thread(task);
thread2.setName("thread2");
//병렬적으로 실행 thread 1 구동중에 thread2가 참여!
thread1.start();
thread2.start();
}
}
실습!
public class Main {
public static void main(String[] args) {
//첫번쨰
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
System.out.print("$");
}
};
//두번째
Runnable task2 = () -> {
for (int i = 0; i < 100; i++) {
System.out.print("*");
}
};
Thread thread1 = new Thread(task);
thread1.setName("thread1");
Thread thread2 = new Thread(task2);
thread2.setName("thread2");
thread1.start();
thread2.start();
//출력 시 ***$$$$***$$$$ 이런식으로됨
//순서 보장X 할때마다 다른 결과
//걸리는 시간이나 동작 예측 할 수 없다!(안정적X)
}
}
보이지않는 곳(background)에서 실행되는 낮은 우선순위 가진 쓰레드!
보조적인 역할 담당
대표적인 데몬 쓰레드 : 가비지 컬렉터(CG) - 메모리 영역 정리
public class Main {
public static void main(String[] args) {
Runnable demon = () -> {
for (int i = 0; i < 1000000; i++) {
System.out.println(i+"번째 demon");
}
};
Thread thread = new Thread(demon);
/*중요 : 우선순위가 낮다! -> 상대적으로 다른 쓰레드에 비해 리소스 적게 할당 받음-> 느림*/
thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨
thread.start();
// 100만번 출력 안되고 중간에 끊김 !
for (int i = 0; i < 100; i++) {
System.out.println(i+"번째 task");
}
//main 쓰레드라 100번 출력다됨
}
}
명시적으로(보이는 곳에서,foreground) 쓰레드를 만들어 실행시킴!
높은 우선순위를 가진 쓰레드!
기존에 만들었던 쓰레드!
멀티 쓰레드-> 쓰레드 마다 작업 중요도에 따라 우선순위 지정 -> 작업시간 많이 부여(리소스 많이 할당) ->빠르게 처리
- 최대 우선순위 (MAX_PRIORITY) = 10
- 최소 우선순위 (MIN_PRIORITY) = 1
- 보통 우선순위 (NROM_PRIORITY) = 5
- 기본 값이 보통 우선순위입니다.
- 1~10 사이의 숫자로 지정
- 이 우선순위의 범위는 OS가 아니라 JVM에서 설정한 우선순위입니다.
setPriority(숫자) 메서드로 설정getPriority()로 우선순위를 반환
public class Main {
public static void main(String[] args) {
Runnable task1 = () -> {
for (int i = 0; i < 100; i++) {
System.out.print("$");
}
};
Runnable task2 = () -> {
for (int i = 0; i < 100; i++) {
System.out.print("*");
}
};
Thread thread1 = new Thread(task1);
thread1.setPriority(8);//우선 순위 할당
int threadPriority = thread1.getPriority();//get으로 반환
System.out.println("threadPriority = " + threadPriority);
Thread thread2 = new Thread(task2);
thread2.setPriority(2);//우선 순위 할당
//thread1 -> thread2
// 더 빨리 끝날 가능 성이 높다 (완벽하게 보장X)
// 차이 별로 안남(가벼운 작업들이라)-> 무거운 작업 시에 빛을 발함.
thread1.start();
thread2.start();
}
}
쓰레드를 특성마다 묶어서 관리(한꺼번에 종료, 멈춤 등)
public class Main {
public static void main(String[] args) {
//
Runnable task = () -> {
// thead.interrupt(); 시행 전까지
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
break;
}
}
System.out.println(Thread.currentThread().getName() + " Interrupted");
};
// ThreadGroup 클래스로 객체를 만듭니다.
ThreadGroup group1 = new ThreadGroup("Group1");
// Thread 객체 생성시 첫번째 매개변수로 넣어줍니다.
// Thread(ThreadGroup group, Runnable target, String name)
// THrad 셍성자가 여러개라 구현 가능
Thread thread1 = new Thread(group1, task, "Thread 1");
Thread thread2 = new Thread(group1, task, "Thread 2");
// Thread에 ThreadGroup 이 할당된것을 확인할 수 있습니다.
System.out.println("Group of thread1 : " + thread1.getThreadGroup().getName());
System.out.println("Group of thread2 : " + thread2.getThreadGroup().getName());
thread1.start();
thread2.start();
try {
// 현재 쓰레드를 지정된 시간(5초)동안 멈추게 합니다.
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 만듭니다.
group1.interrupt();
//한 꺼번에 일시정지
}
}
Thread.sleep();
Thread.interrupt();
일시정지, 종료 => 상태 존재 ,제어가능


쓰레드의 상태


실행중 -> 일시정지
sleep(1000): 현재 쓰레드를 지정된 시간동안 멈추게 함(TIMED WAITING)
- 1000msc = 1sec
- 예외처리 필수 (try - catch)
- static 메소드 이므로 Thread.sleep()으로 사용!(객체 지정 불가)
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
// (1)예외처리 필수
// sleep 중에 interrupt()호출 -> (실행 대기 상태)
// sleep이 깨지면 InterruptedException 발생할 수 있음
try {
//(2) 특정 쓰레드 지정 불가 (static 메서드)
Thread.sleep(2000);// TIMED_WAITING(주어진 시간동안만 기다리는 상태)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task : " + Thread.currentThread().getName());// 2초후 Thread 출력
};
Thread thread = new Thread(task, "Thread");
thread.start();
try {
// thread.sleep(1000);//객체로 만든 것 잠재움
// 특정 쓰레드 지정 불가 (static)
Thread.sleep(1000);//메인 쓰레드가 잠잔다!
System.out.println("sleep(1000) : " + Thread.currentThread().getName());//1초 후 main 출력
} catch (InterruptedException e) {
e.printStackTrace();
}
}
<출력!>
1초 : sleep(1000) : main
2초 : Thread
}
interrupt() : 일시정지 상태인 쓰레드를 실행대기 상태로 보내다!public class Main {
public static void main(String[] args) {
Runnable task = () -> {
// interrupt() 호출 시 while문 통과 X -> catch문안거침
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
break;
}
}
System.out.println("task : " + Thread.currentThread().getName());
};
Thread thread = new Thread(task, "Thread");
thread.start();
thread.interrupt();
//thread.isInterrupted()) 일시 정지 상태인지 확인
System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
}
출력: sleep 안하고 바로 출력
task : Thread
thread.isInterrupted() = true
join () : 정해진 시간동안 지정한 쓰레드가 작업하는 것을 기다림!main은 thread1이 끝날 때 까지 기다려 주지 않음!
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
try {
Thread.sleep(5000); // 5초
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(task, "thread");
thread.start();
long start = System.currentTimeMillis();// 0초
try {
thread.join();//끝날때 까지 기다림!
} catch (InterruptedException e) {
e.printStackTrace();
}
// thread 의 소요시간인 5000ms 동안 main 쓰레드가 기다렸기 때문에 5000이상이 출력됩니다.
System.out.println("소요시간 = " + (System.currentTimeMillis() - start));//5011
}
}
yield():public class Main {
public static void main(String[] args) {
Runnable task = () -> {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
}
} catch (InterruptedException e) {
Thread.yield();//남은 시간 양보한다.
}
};
Thread thread1 = new Thread(task, "thread1");//10초간 이름 출력
Thread thread2 = new Thread(task, "thread2");//10초간 이름 출력
thread1.start();//new->Runnable
thread2.start();//new->Runnbale
try {
Thread.sleep(5000);//5초
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();//thread1 실행대기
//출력
//thread1
//thread2-1초
//thread1
//thread2-2초
//thread1
//thread2-3초
//thread1
//thread2-4초
//thread2-5
//thread2-6
//thread2-7
//thread2-8
//thread2-9
//thread2-10초
}
}
*synchronized: 한 쓰레드가 진행중인 작업을 다른 쓰레드가 침범하지 못하게 막는것 Lock
메서드 or 코드 묶음(참조변수) 앞에 synchronized 붙여 임계 영역 지정
public class Main {
public static void main(String[] args) {
AppleStore appleStore = new AppleStore();
Runnable task = () -> {
while (appleStore.getStoredApple() > 0) {
appleStore.eatApple();
System.out.println(Thread.currentThread().getName()+" 남은 사과의 수 = " + appleStore.getStoredApple());
}
};
for (int i = 0; i < 3; i++) {
new Thread(task,"Thread"+i).start();
}
}
}
class AppleStore {
private int storedApple = 10;
public int getStoredApple() {
return storedApple;
}
public void eatApple() {
/****이영역의 유무가 중요~***/
synchronized (this) {
if (storedApple > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
storedApple -= 1;
}
}
}
}
synchronized 없을 때
Thread0 남은 사과의 수 = 8
Thread2 남은 사과의 수 = 8
Thread1 남은 사과의 수 = 8
Thread2 남은 사과의 수 = 7
Thread0 남은 사과의 수 = 6
Thread1 남은 사과의 수 = 5
Thread2 남은 사과의 수 = 4
Thread0 남은 사과의 수 = 3
Thread1 남은 사과의 수 = 2
Thread0 남은 사과의 수 = 0
Thread2 남은 사과의 수 = 0
Thread1 남은 사과의 수 = -1 -> 0보다 작은값 수행됨
synchronized 있을 때
Thread0 남은 사과의 수 = 9
Thread2 남은 사과의 수 = 8
Thread1 남은 사과의 수 = 7
Thread2 남은 사과의 수 = 6
Thread0 남은 사과의 수 = 5
Thread2 남은 사과의 수 = 4
Thread1 남은 사과의 수 = 3
Thread2 남은 사과의 수 = 2
Thread0 남은 사과의 수 = 1
Thread2 남은 사과의 수 = 0
Thread1 남은 사과의 수 = 0
Thread0 남은 사과의 수 = 0
침범을 막은 코드(임계영역)를 수행하다보면 작업을 더이상 진행할 상황이 아니면,
wait()호출하여 쓰레드가 Lock반납하고 기다리게 할 수 있음.
다른 쓰레드가 Lock을 얻어 해당 객체에 대한 작업 수행,
추후에 작업 진행 할 수 있는 상황이 되면 notify() 호출해
작업 중단했던 쓰레드가 다시 Lock을 얻어 진행!
wait() : 실행 중이던 쓰레드 해당 객체의 대기실(waiting pool)에서 기다림notify() : 객체의 대기실에 있는 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받음->랜덤으로!import java.util.ArrayList;
import java.util.List;
//1명 점원이 재고 상품(각각 재고 5개)판매
//2명 손님이 구매
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) {
int randomItem = (int) (Math.random() * MAX_ITEM);
appleStore.restock(itemList[randomItem]);
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
}
};
// 고객
Runnable Customer = () -> {
while (true) {
try {
Thread.sleep(77);
} catch (InterruptedException ignored) {
}
int randomItem = (int) (Math.random() * MAX_ITEM);//5개 중 랜덤으로 고른다
appleStore.sale(itemList[randomItem]);//랜덤으로 상품 쌓는다
System.out.println(Thread.currentThread().getName() + " Purchase Item " + itemList[randomItem]);
}
};
new Thread(Customer, "Customer1").start();
new Thread(Customer, "Customer2").start();
new Thread(StoreClerk, "StoreClerk").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(); // 재고가 꽉 차있음!! -> 점원 쓰레드 waitin pool로 감 -> 재고가 하나라도 사라지면 notify
Thread.sleep(333);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 재입고
inventory.add(item);
System.out.println(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!");
try {
wait(); //1) 아무 재품가 없음 -> 기다림 -> 점원이 재고 넣었을 때 생길때 notify
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()+" : " +itemName+ "없음 Waiting!");
wait();//2) 찾는 제품 없으면 기다림 -> 점원이 재입고 할 때 까지
Thread.sleep(333);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
출력
MacBook 입고 완료
Inventory 현황: [MacBook]
Mac mini 입고 완료
Inventory 현황: [MacBook, Mac mini]
Customer2 : iMac없음 Waiting!
Customer1 Purchase Item MacBook
AirPods 입고 완료
Inventory 현황: [Mac mini, AirPods]
Customer2 : iMac없음 Waiting!
Customer1 Purchase Item Mac mini
IPhone 입고 완료
Inventory 현황: [AirPods, IPhone]
Customer2 : iMac없음 Waiting!
Customer1 : iMac없음 Waiting!
AirPods 입고 완료
Inventory 현황: [AirPods, IPhone, AirPods]
Customer2 : iMac없음 Waiting!
Mac mini 입고 완료
Inventory 현황: [AirPods, IPhone, AirPods, Mac mini]
Customer1 : iMac없음 Waiting!
AirPods 입고 완료
Inventory 현황: [AirPods, IPhone, AirPods, Mac mini, AirPods]
Customer2 : iMac없음 Waiting!-> iMac재고 채워질때까지 기다림
인벤토리 꽉참 StoreClerk Waiting!->하나라도 팔릴 때까지
점원과 고객이 서로 기다리는 병목현상 발생!!!
->notify,wait 주의점
->누구인지 명시를 해줘야한다 보완필요!!
<Lock 클래스>
synchronized(같은 메서드 내에서만 Lock을 걸 수 있다) 제약 해결
Lock interface를 구현한 클래스!
1) ReentrantLock : 가장 일반적인 Lock, 상호 베타적, 재진입이 가능한 Lock, 특정 조건에서 Lock을 풀고, 나중에 다시 Lock을 얻어 임계 영역으로 진입 가능=> Condition에서 활용
public class MyClass {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void methodA() {
synchronized (lock1) {
methodB();//lock1을 가진 상탱에서 lock2에 접근
}
}
public void methodB() {
synchronized (lock2) {
// do something
methodA();// lock2를 가진 상태에서 lock1에 접근!
}
}
}
이 때 ReentrantLock 사용하면 이미 가지고 있더라도 락을 유지하면서 계속 실행
->데드락 발생 안함!
코드의 유연성 높임!
2) ReentrantReadWriteLock : 읽기를 위한 Lock과 쓰기를 위한 Lock 별도로 제공
3) StampedLock
ReentrantReadWriteLock + 낙관적인 Lock기능 추가
낙관적인 Lock: 데이터를 변경하기 전에 Lock을 걸지 않음! => 데이터를 변경할 때만 Lock 건다!
< Condition 인터페이스 >
wait() & notify()의 문제점!=> waiting pool에 있는 쓰레드 notify()할 때 쓰레드 구분못함!(특정 조건으로 깨우지 못한다!)
=>JDK 5 에서 제공한 Condition interface으로 해결
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(); // 임계영역 끝
}
}
Java의 진화!
(데이터량 증가->효율적인 알고리즘으로 처리)
프로그래머가 해결해야하는 문제가 계속 변화-> 요구 기능도 변화(니즈)
최초에는 C, C++ 같은 언어가 지배적->난이도 높고 보안이슈 ->C#,Java 탄생
새로운 요구사항(빅데이터 AI)->맞춤형으로 진화
1)병렬처리
2)함수형 프로그래밍->람다식
프로그래밍의 패러다임
** Java 성장 큰 시스템에서의 핵심 -> 새로운 생태계가 열리면서 진화하거나 도태되는 시점 도달 -> 모던 자바로 커버
<Java 8에 추가된 개념>
함수형 프로그래밍 기능 추가
함수를 변수로 쓸 때 타입을 어떻게???->(함수형) 인터페이스
인터페이스는 타입 역할 가능!(멀티 리모컨 예제)
class Main{
public staic void main(String []){
....
parkingLot.addAll(parkingCarWithTicket(carsWantToPark));//벤츠 아우디
//일정 돈 이상 있는 차만 주차가능
parkingLot.addAll(parkingCarWithMoney(carsWantToPark));//현대
//parkingCarWithTicket, parkingCarWithMoney 함수를
// parkCars라는 하나의 함수로 재정의
// 파라미터에 함수가 들어간다!
// 함수를 인터페이스로 구현
parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket));
parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));
//더 간단하게 람다함수 사용!!
weekenparkinLot.addAll(parkCars(carsWantToPark,
(Car car) -> car.hasParkingTicket() && car.getParkingMoney() > 1000));
}
public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
ArrayList<Car> cars = new ArrayList<>();
for (Car car : carsWantToPark) {
//전달된 함수를 사용하여 구현!
if (function.test(car)) {
cars.add(car);
}
}
}
class Car{
....
public static boolean noTicketButMoney(Car car) {
return !car.hasParkingTicket && car.getParkingMoney() > 1000;
}
public static boolean hasTicketAndMoney(Car car) {
return car.hasParkingTicket && car.getParkingMoney() > 1000;
}
}
interface Predicate<T> {
boolean test(T t);//티켓 잇는지 없는지 돈 있는지 없는지
}
(파라미터 값, ...) -> { 함수 몸체 }
public int toLambdaMethod(int x, int y) {
return x + y;
}
(x, y) -> {
return x + y;
}
//위와 동일
(x, y) -> x + y
// 이런 함수도 가능하겠죠?
public int toLambdaMethod2() {
return 100;
}
//input 없음!
() ->{
return 100;
}
//위와 동일
() -> 100
// 모든 유형의 함수에 가능합니다.
public void toLambdaMethod3() {
System.out.println("Hello World");
}
() -> System.out.println("Hello World")
자료구조(리스트,맵,셋 등)들의 흐름을 객체로서 제공
사용할 수 있는 메소드를 API 로 제공
원본 데이터 변경하지 않음!->새롭게 추출(map,fiter,foreach)
일회용(한번 사용한 스트림은 어디에도 남지 않는다!)
-컬랙션에 정의 컬래션을 상속하는 구현체들은 스트림 반환 가능!
List<Car> benzParkingLot =
carsWantToPark.stream()
.filter((Car car) -> car.getCompany().equals("Benz"))
.toList();
// 1carsWantToPark의 스트림값을 받아와서
// 거기 구현되어 있는 filter()메서드를 사용합니다.
// filter메서드는 함수를 파라미터로 전달받습니다.
// 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요.
// 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다.
// 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다.
위와 동일한 역할을 하는 코드
ArrayList<Car> benzParkingLotWithoutStream = new ArrayList<>();
for (Car car : carsWantToPark) {
if (car.getCompany().equals("Benz")) {
benzParkingLotWithoutStream.add(car);
}
}
1.스트림 받아오기 (.stream())
2.스트림 가공하기(.filter() & .map())
3.스트림 결과 만들기(.toList())
carNames.stream()
.map(name -> name.toUpperCase()).toList();
// 결과
// ["SERIES 6", "A9", "IONIC 6"]List<String> carNames = Arrays.asList("Series 6", "A9", "Ionic 6");
carNames.stream()
.forEach(System.out::println);
//모든 요소 출력
public class NullIsDanger {
public static void main(String[] args) {
SomeDBClient myDB = new SomeDBClient();
String userId = myDB.findUserIdByUsername("HelloWorldMan");
//1)데이터 있는경우 : data
//2)데이터 없는 경우 : null -> print할 떄 null 참조한다는 문제 발생!->runtime에서 nullpointException 발생
//System.out.println("HelloWorldMan's user Id is : " + userId);
//-> 개선1
if(userId != null){
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이 반환 될 수 있음을 명시하지않았다.
->개선 1 : 조건문으로 null 아닐때만 출력하게함
개선1의 문제점 : 수동이라서 약속을 지키는 사람만하고 안하는 사람은 안함...
->개선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);
}
}
}
개선3) 발전시키기 제네릭 사용
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 객체!
-우리가 데이터를 감쌌더 로직 + a
Optional
1.값이 null 인 Optional 생성
Optional<Car> emptyOptional = Optional.empty();
2.값이 있는 Optional 생성
Optional<Car> hasDataOptional = Optional.of(new Car());
3.값이 있을 수도 있는 Optional생성
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());
예제)
Optional<String> carName = getCarNameFromDB();
String realCarName= carName.ofElse("NoCar");
//OfElse 값을 받아오는 메서드 , 파라미터에 값이 Null인 경우 반환할 값!
//위 두줄을 한줄로
String carName= getCarNameFromDB().ofElse("NoCar");
Car car = getCarNameFromDB().orElseGet(Car::new);
//파라미터에 메서드를 사용해 값 받기
//null일 경우 함수 실행
//값이 없으면 예외 발생 시킴!
//파라미터로 람다(익명함수) 사용
Car car = getCarNameFromDB()
.orElseThrow(() -> new CarNotFoundException("No Car!));