본문 바로가기

자바

예외처리

출처 : '이것이 자바다'

 

컴퓨터 하드웨어의 고장으로 인해 응용프로그램 실행 오류가 발생하는 것을 자바에서는 "에러"라고 한다.
개발자가 어떤 노력을 해도 이를 해결할 방법은 없다.
자바에서는 에러 이외에 "예외"라고 부르는 오류가 있다. 의도하지 않은 대로 프로그램을 쓰다가 뻗는 경우를 의미한다.
하지만 예외처리를 해준다면 에러와 다르게 프로그램을 계속 실행하는 상태로 유지하는게 가능하다.
 
예외에는 두가지가 존재한다.
1.일반 예외(Exception : 컴파일러가 예외 처리 코드 여부를 검사하는 예외다.)
 -> IDE를 사용해서 코드를 실행하기 전에 밑에 빨간줄이 끄이면서 왜 오류가 뜨는지 나타나는 경우다. 몰랐는데 이것도 예외 클래스에서 해주는 거였구나..
2.실행 예외(Runtime Exception : 컴파일러가 예외 처리 코드 여부를 검사하지 않는 예외를 말한다.)
 -> 코드를 실행했는데 프로그램이 동작하다가 뻗는 경우를 의미한다.
자바는 예외가 발생하면 예외 클래스로부터 객체를 생성한다.이 객체가 예외 처리 시 사용된다. 자바의 모든 에러와 예외는 Throwable을 상속받아서 만들어진다.
(예외 클래스는 java.lang.Exception클래스를 상속받는다.)
 
예외의 종류

일반 예외를 제외하면 그냥 전부 실행 예외다. 자바에서는 예외 클래스를 ㅍ준 라이브러리로 제공해준다. 위에 그림속 예외가 전부 그렇다.
예외 처리 코드
예외가 발생했을때 앱이 갑자기 죽는데 이를 막는 역할을 해주는 코드를 예외 처리 코드라고한다.
try - catch - finally 로 구성된다. 이 블록은 생성자 내부와 메소드 내부에서 작성된다.
 
코드의 흐름상 예외처리가 발생하지 않으면
try -> finally 로 흐른다.
 
코드의 흐름상 예외처리가 발생하게되면
try -> catch -> finally 로 흐른다.
아마 아래 코드를 실행해보면 빠르게 확인이 가능할 것이다.
public class Test {
    public static void main(String[] args) {
        try{
            int tmp[] = {4,2,3};
            System.out.println(tmp[4]);
        } catch (Exception e){
            System.out.println("Exception occured");
            
            // 아래 세개는 에러를 얻는 방법이다. 마지막 방법이 제일 자세히 설명해준다.
            System.out.println(e.getMessage());
            System.out.println(e.toString());
            e.printStackTrace();
        } finally {
            System.out.println("running well");
        }
    }
}
try 블록과 catch 블록에서 return 문(메소드 종료)를 사용해도 finally 블록은 항상 실행된다고 한다.
fianlly 블록은 옵션으로 생략이 가능하다.
 
예외 종류에 따라서 다르게 처리하기
try 문에서는 다양한 에러가 발생할 수 있다. catch 문을 여러개 달아놓으면 발생한 예외에 맞게 catch를 찾아가게 된다. 단, 하나의 catch로만 가게된다. 예외가 발생하는 즉시 catch문으로 빠지고 뒷내용으로 코드가 흘러가지 않는다.
public class Test {
    public static void main(String[] args) {
        String[] array = {"100","l00"};
        for(int i=0; i<array.length;i++){
            try{
                int value = Integer.parseInt(array[i]);
                System.out.println("array["+i+"]:"+value);
            } catch (ArrayIndexOutOfBoundsException e){
                System.out.println("배열 인덱스 초과 : "+e.getMessage());
            } catch (NumberFormatException e){
                System.out.println("숫자로 변환 불가 : "+e.getMessage());
            }
        }
    }
}
처리해야 할 예외 클래스들이 상속 관계에 있을때 하위 클래스 catch 블록을 먼저 작성하고 상위 클래스 catch 블록을 나중에 작성해야 한다.(자식에서 부모 순으로) 예외가 발생하면 catch 블록은 위에서 부터 차래대로 검사하는데, 하위 예외도 상위 클래스 타입이므로 상위 클래스 catch블록이 먼저 검사 대상이 되면 안된다.
이렇게 하면 안되고 ...
try{
    ArrayIndexOutofBoundsException 발생
    NumberFormatExcetpion 발생
} catch(Exception e){
    // 위 두개의 에러가 이쪽으로 빠져버린다.
    예외 처리1
} catch(ArrayIndexOutoutBoundsException e){
    예외 처리2
}

 

