예외처리, 제네릭
오류(Error) vs 예외(Exception)
오류 (Error)
- 회복 불가능
- 시스템 레벨에서, 주로 환경적인 이유로 발생
- 어떤 에러로 프로그램이 종료되었는지 확인하고 대응
예외(Exception)
- 회복 가능
- 그 예외가 발생할 수 있다는 것을 인지하고, 대응함
- 코드 레벨에서 할 수 있는 문제 상황에 대한 대응은 여기에 속함
예외의 종류
컴파일 에러(예외)
- .java 파일을 .class 파일로 컴파일할 때 발생하는 에러
- 대부분 자바 프로그래밍 언어의 규칙을 지키지 않았기 때문에 발생
런타임 에러(예외)
- 주로 다루게 될 에러(예외)
- 문법적인 오류는 아니라 컴파일은 잘 되었지만 “프로그램”이 실행 도중 맞닥뜨리게 되는 예외
예외 발생과 try-catch, finally 문
예외 정의하기
class OurBadException extends Exception {
public OurBadException() {
super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
}
}
class OurClass {
private final Boolean just = true;
public void thisMethodIsDangerous() throws OurBadException {
if (just) {
throw new OurBadException();
}
}
}
throws
|
throw
|
메서드 이름 뒤에 붙어 이 메서드가 어떠한 예외사항을 던질 수 있는지 알려주는 예약어
|
메서드 안에서, 실제로 예외 객체를 던질 때 사용하는 예약어
|
여러 종류의 예외사항을 적을 수 있다.
|
일반 메서드의 return 키워드처럼 throw 아래의 구문들은 실행되지 않고 종료된다.
|
예외 handling
public class StudyException {
public static void main(String[] args) {
OurClass ourClass = new OurClass();
try {
// 1. 위험한 메소드의 실행을 "시도"
ourClass.thisMethodIsDangerous();
} catch (OurBadException e) {
// 2. 예외가 발생하면, "잡아서" handling
// 즉 try 블럭 내의 구문을 실행하다가 예외가 발생하면
// 예외가 발생한 줄에서 바로 코드 실행을 멈추고
// 여기 있는 catch 블럭 내의 코드가 실행
System.out.println(e.getMessage());
} finally {
// 3. 예외의 발생 여부와 상관없이 실행시켜야 하는 코드
System.out.println("우리는 방금 예외를 handling 했습니다!");
}
}
}
Exception class 구조


