네트워크 캠퍼스/JAVA
240117 자바의 예외처리(Exception, Runtime Exception, try~catch, 다중 catch, throws)
gayeon_
2024. 1. 17. 15:55
자바의 예외처리
예외 처리(Exception)
- 에러에는 심각한 에러(serious error)와 가벼운 에러(mild error)가 있다. 심각한 에러는 시스템 상의 문제로 인한 에러로 개발자가 처리할 수 없는 에러를 말한다. 가벼운 에러는 개발자가 코드를 통해 처리할 수 있는 에러를 말하며 이 방식을 예외처리라고 한다.
- 예외에는 컴파일러 체크 예외(Checked Exception)와 실행 예외(Runtime Exception)가 있다.
- 컴파일러 체크 예외는 자바 소스를 컴파일하는 과정에서 예외 처리 코드를 검사하여 예외 처리 코드가 없다면 컴파일 오류가 발생한다.
- 실행 예외는 컴파일하는 과정에서 예외처리 코드를 검사하지 않는 예외이다.
- 예외 처리는 컴파일 예외와 실행 예외에 대한 대처방법이다.
- 예외 처리는 시스템 스스로 오류를 복구하는 것이 아니고 오류 발생 가능성이 있는 부분에 대한 처리를 미리 프로그래밍 해주는 것이다.
- 프로그램에서 문제가 될만한 부분을 예상하여 사전에 "문제가 발생하면 이렇게 처리하라" 라고 프로그래밍 하는 것을 예외 처리라고 한다.
- 예외 발생시에는 System.out.println(); 대신 System.err.println();으로 메세지를 띄우는게 좋다.
실행 예외(Runtime Exception)
- 실행 예외는 컴파일러가 예외 처리 코드를 체크하지 않기 때문에 오로지 개발자의 경험에 의해서 예외 처리 코드를 삽입해야 한다.
- 만약 개발자가 실행 예외에 대해 예외처리 코드를 넣지 않았을 경우 해당 예외가 발생하면 프로그램은 곧바로 종료된다.
주요 실행 예외
NullPointerException
객체 참조가 없는 상태, 즉 null 값을 갖는 참조 변수로 객체 접근 연산자인 dot(.)를 사용했을 때 발생한다.
ArrayIndexOutOfBoundsException
배열에서 인덱스 범위를 초과하여 사용할 경우 발생한다.
NumberFormatException
문자열로 되어 있는 데이터를 숫자로 변경하는 경우에 발생한다.
ClassCastException
형 변환은 부모 클래스와 자식 클래스간에 발생하고 구현 클래스와 인터페이스 간에도 발생한다.
이러한 관계가 아니라면 다른 클래스로 타입을 변환할 수 없다.
상속관계나 인터페이스 관계가 없는 클래스들을 억지로 형 변환 할 경우 발생한다.
NullPointerException 예제
package exception.runtime;
public class NullPointerExample {
public static void main(String[] args) {
String str = null;
// str = "HELLO";
// toLowercase()는 모든 문자를 소문자로 만들어준다.
System.out.println(str.toLowerCase());
}
}
ArrayIndexOutOfBoundsException 예제
package exception.runtime;
public class ArrayIndexExample {
public static void main(String[] args) {
int[] arr = {3, 6, 9};
System.out.println(arr[5]);
}
}
NumberFormatException 예제
package exception.runtime;
public class NumberFormatExample {
public static void main(String[] args) {
String a = "34";
String b = "21";
System.out.println(a + b);
// str -> int 변환
int i = Integer.parseInt(a); // 문자를 숫자로 변환
int j = Integer.parseInt(b); // 문자를 숫자로 변환
System.out.println(i + j);
// parseInt는 문자열 내부에 순수한 정수가 들어있어야 변환하며
// 정수값이 아니라면 NumberFormatExeption이 발생한다.
String str = "Hello";
System.out.println(Integer.parseInt(str));
}
}
ClassCastException 예제
package exception.runtime;
// 하나의 클래스파일에 2개 이상의 클래스 선언 가능(자주 사용하지는 않는다.)
// 상속관계: 부모 Animal을 상속한 자식 Dog, Cat
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}
public class ClassCastExample {
public static void main(String[] args) {
Dog d = new Dog();
Animal da = d;
d = (Dog)da;
System.out.println("타입 변환 성공: Animal -> Dog");
Animal c = new Cat();
Dog d2 = (Dog)c;
System.out.println("타입 변환 성공?: Dog -> Cat");
}
}
try~ catch
- 프로그램에서 예외가 발생했을 경우 프로그램의 갑작스러운 종료를 막고, 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드라고 한다.
- try~ catch ~ finally 블록은 생성자 내부나 메서드 내부에서 작성되어 컴파일 예외와 실행 예외가 발생할 경우에 예외 처리를 할 수 있게 한다.
- try 블록에는 예외 발생 가능성이 있는 코드를 작성한다. try 블록의 코드가 예외 발생 없이 정상 실행되면 catch 블록은 실행되지 않는다.
- try 내부에서 예외가 발생하면 즉시 실행을 멈추고 catch 블록으로 이동하여 예외 처리 코드를 실행한다.
- 예외 발생 여부와 상관없이 항상 실행할 내용이 있다면 finally 블록 내부에 실행 내용을 작성한다.
finally 구문이 실행되지 않는 경우
- finally 구문 이전에 System.exit() 구문을 호출했을 시
- 컴퓨터가 꺼져서 시스템이 멈추었을 시
- finally 블록 내부에서 예외가 발생했을 시
try~catch문을 사용하지 않았을 때 제어문으로 결과를 제어하는 예제
package exception.trycatch;
// 예외문이 없었을 때는 제어문으로 결과 제어
// 문제점: 가독성이 좋지 않다.
public class NoTryCatchExample {
public static void main(String[] args) {
int preValue = 5;
int nextValue = 2;
// nextValue에 0만 안 들어오면 예외 발생 없음
if(nextValue != 0) {
System.out.println(preValue / nextValue);
} else {
System.out.println("0을 나누는 값으로 넣을 수 없습니다.");
}
}
}
try~catch문을 사용해 예외를 처리한 예제
package exception.trycatch;
public class TryCatchExample1 {
public static void main(String[] args) {
int i = 10;
int j = 0;
// int j = 5;
try { // 예외가 발생할 수 있는 구문 기입
System.out.println(i / j); // 예외 발생 가능성 존재
System.out.println("예외 발생하지 않을 때만 실행됨");
} catch(Exception e) { // catch 블럭에는 Exception의 종류를 기입
System.out.println("0으로 나눠서 catch 블럭으로 넘어왔습니다.");
} finally { // try, catch 둘 중 어느블럭이라도 실행되면 마무리 블럭 실행
System.out.println("어쨌든 잘 마무리 했습니다.");
}
System.out.println("프로그램 종료!");
}
}
try~catch문을 사용해 예외를 처리한 예제2
package exception.trycatch;
public class TryCatchExample2 {
public static void main(String[] args) {
// 어떤 종류가 되었건 직접 예외가 발생할 수 있는 구문을 작성한 다음
// try~catch를 이용해 예외 발생시 처리되도록 프로그래밍하기
// 0으로 나누기를 제외한 나머지를 이용하기
String str = "JAVA";
int integer = 10;
try {
System.out.println(Integer.parseInt(str) + 10);
} catch(Exception e){
System.out.println("알파벳은 정수로 변환할 수 없습니다.");
} finally {
System.out.println("예외문 종료");
}
System.out.println("프로그램 종료");
}
}
다중 catch
- try 블록 내부는 다양한 종류의 예외가 발생할 수 있다. 예외가 여러 가지 발생한다면 다중 catch 블록을 작성하여 예외들을 처리한다.
- 다중 catch 블록을 작성할 때 주의할 점은 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 한다.
- catch 블록은 위에서부터 차례대로 검색되므로 상위 예외 클래스의 catch 블록이 위에 있다면 하위 예외 클래스의 catch블록은 실행되지 않는다.
- 자바 7 버전부터 하나의 catch 블록에서 여러 개의 예외를 처리할 수 있도록 기능이 추가되었다.
- catch() 괄호 안에 동일하게 처리하고 싶은 예외를 | 로 연결하면 된다. 이 방식을 사용할 때는 두 예외가 상속 관계가 있으면 안 된다.
다중 catch 예제
package exception.muti;
public class MultiCatchExample {
public static void main(String[] args) {
String data1 = "30";
String data2 = "11";
try {
// NumberFormatException 발생 가능
int i = Integer.parseInt(data1);
int j = Integer.parseInt(data2);
// ArithmeticException 발생 가능
int result = i / j;
System.out.println("i / j = " + result);
// NullPointerException 발생 가능
String str = null;
str.charAt(0); // 0번째 문자 얻기인데 null
} catch (NumberFormatException | NullPointerException e) {
System.err.println("데이터를 숫자만 넣어주세요.");
System.err.println("혹은 문자를 제대로 만들어주세요.");
} catch(ArithmeticException e) {
System.err.println("0으로 나눌 수 없습니다.");
} catch(Exception e) { // 범용 에러 처리 (대부분의 에러처리)
System.err.println("알 수 없는 에러가 발생했습니다.");
System.err.println("복구 중입니다.");
}
}
}
throws
- try ~catch 구문이 예외가 발생했을 때 직접 해결을 하고자 하는 코드라면 throws는 메서드나 생성자를 호출한 곳으로 예외를 떠넘기는 코드이다.
- 즉 예외처리를 직접 수행하지 않고 메서드 호출자에게 예외를 던지는 방법이다.
- throws 키워드가 붙어있는 메서드는 반드시 try 블록 내부에서 호출되어야 한다. 그리고 catch블록에서 떠넘겨 받은 예외를 처리해야 한다.
- main() 메서드에서 throws를 사용하는 것은 예외처리를 JVM에게 넘기겠다는 의미이다. 그러나 JVM은 그 예외를 직접 처리해주지 않고 예외가 발생하면 예외 메시지만 출력하고 프로그램을 종료시킨다.
throws 예제
package exception.throws_;
public class ThrowsExample {
public static String[] greetings = {"안녕", "싸웠다", "헬로"};
/*
* 예외의 원인이 메서드 선언부가 아닌 호출부에 있을 경우
* 메모리 영역이 다르므로 예외처리를 메서드 호출지역으로 떠넘겨줘야 한다.
* 이를 throws라고 하고, 메서드 혹은 생성자 호출 시 예외처리를 강요할 때 사용한다.
*/
// throws 오른쪽 종류 예외가 터지면 호출부(이 예제에서는 main)에게 처리를 떠넘기게 된다.
public static void greet(int idx) throws Exception {
// 메서드 안에 try~catch문을 사용할 수는 있지만 너무 복잡하고
// 객체지향적이지 않다.
// try {
System.out.println(greetings[idx]);
// } catch(ArrayIndexOutOfBoundsException e) {
// }
}
public static void main(String[] args) {
// throws가 붙어있는 메서드나 생성자 호출 시에는
// 해당 메서드를 try 블록 내부에서 호출해야 예외처리를 진행해준다.
// greet(3); // 3이 없는데 3 호출
// throws를 작성하니 greet(3);에 밑줄 발생 -> try~catch문 작성 필요
try {
greet(3);
} catch(Exception e) {
// .printStackTrace()는 예외발생 경로를 추적하는 메시지를 출력한다.
// 주로 개발 과정에서 예외의 원인을 역추적할 때 유용하다.
e.printStackTrace();
}
System.out.println("프로그램 정상 종료");
}
}
예외가 발생해서 프로그램이 비정상 종료가 된 것이 아닌
위 출력된 예외문구는 .printStackTrace()로 예외발생 경로가 출력된 것이다.
경로 출력 후 프로그램이 정상 종료되었다.