▶︎ 코드 실행 관점에서 예외의 종류
▶︎ 예외처리 관점에서 예외의 종류
확인된 예외(Checked Exception): 컴파일 시점에 확인하는 예외 → 반드시 예외 처리를 해줘야하는 예외
🤔 Checked Exception 에 대한 처리가 바로 개발자가 주로 다루게 될 예외처리이다.
미확인된 예외(Unchecked Exception): 런타임 시점에 확인되는 예외, 예외처리가 반드시 필요하지 않은 예외
💡 컴파일이란? 프로그래밍 언어로 작성한 코드가 컴퓨터가 이해할 수 있는 언어로 변환되는 것
1. 예외를 어떻게 정의하고,
2. 예외가 발생 할 수 있음을 알리고, → 예외가 발생할 수 있음을 논리적 flag를 세워둔다.
3. 사용자는 예외가 발생할 수 있음을 알고 예외를 핸들링하는지
어떠한 상황(정의)에 의해 프로그램에서 예외가 발생할 수 있음을 알리고 예외가 발생하면 이러한 처리를 한다(핸들링)
OurBadException.java: 개발자가 직접 예외 클래스를 정의할 수 있다. → 사용자 정의 ExceptionOurClass.java: 메서드에서 정의된 예외 클래스를 인스턴스화하여 사용할 수 있다.▶︎ OurBadException.java
package week04.sample01;
// 예외 클래스를 만들어서 예외를 정의 : 사용자 정의 Exception
public class OurBadException extends Exception {
public OurBadException() {
super("위험한 행동을 하면 예외처리를 꼭 해야함!");
}
}
▶︎ OurClass.java
package week04.sample01;
public class OurClass {
private final boolean just = true;
// throws : 던지다! (= 예외를 던지다 / 발생시키다)
public void thisMethodIsDangerous() throws OurBadException {
// custom logic
if (just) {
throw new OurBadException();
}
}
}
▶︎ StudyException.java
package week04.sample01;
public class StudyException {
public static void main(String[] args) {
OurClass ourClass = new OurClass();
// try ~ catch ~ finally 구문
// try: 시도하다
// catch: 잡다 (붙잡다)
// finally: 마침내
// 일단 try ~~ 그리고, 예외가 발생하면 그걸 잡아 (catch)
// 그리고, 정상적으로 수행되든, 예외가 발생하든 마침내 수행해야 하는 로직을 'finally' 수행해
try {
// 일단 실행!
ourClass.thisMethodIsDangerous();
} catch (OurBadException e) {
System.out.println(e.getMessage());
} finally {
// 무조건 여기는 거쳐요!
System.out.println("우리는 방금 예외를 handling 했습니다. 정상처리되든, 오류가 발생하든 여기를 반드시 거쳐요");
}
}
}
throws: 메서드 이름 뒤에 붙어 이 메서드가 어떠한 예외사항을 던질 수 있는지 알려주는 예약어이다.throw: 메서드 안에서 실제로 예외 객체를 던질 때 사용, 일반 메서드의 return 키워드처럼 throw 아래의 구문들은 실행되지 않고, throw 문과 함께 메서드가 종료된다.▶︎ 용어 정리
프로그래밍하다 보면 "문제"상황에 직면하고, 회복 가능 여부에 따라 ❌(Error), ⭕️(Exception) 으로 나누고, 예외와 에러를 컴파일/런타임과 같은 여러 기준에 따라 구체적으로 정의함
결론적으로 우리가 맞닥뜨리는 "문제"상황을 추상화, 일반화, 구체화해서 정의
이러한 에러 객체들을 구현하는 것 자체가 조금 전에 언급된 언어 설계 차원에서의 에러 대응 프로세스이기도 하기 때문에 당연히 이 문제들을 추상화해서 객체로 만들어 주는 것은 자바 언어 차원에서 지원해 준다.
모든 객체의 원형인 Object 클래스를 상속받는다.
Throwable 클래스의 자식으로 에러(Error)와 예외(Exception)가 있다.
에러 클래스와 예외 클래스는 각각 IOError 클래스와 RuntimeException 클래스와 같이 구분하여 처리된다.

CheckedException 과 UncheckedException을 쉽게 구분하는 방법
RuntimeException을 상속한 예외들은 Unchecked Exception, 반대로 상속하지 않은 예외들은 CheckedException 으로 구현되어 있다.
✨ 이미 수많은 구현체들이 있고, 거의 대부분의 상황에 맞는 에러들은 이미 구현되어 있다. 명시적으로 어떠한 에러를 내보낼지는 찾아보고 결정하면 된다. 만약, 찾아봤는데 알맞은 에러가 없다면 특정 에러를 더 구체화해서 직접 예외 클래스를 "정의", "구현"하면 된다.
▶︎ 원인 예외를 다루기 위한 메소드
🤔 왜 예외를 연결하죠?
▶︎ 연결된 예외 예시
// 연결된 예외
public class Main {
public static void main(String[] args) {
try {
// 예외 생성
NumberFormatException ex = new NumberFormatException("가짜 예외이유");
// 1. initCause: 원인 예외 설정(지정한 예외를 원인 예외로 등록)
ex.initCause(new NullPointerException("진짜 예외이유"));
// 예외를 직접 던집니다.
throw ex;
} catch (NumberFormatException ex) {
// 예외 로그 출력
ex.printStackTrace();
// 2. getCause(): 예외 원인 조회 후 출력
ex.getCause().printStackTrace();
}
// checked exception 을 감싸서 unchecked exception 안에 넣습니다.
throw new RuntimeException(new Exception("이것이 진짜 예외 이유 입니다."));
}
}