이렇게 해야 한다. 상위 예외 클래스는 아래쪽에 작성하는게 맞다.
try{
    ArrayIndexOutofBoundsException 발생
    NumberFormatExcetpion 발생
} catch(ArrayIndexOutoutBoundsException e){ 
    예외 처리1 
} catch(Exception e){     
    예외 처리2 
}
예외 떠넘기기
예외 출력을 메소드 안에서 하는 것을 위에서 살펴봤다. 하지만 호출했던 부분으로 예외를 떠넘기는게 가능하다. 이때 사용하는 키워드가 throws 다.
아래 구문을 참조.
리턴타입 메소드명(매개변수, ...) throws 예외클래스1, 예외클래스2, ...{
}
throws 키워드가 붙어 있는 메소드에서 해당 예외를 처리하지 않고 떠넘겼기 때문에 이 메소드를 호출하는 곳에서 예외를 받아 처리해야 한다.
ClassNotFoundException을 throws 하는 metohd2() 의 예외를 method1() 에서 호출할 때 처리하고 있다.
public void method1() {
 try{
    method2();
} catch(ClassNotFoundExcetption e) {
    System.out.prinltn(e.getMessage());
 }
}

public void method2() throws ClassNotFoundException {
    Class.forName("java.lang.String2");
}
아래 예제를 보면 바로 알 수 있다. throws 예약어를 사용해서 예외가 호출한 부분에서 발생하는 사실을 말이다.
public class Test {
    public static void main(String[] args) {
        try{
            findClass();
        } catch (ClassNotFoundException e){
            System.out.println(e.toString());   // java.lang.ClassNotFoundException: java.lang.String2
        }
    }
    public static void findClass() throws ClassNotFoundException{
        Class.forName("java.lang.String2");
    }
}
만약에 넘겨줘야 할 예외가 많으면 throws Exception 또는 throws Throwable 만으로 모든 예외를 처리하는게 가능하다.
리턴타입 메소드명(매개변수) throws Exception {

}
main() 메소드에서도 throws 키워드를 사용해서 예외를 떠넘길 수 있는데, 결국 JVM이 최종적으로 예외 처리를 하게 된다. JVM은 예외의 내용을 콘솔에 출력하는 것으로 예외 처리를 한다.
public static void main(String[] args) throws Exception {

}
사용자 정의 예외
직접 예외를 정의할 수도 있다. 일반예외나 실행예외를 상속받는 클래스를 만들어서 사용하면 된다.
치킨을 사먹을려고 하는데 나는 5000원이 있고 치킨가격은 10000원이라고 치자. 5000원 이 부족하니 예외를 뱉어내게끔 한번 만들어 보겠다.
통상적으로 일반 예외는 Exception의 자식 클래스를 선언하고, 실행 예외는 RuntimeException의 자식 클래스로 선언한다.
//일반 예외를 상속받는다.
public class XXXException extends Exception{
 //기본 생성자
 public XXXException() {
}

public XXXException(String message){
 // 예외 메세지를 입력받는 생성자.
 super(message);
 }
}
//실행 예외를 상속받는다. 
public class XXXException extends RuntimeException{ 
 //기본 생성자
 public XXXException() {
}

public XXXException(String message){
 // 예외 메세지를 입력받는 생성자. 
 super(message);
 }
}
예외 메세지를 부모 생성자의 매개값으로 넘겨주는데, 그 이유는 예외 객체의 공통 메소드인 getMessage()의 리턴값으로 사용하기 위해서이다.
아래는 돈이 부족할때를 대비한 예외를 정의한 클래스다.
// 일반 예외를 상속받은 사용자정의 예외처리 클래스다.
public class InsufficientException extends Exception{
    public InsufficientException() {

    }

    public InsufficientException(String message){
        super(message);
    }
}
public class Account {
    private long balance;
    public Account() {}

    public long getBalance() {
        return balance;
    }

    public void deposit(int money){
        balance += money;
    }

    public void withdraw(int money) throws InsufficientException{
        if(balance < money){
            throw new InsufficientException("금액 부족 : "+(money-balance)+" 모자람");
        }
        balance -= money;
    }
}
public class AccountExample {
    public static void main(String[] args) {
        Account account = new Account();
        
        account.deposit(5000);;
        System.out.println("가진금액 : "+account.getBalance());
        
        try {
            account.withdraw(10000);
        } catch (InsufficientException e){
            String message = e.getMessage();
            System.out.println(message);
        }
    }
}
가진금액 : 5000
금액 부족 : 5000 모자람
 
Throwable 상속도

 

'자바' 카테고리의 다른 글

추상화란 추상클래스란  (0) 2023.10.23
객체지향의 4대 특징  (4) 2023.10.22
리스코프 치환원칙  (0) 2023.10.18