연결된 예외 (Chained Exception)
- 여러 가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해 사용
- checked exception을 unchecked exception으로 포장(wrapping) 하는데 사용
- initCause()
- getCause()
// 연결된 예외
public class main {
public static void main(String[] args) {
try {
// 예외 생성
NumberFormatException ex = new NumberFormatException("가짜 예외이유");
// 원인 예외 설정(지정한 예외를 원인 예외로 등록)
ex.initCause(new NullPointerException("진짜 예외이유"));
// 예외 직접 던지기
throw ex;
} catch (NumberFormatException ex) {
// 예외 로그 출력
ex.printStackTrace();
// 예외 원인 조회 후 출력
ex.getCause().printStackTrace();
}
// checked exception 을 감싸서 unchecked exception 안에 넣기
throw new RuntimeException(new Exception("이것이 진짜 예외 이유 입니다."));
}
}
// 출력
Caused by: java.lang.NullPointerException: 진짜 예외이유
실제로 예외 처리하기
- 예외 복구하기
public String getDataFromAnotherServer(String dataPath) {
try {
return anotherServerClient.getData(dataPath).toString();
} catch (GetDataException e) {
return defaultData;
}
}
- try-catch로 예외를 처리하고 프로그램을 정상 상태로 복구하는 방법
- 현실적으로 복구가 가능한 상황이 아닌 경우가 많거나 최소한의 대응만 가능한 경우가 많기 때문에 자주 사용되지는 않는다. 추천 X
- 예외 처리 회피하기
public void someMethod() throws Exception { ... }
public void someIrresponsibleMethod() throws Exception {
this.someMethod();
}
- someMethod()에서 발생한 에러가 someIrresponsibleMethod()의 throws를 통해서 그대로 다시 흘러나가게 된다. (예시 코드일 뿐 물론 같은 객체 내에서 이런 일은 안한다)
- 관심사를 분리해서 한 레이어에서 처리하기 위해서 이렇게 에러를 회피해서 그대로 흘러 보내는 경우도 있다.
- 예외 전환하기
public void someMethod() throws IOException { ... }
public void someResponsibleMethod() throws MoreSpecificException {
try {
this.someMethod();
} catch (IOException e) {
throw new MoreSpecificException(e.getMessage());
}
}
- 예외 처리 회피하기의 방법과 비슷하지만, 조금 더 적절한 예외를 던져주는 경우
- 보통은 예외 처리에 더 신경 쓰고 싶은 경우나, 오히려 RuntimeException처럼 일괄적으로 처리하기 편한 예외로 바꿔서 던지고 싶은 경우 사용
제네릭 (Generic)
- 타입 언어에서 “중복되거나 필요 없는 코드”를 줄여준다. (타입이 다르므로 같은 로직 수행하는 메소드라도 오버로딩을 통해 여러 번 구현해줘야 함)
- 타입 안정성을 해치지 않는다. (타입을 Object로 대신 작성해도 형변환, 연산자, 순서 등 내부에서 수 많은 부수적인 코드 구현이 필요해짐)
// 1. 제네릭은 클래스 또는 메서드에 사용 가능
// <> 안에 들어가야 할 타입 명시
public class Generic<T> {
// 2. T 전부 String으로 바뀌어서 사용됨
private T t;
// 3. 리턴 타입 String
public T get() {
return this.t;
}
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
// 4.
Generic<String> stringGeneric = new Generic<>();
// 5.
stringGeneric.set("Hello World");
String tValueTurnOutWithString = stringGeneric.get();
System.out.println(tValueTurnOutWithString);
}
}
문법
- 제네릭 클래스 : 제네릭을 사용한 클래스
- <> 사이에 들어가는 변수명 T : 타입 변수
- Generic 클래스 : 원시 타입
- 객체의 static 멤버에 사용할 수 없음.
static T get() { ... } // 에러
static void set(T t) { ... } // 에러
타입 변수는 인스턴스 변수로 간주됨.
- 제네릭 배열을 생성할 수 없음.
- 다수의 타입 변수 사용 가능
public class Generic<T, U, E> {
public E multiTypeMethod(T t, U u) { ... }
}
Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);
- 다형성 즉 상속과 타입의 관계는 그대로 적용
- 와일드카드를 통해 제네릭의 제한을 구체적으로 정할 수 있음
public class ParkingLot<T extends Car> { ... }
ParkingLot<BMW> bmwParkingLot = new ParkingLot();
ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!
- <? extends T> : T와 그 자손들만 사용 가능
- <? super T> : T와 그 조상들만 가능
- <?> : 제한 없음
- 메서드를 스코프로 제네릭을 별도로 선언할 수 있음
public class Generic<T, U, E> {
// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}
- 이렇게 반환 타입 앞에 <> 제네릭을 사용한 경우, 해당 메서드에만 적용되는 제네릭 타입 변수를 선언할 수 있다.
- 타입 변수를 클래스 내부의 인스턴스 변수로 취급하기 때문에 제네릭 클래스의 타입 변수를 static 메서드에는 사용할 수 없었지만, 제네릭 메소드의 제네릭 타입 변수는 해당 메소드에만 적용되기 때문에 메소드 하나를 기준으로 선언하고 사용할 수 있다.
- 같은 이름의 변수를 사용했다고 해도 제네릭 메소드의 타입 변수는 제네릭 클래스의 타입 변수와 다르다.
Collection
Collection을 선언할 때 사용한 <>도 제네릭임.
추상적인 타입인 인터페이스로 어떤 속성을 가져야 하는지 명세를 정리하고 이를 구현체들이 구현함.

- Collection(집합적 자료)라는 속성은 Iterable(순회 가능)이라는 속성을 상속받고 있음
- Collection의 하위 ‘속성’으로는 List, Queue, Set 등이 있음
- List의 실제 구현체들은 Arr, Linked, Vector, Stack 등등
- 상황에 맞는 적절한 자료구조를 택하는 방법
Wrapper 객체

- 자바는 소수, 반올림, 정수와 같은 개념들을 추상화하여 객체화 해둠 (wrapper class)
- 그러나 성능상의 이유로 “값”을 사용하는 이상의 의의를 가지지 않을 경우 그냥 원시 타입 사용
- 객체로의 “기능”이 필요할 때 잠시 객체로 만들어 사용할 수도 있음
- 기본값 → 객체화 : 박싱
- 객체 → 기본값 : 언박싱
Integer num = new Integer(17); // Boxing
int n = num.intValue(); // UnBoxing
Character ch = 'X'; // AutoBoxing
char c = ch; // AutoUnBoxing
쓰레드, 람다, 스트림, Optional
쓰레드
프로세스와 쓰레드
프로세스
- 운영체제로부터 자원을 할당받는 작업의 단위
- “실행 중인 프로그램”을 의미
- 예를 들어 우리가 Java 프로그램을 실행시키면 이 프로그램은 프로세스라는 이름으로 운영체제 위에서 실행됨
- 즉, OS 위에서 실행되는 모든 프로그램은 OS가 만들어준 프로세스에서 실행된다.
📌
OS가 프로그램 실행을 위한 프로세스를 할당해 줄 때 프로세스 안에 프로그램 Code와 Data 그리고 메모리 영역(Stack, Heap)을 함께 할당해 준다.
- Code는 Java main 메소드와 같은 코드를 말한다.
- Data는 프로그램이 실행 중 저장할 수 있는 저장 공간을 의미한다.
- Memory (메모리 영역)
📌
각 프로그램은 프로세스를 통해 Code, Data, Memory (Stack, Heap)를 OS로부터 할당받는다.

쓰레드
📌
쓰레드는 프로세스 내에서 일하는 일꾼(코드 실행의 흐름)
- 프로세스가 작업 중인 프로그램에서 실행 요청이 들어오면 쓰레드(일꾼)을 만들어 명령을 처리하도록 한다.
- 프로세스 안에는 여러 쓰레드들이 있고, 쓰레드들은 실행을 위한 프로세스 내 주소 공간이나 메모리 공간(Heap)을 공유 받는다.
- 추가로, 쓰레드들은 각각 명령 처리를 위한 자신만의 메모리 공간(Stack)도 할당받는다.
자바 쓰레드
📌
일반 쓰레드와 동일하며 JVM 프로세스 안에서 실행되는 쓰레드를 말한다.
- Java 프로그램을 실행하면 앞서 배운 JVM 프로세스 위에서 실행된다.
- Java 프로그램 쓰레드는 Java Main 쓰레드부터 실행되며 JVM에 의해 실행된다.

멀티 쓰레드
- Java는 메인 쓰레드가 main() 메서드를 실행시키면서 시작이 된다.
- 메인 쓰레드는 필요에 따라서 작업 쓰레드들을 생성해서 병렬로 코드를 실행시킬 수 있다.
- JVM의 메인 쓰레드가 종료되면, JVM도 같이 종료된다.
- 하나의 프로세스는 여러 개의 실행 단위(쓰레드)를 가질 수 있으며 이 쓰레드들은 프로세스의 자원을 공유한다.
- 멀티 쓰레드 장점
- 멀티 쓰레드 단점
Thread와 Runnable
Thread
- 자바에서 제공하는 Thread 클래스를 상속받아 쓰레드를 구현해준다.
public class Main {
public static void main(String[] args) {
TestThread thread = new TestThread();
thread.start();
}
}
class TestThread extends Thread {
@Override
public void run() {
for (int i = 0; i <100; i++) {
System.out.print("*");
}
}
}
run() 메서드에 작성된 코드가 쓰레드가 수행할 작업이다.
Runnable
- 자바에서 제공하는 Runnable 인터페이스를 사용하여 쓰레드를 구현해준다.
public class Main {
public static void main(String[] args) {
Runnable run = new TestRunnable();
Thread thread = new Thread(run);
thread.start();
}
}
class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i <100; i++) {
System.out.print("$");
}
}
}
마찬가지로 run() 메서드에 작성된 코드가 쓰레드가 수행할 작업이다.
Java는 다중 상속을 지원하지 않기 때문에 Thread를 상속받아 처리하는 방법은 확장성이 매우 떨어진다. 반대로 Runnable은 인터페이스이기 때문에 다른 필요한 클래스를 상속받을 수 있다. 따라서 확장성에 매우 유리하다.
대부분 람다식을 이용해 쓰레드를 구현한다.
Copy
public class Main {
public static void main(String[] args) {
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);
thread1.setName("thread1");
Thread thread2 = new Thread(task);
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
데몬 쓰레드와 사용자 쓰레드
데몬 쓰레드
- 보이지 않는 곳(background) 에서 실행되는 낮은 우선순위를 가진 쓰레드.
- 보조적인 역할을 담당하며 대표적인 데몬 쓰레드로는 메모리 영역을 정리해 주는 가비지 컬렉터(GC)가 있다.
public class Main {
public static void main(String[] args) {
Runnable demon = () -> {
for (int i = 0; i < 1000000; i++) {
System.out.println("demon");
}
};
Thread thread = new Thread(demon);
thread.setDaemon(true); // true로 설정시 데몬스레드로 실행됨
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("task");
}
}
}
- demon 쓰레드는 우선순위가 낮고 다른 쓰레드가 모두 종료되면 강제 종료 당하기 때문에 main() 쓰레드의 task가 100번이 먼저 찍히면 종료되어 1000000번 수행이 되지 않고 종료됨.
사용자 쓰레드
- 보이는 곳(foregorund)에서 실행되는 높은 우선순위를 가진 쓰레드
- 프로그램 기능을 담당하며 대표적인 사용자 쓰레드로는 메인 쓰레드가 있다.
- 디폴트 쓰레드.
⚠️
JVM 은 사용자 쓰레드의 작업이 끝나면 데몬 쓰레드도 자동으로 종료시켜 버린다.
쓰레드 우선순위와 쓰레드 그룹
쓰레드 우선순위
📌
쓰레드 작업의 중요도에 따라서 쓰레드의 우선순위를 부여할 수 있다.
- 작업의 중요도가 높을 때 우선순위를 높게 지정하면 더 많은 작업시간을 부여받아 빠르게 처리될 수 있음.
- 직접 정하기도, JVM에 의해 지정되기도 함.
- JVM의 우선순위
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); // 우선순위 설정 (8)
int threadPriority = thread1.getPriority(); // 우선순위 반환
System.out.println("threadPriority = " + threadPriority);
Thread thread2 = new Thread(task2);
thread2.setPriority(2); // 우선순위 설정 (2)
thread1.start();
thread2.start();
// 우선순위가 더 높은 thread1이 더 일찍 끝날 확률이 높음.
}
}
쓰레드 그룹
📌
서로 관련이 있는 쓰레드들을 그룹으로 묶어서 다룰 수 있다.
- 쓰레드들은 기본적으로 그룹에 포함되어 있다.
- 메인 쓰레드는 system 그룹 하위에 있는 main 그룹에 포함.
- 모든 쓰레드들은 반드시 하나의 그룹에 포함되어 있어야 한다.
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
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)
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 {
// 현재 쓰레드를 지정된 시간동안 멈추게 하기
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// interrupt()는 일시정지 상태인 쓰레드를 실행대기 상태로 만든다.
group1.interrupt();
}
}
쓰레드 상태와 제어
쓰레드 상태