ex.printStackTrace()로 예외 로그를 콘솔에 출력하면 예외 ex의 메세지와 ex가 원인 예외로 지정한 NullPointerException 의 메세지까지 함께 출력된다. → 에러 원인에 대한 추적이 가능하다.
예외복구, 예외 처리 회피, 예외 전환
public String getDataFromAnotherServer(String dataPath) {
try {
return anotherServerClient.getData(dataPath).toString();
} catch (GetDataException e) {
return defaultData;
}
}
public void someMethod() throws Exception { ... }
public void someIrresponsibleMethod() throws Exception {
this.someMethod();
}
public void someMethod() throws IOException { ... }
public void someResponsibleMethod() throws MoreSpecificException {
try {
this.someMethod();
} catch (IOException e) {
throw new MoreSpecificException(e.getMessage());
}
}
gereralized: 일반화된, genetic: 유전적인
타입 문법이기 때문에 특정 타입을 지정해줘야 하는데(강타입) 이로 인한 문제가 발생할 수 있다.
하지만, 타입없이 쓰고 싶은데! : 제네릭을 사용해보자
package week04.gen;
// 1. 제네릭은 클래스 또는 메서드에 사용 가능하다.
// <> 안에 들어가야 할 타입을 명시
// T (타입변수) 는 규약 (컨벤션)
public class Generic<T> {
// 2. main 안 Generic<String>에 의해 내부 필드는 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);
}
}
public class Generic<T> { ... }
Generic<String> stringGeneric = new Generic<>();
Generic<T>의 클래스처럼, 제네릭을 사용한 클래스를 제네릭 클래스라고 한다.타입 변수라고 한다.원시 타입이라고 한다.▶︎ 제네릭의 제한
1. 객체의 static 멤버에 사용할 수 없다.
(타입변수는 인스턴스 변수로 간주된다.)
2. 제네릭 배열을 생성할 수 없다.
▶︎ 제네릭 문법
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!
1. <? extends T>: T와 그 자손들만 사용 가능
2. <? super T>: T와 그 조상들만 가능
3. <?>: 제한 없음
이렇게 제한하는 이유는 다형성 때문이다. 제네릭 클래스 안의 로직은 다형성을 염두에 두고(부모 클래스의 메서드를 사용) 개발되었기 때문이다.

📌 타입변수를 클래스 내부의 인스턴스 변수 취급하기 때문에 제네릭 클래스의 타입변수를 static 메서드에서는 사용할 수 없었지만, 제네릭 메서드의 제네릭 타입 변수는 해당 메서드에만 적용되기 때문에 메서드 하나를 기준으로 선언하고 사용할 수 있다.
public class Generic<T, U, E> {
// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}
👉 제네릭 클래스에서의 <T>와 제네릭 메서드 sort에서의 <T>는 관계가 없다! (<T>가 관여하는 범위가 다르다.)
Collection으로 돌아온 이유는 사실 우리가 쓰는 컬렉션에는 제네릭이 포함되어있다. → 자료구조 (List, Array, Map)
리스트는 추상적 자료구조로서 (이를 실체화한게 ArrayList, LinkedList), 순서를 가지고, 일렬로 나열한 원소들의 모임이다. 순서가 있고, 중복을 허용한다는 점에서 집합(Set)과 구별된다.
▶︎ 추상적 자료구조인 리스트는 개념적으로 보통 다음 연산들을 지원한다.
* 빈 리스트를 만드는 연산
* 리스트가 비어있는지 확인하는 연산
* 리스트의 앞에 원소를 삽입하는 연산
* 리스트의 뒤에 원소를 삽입하는 연산
* 리스트의 제일 첫 원소를 알아보는 연산
* 리스트의 첫 원소를 제외한 나머지 리스트를 알아보는 연산
리스트는 사실 인터페이스다.

ArrayList, LinkedList는 이러한 리스트 인터페이스를 구현(implements)한 클래스로 리스트에 작성된 메서드를 모두 오버라이딩하여 완성시킨다.
배열은 프로그래밍 언어에서 지원하는 자료형
int[] arr = {1,2,3};
배열은 첫 칸의 주소를 알고있으면 인덱스를 통해 쉽게 조회가 가능하다. 하지만 순서를 가지고 있기 때문에 추가와 삭제가 매우 느리다.
→ 검색에는 유리하고, 수정/삭제는 불리한 자료구조다.

출처 : https://www.javatpoint.com/collections-in-java
자바에서는 Computer Science의 자료구조를 구체화하여 객체 클래스로 제공하고 있으며 개발자는 해당 클래스를 이용해 알고리즘을 구현할 수 있다.
원시 타입(Primitive Data Type)과 Wrapper 클래스
| 원시 타입 | 래퍼 클래스 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
래퍼 클래스는 기본형 타입이 가지고 있는 기능을 개선하여 여러 기능을 추가했다.
기본형 타입은 실제로 "값" 만 가지고 있다. 하지만 자주 쓰이는 정수, 실수, 문자 값들까지 모두 객체로 관리하면 메모리를 많이 차지하게 되므로 성능·비용문제까지 발생한다. 그래서 객체 대신 원시 타입을 사용한다.
Integer num = new Integer(17); // Boxing
int n = num.intValue(); // UnBoxing
Character ch = 'X'; // AutoBoxing
char c = ch; // AutoUnBoxing