나는 프로그램을 구현하다보면 코드를 작성하는 시간보다 오류를 확인하고 처리하는데 많은 시간을 보낸다.
예외 처리 방법에 대해 알면 더 안전하고 유연한 프로그램을 작성할 수 있다.
그래서 오늘은 예외에 대해 알아보자!
결론부터 말하면 예외적인 상황을 대비하여 미리 안전장치를 두기 위해서다.
메모리가 부족할 때, 불러 올 파일이 존재하지 않거나, 서버 연결이 끊기거나, 정상적으로 운영되다가도 예외가 발생하면 프로세스가 강제로 종료된다. 프로그램을 실컷 잘 만들어놔도 이런 예외 상황에 대처할 수 없다면, 어플리케이션이 망가지게 되고 잘 작성한 코드 역시 쓸모없게 된다.
그래서 예외에 대해 알고 각 상황에 맞춰 코드를 짤 수 있어야한다.
자바의 에러는 크게 세가지 계층으로 나눌 수 있다.
어플리케이션 내 우리가 예상하고 대처할 수 있는 exception(예외)
컴파일러가 런타임 이전에 알 수 있는 예외라서 checked라고 한다.
컴파일러가 명시적으로 고쳐!! 라고 말하는 exception
예외가 발생할 경우 트랜잭션을 롤백하지 않는다.
check 예외는 반드시 try/catch 블록으로 처리를 해야한다.
FileNotFountException
존재하지 않는 파일에 접근
ClassNotFoundException
존재하지 않거나 도달할 수 없는 클래스로 인스턴스를 생성(나랑 가장 자주만난 오류ㅡㅡ)
사람때문에 일어나는 예외
컴파일 시점이 아닌 런타임 시점에 일어나기 때문에 RunTime Exceptions
라고도 한다.
명시적으로 예외 처리를 강제하지는 않는다.
예외가 발생할 경우 트랜잭션을 롤백한다.
ArrayIndexOutOfBoundsException
범위를 벗어난 배열의 요소에 접근
NullPointerException
null인 객체 참조
메모리 부족이나 시스템에 심각한 상황이 발생할 경우 일어나는 예외
복구가 어렵고 실질적으로 대처하기 어렵다.
StackOverFlow
순환 코드 작성
OutOfMemoryError
메모리 누수
checked exception을 다룰 때 가장 간단한 방법은 그냥 예외(exception)를 던지는(throw) 것이다.
throw : 에러를 고의로 발생시킬 때 사용
throws : 자신을 호출한 상위 메소드로 에러를 던지는 역할
public class File {
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
}
위의 코드를 보면 pathname이 Null일 경우 throw를 통해 강제로 에러를 발생시키는 것을 알 수 있다.
public class FileOutputStream {
public FileOutputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null, false);
}
}
위 클래스 생성자를 보면 throws FileNotFoundException이 있는데 이렇게 메소드나 생성자 선언부에 예외를 선언하면 메소드를 사용하려는 사람이 이 메소드를 사용하려면 어떤 예외 처리가 필요한지 쉽게 알 수 있다.
위의 방법들은 exception을 던지기 때문에 메소드의 흐름을 끝내버린다.
그래서 return 구문을 넣는다해도 도달할 수 없고 에러만 발생시킨다.
try {
예외 발생 가능성이 있는 문장
} catch(예외의 종류) {
예외가 발생하면 처리할 문장
}
일반적으로 사용되는 예외 처리 방법이다.
try
catch
즉 컴파일러에게 "이거 실행하면 excception 발생할 수 있는거 알아~ 발생하면 다룰게" 하고 알려주는 것과 같다.
finally는 try에서 무슨 일이 일어나도 실행이 되는 것을 말한다.
public String readFirstLin(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally{
if(br!=null) br.close();
}
}
일부러 예외를 발생시키는 throw를 사용해소 finally는 실행된다.
보통 try구문에서 열린 리소스를 닫는데 사용된다.
static String readFirstLineFromFile(String path) throws IOException{
try(BufferedReader br = new BufferedReader(new FileReader(path))){
return br.readLine();
}
}
try 구문을 괄호 안에 넣어 간단하고 축약된 버전으로 작성할 수 있다.
이 방식을 사용하면 리소스를 직접 닫을 필요가 없다. 왜냐면 자동으로 닫아줌
자바 7 부터 이 try-with-resources가 등장해 finally는 안좋은 방법으로 여겨지고 있다.
위의 사진으로 다시 가서 예외의 계층 구조에 대해 알아보자.
위 사진처럼 모든 예외의 조상은 Throwable(java.lang.Throwable) 클래스다.
먼저, 모든 클래스의 최상위 클래스인 Object를 확장한 예외의 조상 클래스 Throwable, 그리고 이를 확장한 Error 클래스와 Exception 클래스가 있다.
위에서도 설명했지만 Error는 자바 프로그램 외에서 발생하는 오류, Exception은 자바 프로그램 내에서 발생하는 오류다.
checked와 unchecked를 보면 그냥 unchecked도 컴파일러가 처리하면 안돼?라는 생각이 들 수도 있다.
하지만 모든 참조 변수는 NullPointException이 발생할 수 있고 모든 배열도 ArrayIndexOutOfBoundsException이 발생할 수 있다.
모든 참조 변수는 널 값이 대입될 수 있고, 모든 배열은 인덱스 범위를 넘어갈 가능성이 있다. 만약 이런 것들의 예외처리를 컴파일러가 강제적으로 하게된다면 모든 배열과 모든 참조변수에 예외처리를 하게 될 것이다ㅜ
그래서 checked와 unchecked로 나눠 선택적으로 예외를 처리하도록 한 것이다.
글의 앞부분에서 예외 처리는 더 안전하고 유연한 프로그램을 작성할 수 있도록 한다고 말했었다.
이를 위해 우리는 코드를 작성할 때 발생할 예외를 잘 파악하고 핸들링하는 방법에 대해 늘 고민을 해야한다고 생각했다!!
끝!
출처
https://velog.io/@pearl0725/%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-%EC%98%88%EC%99%B8%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95
https://juneyr.dev/2019-10-04/exception-handling-translation
http://www.tcpschool.com/java/java_exception_class