- 쓰레드는 실행과 대기를 반복하며 run() 메서드를 수행
- run() 메서드가 종료되면 실행이 멈추게 됨

- 일시정지 상태에서는 쓰레드가 실행을 할 수 없는 상태가 됨
- 쓰레드가 다시 실행 상태로 넘어가기 위해서는 우선 일시정지 상태에서 실행 대기 상태로 넘어가야 한다.
상태
|
Enum
|
설명
|
객체생성
|
NEW
|
쓰레드 객체 생성, 아직 start() 메서드 호출 전의 상태
|
실행대기
|
RUNNABLE
|
실행 상태로 언제든지 갈 수 있는 상태
|
일시정지
|
WAITING
|
다른 쓰레드가 통지(notify) 할 때까지 기다리는 상태
|
일시정지
|
TIMED_WAITING
|
주어진 시간 동안 기다리는 상태
|
일시정지
|
BLOCKED
|
사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태
|
종료
|
TERMINATED
|
쓰레드의 작업이 종료된 상태
|
쓰레드 제어

- sleep()
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
try {
// 특정 쓰레드 지목 불가
Thread.sleep(2000);
// TIMED_WAITING (주어진 시간동안만 기다리는 상태)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task : " + Thread.currentThread().getName());
};
Thread thread = new Thread(task, "Thread");
thread.start(); // NEW -> RUNNABLE
try {
// 1초가 지나면 RUNNABLE 상태로 변하여 다시 실행
// 특정 쓰레드 지목해서 멈추게 하는 것은 불가능 (sleep은 static 메소드)
thread.sleep(1000);
System.out.println("sleep(1000) : " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
// 원래의 의도는 Thread라고 이름지어진 특정 쓰레드를 1초 잠재운 후 출력하려 했으나
// main이 1초 잠들고 main 출력, 그리고 1초 후 (총 2초) Thread 출력됨.
}
}
- interrupt()
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
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();
System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
// 바로 interrupt 되었기 때문에 1초 잠들지 않음
}
}
- join()
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();
try {
thread.join();
// join()하지 않으면 기다리지 않고 main 끝나면서 바로 끝나버림
// join()해서 main이 기다려줌
} catch (InterruptedException e) {
e.printStackTrace();
}
// thread의 소요시간인 5000ms 동안 main 쓰레드가 기다렸기 때문에 5000이상이 출력
System.out.println("소요시간 = " + (System.currentTimeMillis() - start));
}
}
- 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");
Thread thread2 = new Thread(task, "thread2");
thread1.start();
thread2.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
}
}
thread1과 thread2가 같이 1초에 한 번씩 출력되다가 5초 뒤에 thread1에서 InterruptedException이 발생하면서 Thread.yield(); 이 실행되어 thread1은 실행 대기 상태로 변경되면서 남은 시간은 thread2에게 리소스가 양보된다.
- synchronized
📌
멀티 쓰레드의 경우 여러 쓰레드가 한 프로세스의 자원을 공유해서 작업하기 때문에 서로에게 영향을 줄 수 있다. 이로 인해서 장애나 버그가 발생할 수 있다.
- 이러한 일을 방지하기 위해 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 침범하지 못하도록 막는 것을 '쓰레드 동기화(Synchronization)'라고 한다.
- 동기화를 하려면 다른 쓰레드의 침범을 막아야 하는 코드들을 ‘임계 영역’으로 설정하면 된다.
- 임계 영역에는 Lock을 가진 단 하나의 쓰레드만 출입이 가능하다.
- 실행할 메서드 또는 실행할 코드 묶음 앞에 synchronized를 붙여서 임계 영역을 지정, 다른 쓰레드의 침범을 막을 수 있다. (침범을 막다 = Lock을 걸다)
public class Main {
public static void main(String[] args) {
AppleStore appleStore = new AppleStore();
Runnable task = () -> {
while (appleStore.getStoredApple() > 0) {
appleStore.eatApple();
System.out.println("남은 사과의 수 = " + appleStore.getStoredApple());
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
}
class AppleStore {
private int storedApple = 10;
public int getStoredApple() {
return storedApple;
}
public void eatApple() {
synchronized (this) {
// lock걸고 싶은 코드 묶음 앞에 synchronized로 묶어줌.
// 없다면 동시에 들어가 있지도 않은 사과 먹으면서 -1 -2 출력되는데
// 동기화해준다면 한 스레드가 이 안에서 작업을 하고 있을 때 다른 스레드 접근 막음
// 결국 사과 0개되면 if문 통과 못하면서 정상적으로 실행
if(storedApple > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
storedApple -= 1;
}
}
}
}
- wait(), notify()
예제
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);
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!");
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!");
wait();
Thread.sleep(333);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- Lock, Condition (이해 x)
모던 자바 : 자바 8 변경점 알아보기
- 함수형 프로그래밍의 아이디어와 문법을 자바 8에서 지원한다.
- 함수형 프로그래밍의 아이디어인 (함수를 값으로 다루거나, 다른 함수에 넘길 수 있다)와 같은 일들이 가능하다.
- 함수형 프로그래밍의 문법인 익명 함수 문법을 지원한다.
- 스트림이라는 컬렉션의 흐름과 같은 것을 지원한다.
- 스트림 기능의 지원으로 우리는 더 간결하고, 유연하고, 성능 좋은 코드를 작성할 수 있다.
람다와 스트림
람다
예시 코드
import java.util.ArrayList;
import java.util.List;
public class LambdaAndStream {
public static void main(String[] args) {
ArrayList<Car> carsWantToPark = new ArrayList<>();
ArrayList<Car> parkingLot = new ArrayList<>();
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(parkCars(carsWantToPark, Car::hasTicket));
parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));
for (Car car : parkingLot) {
System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
}
}
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;
}
}
interface Predicate<T> {
boolean test(T t);
}
import java.util.ArrayList;
import java.util.List;
public class LambdaAndStream {
public static void main(String[] args) {
ArrayList<Car> carsWantToPark = new ArrayList<>();
ArrayList<Car> parkingLot = new ArrayList<>();
ArrayList<Car> weekendParkingLot = new ArrayList<>();
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(parkCars(carsWantToPark, Car::hasTicket));
parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));
weekendParkingLot.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> 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;
}
}
interface Predicate<T> {
boolean test(T t);
}
Optional
- Java8에서는 Optional<T> 클래스를 사용해 Null Pointer Exception을 방지할 수 있도록 도와줌.
- Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스
Optional<String> carName = getCarNameFromDB();
// orElse()를 통해 값을 받아온다, 파라미터로는 null인 경우 반환할 값을 적는다.
String carName = getCarNameFromDB().orElse("NoCar");
// orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있다.
// 파라미터로는 없는 경우 실행될 함수를 전달한다.
Car car = getCarNameFromDB().orElseGet(Car::new);
// 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있다.
Car car = getCarNameFromDB()
.orElseThrow(() -> new CarNotFoundException("NO CAR!)
'내배캠 > TIL' 카테고리의 다른 글
24. 07. 26 (0) | 2024.07.26 |
---|---|
N의 약수 찾기 알고리즘 (0) | 2024.07.25 |
Java 문법 종합반 1~3주 (6) | 2024.07.23 |
SQL 정리 2 (7) | 2024.07.22 |
SQL 정리 1 (1) | 2024.07.22 |