- 오류 및 예의에 대한 이해
- 예외 발생과 try-catch, finally 문
- 예외 클래스 구조 이해하기
- Chained Exception, 실제 예외 처리하는 방법
1. 오류 및 예외에 대한 이해
프로그램을 작성하다 보면 예외 상황이 발생한다. 예를 들어 사용자가 숫자가 아닌 문자를 입력하는 경우, 프로그램의 메모리가 감당할 수 없는 숫자가 입력되는 경우, 프로그램을 돌리는 메모리가 부족한 경우 등등...
이러한 상황들의 예외를 미리알고 해결할 수 있다면 더욱 완벽한 프로그램을 만들 수 있을 것이다.
완벽한 프로그램을 만들기전에 오류와 예외에 대한 이해를 해야 한다.
1-1 오류
- 오류(Error)는 일반적으로 회복이 불가능한 문제이다.
- 이는 시스템 레벨에서, 또는 주로 환경적인 이유로 발생
- 코드의 문제로 발생하는 경우도 있지만, 일단 발생하는 경우 일반적으로 회복이 불가능
- 에러가 발생한 경우 우리는 어떠한 에러로 프로그램이 종료되었는지를 확인하고 대응
1-2 예외
- 예외(Exception)는 일반적으로 회복이 가능한 문제
- 회복이 가능하다는 전제는 우리가 “그 예외가 발생할 수 있다는 것을 인지하고, 대응했을 것”
- 현실적으로 코드레벨에서 할 수 있는 문제상황에 대한 대응은 “예외처리”에 속한다.
1-3 관점에 서서의 예외
1) 코드실행 관점에서의 예외
- 컴파일 에러(예외)
- . java 파일을. class 파일로 컴파일할 때 발생하는 에러
- 대부분 여러분이 자바 프로그래밍 언어의 규칙을 지키지 않았기 때문에 발생
- 예를 들어 있지 않은 클래스를 호출한다거나, 접근이 불가능한 프로퍼티나 메서드에 접근한다거나 하는 경우에 발생
- 컴파일 에러가 발생하는 경우 해결 방법은 문법에 맞게 다시 작성하는 것
- 런타임 에러(예외)
- 우리가 주로 다루게 될 에러(예외)
- 문법적인 오류는 아니라서, 컴파일은 잘 되었지만 “프로그램”이 실행도중 맞닥뜨리게 되는 예외
2) 예외처리 과넘에서의 예외
- 확인된 예외(Checked Exception)
- 컴파일 시점에 확인하는 예외
- 반드시 예외 처리를 해줘야 하는 예외
컴파일 시점의 예외는 이미 특정한 문제를 인지하고 있어서, 해당 예외를 정의해 두었고, 정의해 두었기 때문에 컴파일하는 동안 이 예외에 대한 예외처리를 했는지 확인(Check) 할 수 있는 예외. ”즉 Checked Exception에 대한 예외처리를 하지 않으면 컴파일 에러가 발생!”
3) 미확인된 예외 (Unchecked Exception)
- 런타임 시점에 확인되는 예외
- 예외 처리가 반드시 필요하지 않은 예외
2. 예외 발생과 try-catch, finally 문
예외처리의 흐름
1. 우리가 예외를 어떻게 정의하고,
2. 예외가 발생할 수 있음을 알리고,
3. 사용자는 예외가 발생 할 수 있음을 알고 예외를 핸들링하는지
예외 정의하기
직접 다음과 같이 우리만의 에러를 정의할 수 있다.
class OurBadException extends Exception {
public OurBadException() {
super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
}
}
클래스를 만들고, 메서드를 만들며 우리의 메서드가 위험하다고 알리기(throw, throws)
class OurClass {
private final Boolean just = true;
// 신규 문법 throws!
public void thisMethodIsDangerous() throws OurBadException {
if (just) {
// 신규 문법 throw!
throw new OurBadException();
}
}
}
throws | throw |
메서드 이름 뒤에 붙어 이 메서드가 어떠한 예외사항을 던질 수 있는지 알려주는 예약어 | 메서드 안에서, 실제로 예외 객체를 던질 때 사용하는 예약어 이다. |
여러 종류의 예외사항을 적을 수 있다. | 실제로 던지는 예외 객체 하나와 같이 써야 한다. |
일반 메서드의 return 키워드처럼 throw 아래의 구문들은 실행되지 않고, throw문과 함께 메서드가 종료됩니다. |
메서드를 선언할 때, 이 메서드가 위험하다는 것을 미리 예측해야 한다.
예측이 된다면, throw 키워드와 함께 이 메서드가 위험하다고 알려야 한다.
위험한 메서드를 사용한다면 예외를 handling 해야 한다.
public class StudyException {
public static void main(String[] args) {
OurClass ourClass = new OurClass();
try {
// 1. 위험한 메소드의 실행을 "시도"
// "시도" 해보는 코드가 들어가는 블럭
ourClass.thisMethodIsDangerous();
} catch (OurBadException e) {
// 2. 예외가 발생하면, "잡아서" handling 한다.
// 예외가 발생하는경우 "handling" 하는 코드가 들어가는 블럭
// 즉 try 블럭 내의 구문을 실행하다가 예외가 발생하면
// 예외가 발생한 줄에서 바로 코드 실행을 멈추고
// 여기 있는 catch 블럭 내의 코드가 실행
System.out.println(e.getMessage());
} finally {
// 3. 예외의 발생 여부와 상관없이, 실행시켜야 하는 코드가 들어간다.
// 무조건 실행되는 코드가 들어가는 블럭
System.out.println("우리는 방금 예외를 handling 했습니다!");
}
}
}
1. 위험 감지하기
- 우리는 특정한 클래스의 메서드를 사용할 때, 특히 우리가 작성한 코드가 아니라면 더더욱, 이 메서드가 위험한지 알아봐야 한다. 보통 메서드의 명세를 확인할 수 있으며 intellij를 사용중이라면, 커서를 올려 간단하게 확인 할 수 있다.
2. 위험을 감지했다면, try-catch(finally) 키워드 이용하기
- **try** - **catch** 는 각각 중괄호{}를 통해 실행할 코드들을 담는다.
- **try** 단어의 “시도한다”라는 뜻에 맞게 의 중괄호{} 안에는 예외가 발생할 수 있지만 실행을 시도할 코드를 담는다.
- **catch** 단어의 **“잡는다”**라는 의미에 맞게 중괄호{} 안에는 try 안에 있는 코드를 실행하다가 예외가 났을 때 실행할 코드를 담는다.
- catch는 소괄호()를 통해 어떤 예외클래스를 받아서 처리할지 정의해주어야 한다.
- catch로 모든 예외를 다 받고 싶으면 Exception 을 넣어주면 된다.
- catch 로 일부 예외만 받아서 처리하고 싶으면 해당 예외 클래스명을 넣어주면 된다.
- 1개의 try 문에 catch 문은 여러 개 사용할 수 있다. ex) 1 try : 4 catch
- 기존 **try - catch** 의 맨 마지막에 **finally** 를 붙여서 마지막에 반드시 실행할 코드를 넣을 수 있다.
3. 예외 클래스 구조 이해하기
- 자바의 Throwable Class
- 시작은 모든 객체의 원형인 Object 클래스에서 시작
- 아까 정의한 “문제 상황”을 뜻하는 Throwable 클래스가 Object 클래스를 상속
- Throwable 클래스의 자식으로 앞서배운 에러(Error)와 예외(Exception) 클래스가 있다.
- 에러(Error) 클래스와 예외(Exception) 클래스는 각각 IOError 클래스, RuntimeException 클래스와 같이 구분하여 처리된다.
NullPointException, ArrayIndexOutOfBoundsException 등의 예외 구현체들은 명시적인 에러처리를 하지 않아도 컴파일 에러가 발생하지는 않는다.
Checked Exception에 속하는 에러 구현체들은 핸들링하지 않으면 컴파일 에러가 발생하는 대신, 컴파일이 됐다면 100% 복구가 가능한 에러였다는 것 역시 알아두시면 좋을 것 같다.
// 출처 : https://programming.guide/java/list-of-java-exceptions.html
java.io
IOException
CharConversionException
EOFException
FileNotFoundException
InterruptedIOException
ObjectStreamException
InvalidClassException
InvalidObjectException
NotActiveException
NotSerializableException
OptionalDataException
StreamCorruptedException
WriteAbortedException
SyncFailedException
UnsupportedEncodingException
UTFDataFormatException
UncheckedIOException
java.lang
ReflectiveOperationException
ClassNotFoundException
InstantiationException
IllegalAccessException
InvocationTargetException
NoSuchFieldException
NoSuchMethodException
CloneNotSupportedException
InterruptedException
산술 예외
IndexOutOfBoundsException
ArrayIndexOutOfBoundsException
StringIndexOutOfBoundsException
ArrayStoreException
ClassCastException
EnumConstantNotPresentException
IllegalArgumentException
IllegalThreadStateException
NumberFormatException
IllegalMonitorStateException
IllegalStateException
NegativeArraySizeException
NullPointerException
SecurityException
TypeNotPresentException
UnsupportedOperationException
java.net
HttpRetryException
SocketTimeoutException
MalformedURLException
ProtocolException
SocketException
BindException
ConnectException
NoRouteToHostException
PortUnreachableException
UnknownHostException
UnknownServiceException
URISyntaxException
java.text
ParseException
java.time
DateTimeException
java.time.zone
ZoneRulesException
4. Chained Exception, 실제 예외 처리하는 방법
- 연결된 예외 (Chained Exception)
- 예외는 다른 예외를 유발할 수 있다.
- 예외 A가 예외 B를 발생시켰다면, 예외 A는 B의 원인 예외이다.
- 원인 예외를 새로운 예외에 등록한 후 다시 새로운 예외를 발생시키는데, 이를 예외 연결이라고 한다.
왜 예외를 연결해야 하는가?
- 예외를 연결하는 이유는 여러 가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위함이다.
- checked exception을 unchecked exception으로 포장(wrapping)하는데 유용하게 사용되기도 한다.
- 원인 예외를 다루기 위한 메서드
- initCause()
- 지정한 예외를 원인 예외로 등록하는 메서드
- getCause()
- 원인 예외를 반환하는 메서드
- initCause()
// 연결된 예외
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: 진짜 예외이유
실질적으로 예외를 처리하는 방법으로는 예외 복구, 예외 처리 회피, 예외 전환 이 있다.
1. 예외 복구하기
public String getDataFromAnotherServer(String dataPath) {
try {
return anotherServerClient.getData(dataPath).toString();
} catch (GetDataException e) {
return defaultData;
}
}
- 실제로 try-catch로 예외를 처리하고 프로그램을 정상 상태로 복구하는 방법이다.
- 가장 기본적인 방식이지만, 현실적으로 복구가 가능한 상황이 아닌 경우가 많거나 최소한의 대응만 가능한 경우가 많기 때문에 자주 사용되지는 않는다.
2. 예외 처리 회피하기
public void someMethod() throws Exception { ... }
public void someIrresponsibleMethod() throws Exception {
this.someMethod();
}
- 이렇게 처리하면, someMethod()에서 발생한 에러가 someIrresponsibleMethod()의 throws를 통해서 그대로 다시 흘러나가게 되겠죠, 물론 같은 객체 내에서 이런 일은 하지는 않습니다, 예외처리 회피를 보여드리기 위한 단순한 예시 코드이다.
- 관심사를 분리해서 한 레이어에서 처리하기 위해서 이렇게 에러를 회피해서 그대로 흘러 보내는 경우도 있다.
3. 예외 전환하기
public void someMethod() throws IOException { ... }
public void someResponsibleMethod() throws MoreSpecificException {
try {
this.someMethod();
} catch (IOException e) {
throw new MoreSpecificException(e.getMessage());
}
}
- 예외처리 회피하기의 방법과 비슷하지만, 조금 더 적절한 예외를 던져주는 경우이다.
- 보통은 예외처리에 더 신경 쓰고 싶은 경우나, 오히려 RuntimeException처럼 일괄적으로 처리하기 편한 예외로 바꿔서 던지고 싶은 경우 사용한다.
오늘 자바 try catch 구문을 공부하면서 느낀 점은 많이 안 써봐서도 그렇지만 if else로 처리하던 것을 try-catch로 다시금 하려니 너무 방대한 양이다. 아마도 코드를 짜보면서 에 외상황에 대해서 하나하나 경험해 봐야 어느 정도 마스터 할 것 같다.
'배운내용 정리' 카테고리의 다른 글
Java Thread(1) (0) | 2023.10.24 |
---|---|
Java Generic (1) | 2023.10.23 |
자바의 구조체 (0) | 2023.10.18 |
객체지향(3) (1) | 2023.10.17 |
객체지향(2) (0) | 2023.10.17 |