Java

[Java] 10. 예외처리 (1)

hyomee2 2024. 8. 21. 14:37

: 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것

1. 오류와 예외

(1) 오류

: 시스템 상에서 프로그램에 심각한 문제가 발생하여 실행중인 프로그램이 종료되는 것

- 개발자가 미리 예측하거나 코드로 처리하는 것이 불가능한 경우를 말한다.

- ex) JVM 에러, 메모리 초과, 정전 등

 

(2) 예외

: 개발자가 미리 예측하고 처리할 수 있는 미약한 오류

- 개발자가 적절히 처리하여 코드의 흐름은 컨트롤(비정상적인 종료 or not) 할 수 있다.

 

 

2. 예외 클래스의 종류

(1) 예외 클래스의 계층 구조

 

 

3. Checked Exception과 Unchecked Exception

- Checked Exception컴파일 예외클래스, Unchecked Exception런타임 예외클래스를 가리킨다고 생각하면 된다.

- Checked Exception 계열반드시 예외처리를 해야하지만, unchecked Exception 계열명시적으로 해주지 않아도 된다.

- Checked Exception 계열은 체크하는 시점이 컴파일 단계이기 때문에, 예외처리를 해주지 않으면 컴파일 자체가 되지 않으며 컴파일 에러가 발생한다. 따라서 Checked Exception이 발생할 가능성이 있는 메소드라면 반드시 로직을 try-catch로 감싸거나 throws로 던져서 처리해주어야 한다.

 

(1) Checked Exception을 Unchecked Exception으로 변환하기

- 앞에서 checked Exception은 반드시 예외처리를 해주어야 한다고 했는데, 예외처리 하는 것이 귀찮기도 하고, 가독성을 해치기도 한다.

- 따라서 아래와 같이 chained exception을 이용하여 checked exception을 unchecked exception으로 바꿔주면 예외처리를 반드시 해주지 않아도 된다.

- 이런 방식이 나오게 된 배경을 살짝 살펴보면,

처음 Java을 개발했을 때 견고한 프로그램을 작성할 수 있도록 유도하기 위해 심각하지 않음에도 checked exception으로 등록한 것이 많다. 또 Java가 처음 개발될 때와 현재의 컴퓨터 환경은 많이 달라졌기에 runtime exception으로 처리해도 되는 경우에도 checked exception으로 등록되어 있기도 하다. 따라서 이런 경우 예외처리를 필수적으로 해줘야 하는 번거로움을 해결하기 위해 추가된 기법이다.

class CheckedException extends Exception { ... } // Checked Excpetion

public class Main {
    public static void main(String[] args) {
            test();
    }

    public static void test() {
        // Checked Exception인 IOException을 Unchecked Exception인 RuntimeException으로 감싸 Unchecked Exception으로 변환시켜준다
        throw new RuntimeException(new IOException("IOException"));
    }
}

 

 

4. throw을 이용한 예외 발생시키기

- 프로그램적인 에러가 아니라도, 로직상 개발자가 일부러 에러를 내서 로그에 기록하고 싶은 상황이 올 수 있다.

  그럴 때 throw 키워드를 사용하면 강제로 예외를 발생시킬 수 있다.

- 아래에서 다루는 throws와는 다른 개념이므로 유의해야 한다!

- ex. throw new FoolException();

- 실제 사용 예시는 아래 코드들에서 확인할 수 있다.

 

5. 예외 처리 방법

(1) try-catch-finally

- 발생한 exception을 직접 처리하는 방식

1) try 블럭

- Exception이 발생할 가능성이 있는 코드를 포함해 작성하는 블럭

2) catch 블럭

- try 블럭에서 예외 발생 시 해당 예외 타입에 대한 처리에 관한 코드를 작성하는 블럭

- 여러 개의 catch 블럭을 이어서 사용할 수 있지만, 상위 타입의 예외를 처리하는 catch 블럭이 하위 타입의 예외를 처리하는 catch 블럭보다 더 아래에 있어야 한다.

3) finally 블럭

- 예외 발생여부와 상관없이 실행되어야 하는 코드를 작성하는 블럭

- 주로 java.io나 java.sql 패키지의 메소드 처리 시 자원 반납을 위해 사용한다.

- 생략이 가능하며, 생략하게 될 경우 try-catch 문이 된다.

try-with-resource 구문

- 입출력에서 사용되는 스트림의 자원반납(close())을 finally 블럭을 사용않고 용이하게 처리하기 위해 도입된 문법이다.

try (BufferedReader in = new BufferedReader(new FileReader("test.dat"));){
	
	String s;
	
	while((s = in.readLine()) != null){
		System.out.println(s);
	}

} catch (FileNotFoundException/* | EOFException*/ e) {
	e.printStackTrace();

} catch (IOException e) {
	e.printStackTrace();
}

 

(2) throws를 이용한 예외 던지기

: Exception이 발생하는 메소드/생성자를 호출한 상위 메소드에게 처리를 위임하는 방식

 

 

6. 오버라이딩 시 예외 발생 가능 범위

- 상속 시 오버라이딩하는 메소드는 부모 클래스의 원본 메소드보다 더 상위 타입의 예외를 발생시켜선 안된다.

- 아래의 경우에는 Parent 클래스를 상속받는 Child1과 Child2 클래스가 있는데,

  Parent 클래스의 method()가 IOException을 throws해주는데

  Child2에서는 IOException보다 상위 타입의 예외인 Exception을 throws해주므로 올바르지 않다.

  cf. 같은 타입의 예외는 발생시킬 수 있다.

 

 

7. 예외 활용하기

- 아래 예시의 경우 sayNick 메서드는 "바보" 라는 문자열이 입력되면 return으로 메서드를 종료한다.

public class Sample {
    public void sayNick(String nick) {
        if("바보".equals(nick)) {
            return;
        }
        System.out.println("당신의 별명은 "+nick+" 입니다.");
    }

    public static void main(String[] args) {
        Sample sample = new Sample();
        sample.sayNick("바보");
        sample.sayNick("야호");
    }
}
// 위의 코드 실행 결과
당신의 별명은 야호 입니다.

 

(1) RuntimeException

- 아래 코드를 실행시키게 되면 "바보" 라는 입력값을 받게 되면 아래와 같은 예외가 발생하게 된다.

- 이 때 FoolException이 상속받은 클래스는 runtimeException이므로 런타임 에러가 발생했다.

class FoolException extends RuntimeException {
}

public class Sample {
    public void sayNick(String nick) {
        if("바보".equals(nick)) {
            throw new FoolException();
        }
        System.out.println("당신의 별명은 "+nick+" 입니다.");
    }

    public static void main(String[] args) {
        Sample sample = new Sample();
        sample.sayNick("바보");
        sample.sayNick("야호");
    }
}
Exception in thread "main" FoolException
    at Sample.sayNick(Sample.java:7)
    at Sample.main(Sample.java:14)

 

(2) Exception

- 위에서는 FoolException이 RuntimeException을 상속받았지만, 이번에는 Exception을 상속 받게 해본다.

  이렇게 되면 Sample 클래스에서 컴파일 오류가 발생한다.

1) try-catch문 이용하기

- 컴파일 오류를 막기 위해 아래와 같이 try-catch문을 이용하여 FoolException을 처리할 수 있다.

class FoolException extends Exception {
}

public class Sample {
    public void sayNick(String nick) {
        try {
            if("바보".equals(nick)) {
                throw new FoolException();
            }
            System.out.println("당신의 별명은 "+nick+" 입니다.");
        }catch(FoolException e) {
            System.err.println("FoolException이 발생했습니다.");
        }
    }

    public static void main(String[] args) {
        Sample sample = new Sample();
        sample.sayNick("바보");
        sample.sayNick("야호");
    }
}

 

2) throws을 이용한 예외 던지기

- try-catch 문을 이용하는 대신, 메소드(여기선 sayNick())를 호출한 곳에서 FoolException을 처리하도록 예외를 던질 수도 있다.

- 아래의 경우에는 FoolException를 처리해야 하는 대상이 sayNick 메서드에서 main 메서드(sayNick 메서드를 호출하는 메서드)로 변경된다. 따라서 컴파일 오류를 해결하려면 아래와 같이 main 메서드에 exception 처리를 할 수 있는 try-catch문을 추가해주어야 한다.

public class Sample {
    public void sayNick(String nick) throws FoolException {
        if("바보".equals(nick)) {
            throw new FoolException();
        }
        System.out.println("당신의 별명은 "+nick+" 입니다.");  
    }

    public static void main(String[] args) {
        Sample sample = new Sample();
        try {
            sample.sayNick("바보");
            sample.sayNick("야호");
        } catch (FoolException e) {
            System.err.println("FoolException이 발생했습니다.");
        }
    }
}
// 결과는 아래와 같다.
FoolException이 발생했습니다.

 

(3) 예외 메세지 출력하기

- catch문의 Exception e에서 e는 변수로, 이 객체 변수 안에는 printStackTrace(), getMessage() 등 에러 메세지를 출력하는 메소드가 들어있다.

1) printStackTrace(): 예외 발생 당시 호출스택에 있었던 메소드의 정보와 예외 메세지를 화면에 출력. 에러 추적 메세지를 상세하게 내보이기 때문에, 보안을 위해 관계자만 확인할 수 있도록 만들어 주는 것이 좋다.

2) getMessage(): 발생한 예외클래스의 인스턴스에 저장된 메세지 get

try {

    System.out.println(0/0); // ArithmeticException 예외 발생
} catch(ArithmeticException e){

	// 에러 메세지
    System.out.println(aa.getMessage()); // by zero

	// 상세한 에러 추적 메세지
	e.printStackTrace(); // java.lang.ArithmeticException: / by zero at MyClass.main(MyClass.java:5)
}

 

 

그러면 위의 FoolException의 경우에, 

FoolException 처리를 sayNick 메서드에서 하는 것이 좋을까,

아니면 throws을 이용해 main 메서드에서 예외처리를 해주는 것이 좋을까?

 

두 방식에는 아주 큰 차이가 있다.

sayNick 메서드에서 예외를 처리해주는 경우에는 

 

sample.sayNick("바보");
sample.sayNick("야호");

 

위의 두 문장이 모두 수행된다. 

 

하지만 main 메서드에서 예외 처리를 한 경우에는 첫번째 문장에서 예외가 발생하여 catch문으로 빠져버리기 때문에

두 번째 문장인 sample.sayNick("야호")는 수행되지 않는다.

 

따라서 적절한 위치에서 예외 처리가 필요하다.

 

 

8. 사용자 정의 예외 클래스

(1) Exception들

// MoneyNegativeException 처리: 가지고 있는 돈이 음수일 때의 예외처리
public class MoneyNegativeException extends NegativeException {
    public MoneyNegativeException(String message) {
        // 부모 클래스인 NegativeException의 생성자를 호출해서 예외 메세지를 설정한다.
        // NegativeException의 생성자는 Exception의 생성자를 호출하여 메세지를 설정한다.
        super(message);
    }
}

// NegativeException 처리
public class NegativeException extends Exception {
    public NegativeException(String message) {
        // 부모 클래스인 Exception의 생성자를 호출해서 예외 메세지를 설정한다.
        super(message);
    }
}

// NotEnoughMoneyException 처리: 상품 가격보다 가진 돈이 적을 때의 예외처리
public class NotEnoughMoneyException extends Exception {
    public NotEnoughMoneyException(String message) {
        super(message);
    }
}

// PriceNegativeException 처리: 상품 가격이 음수일 때의 예외처리
public class PriceNegativeException extends NegativeException {
    public PriceNegativeException(String message) {
        super(message);
    }
}

 

(2) ExceptionTest

public class ExceptionTest {

    /* throws 구문 작성 시 여러 Exception을 나열할 수 있으며, 상위 타입의 Exception만 작성할 수도 있다. */
    public void checkEnoughMoney(int price, int money)
            throws PriceNegativeException, MoneyNegativeException, NotEnoughMoneyException {

        if(price < 0) {
            throw new PriceNegativeException("상품 가격은 음수일 수 없습니다.");
        }

        if(money < 0) {
            throw new MoneyNegativeException("가지고 있는 돈은 음수일 수 없습니다.");
        }

        if(money < price) {
            throw new NotEnoughMoneyException("가진 돈보다 상품 가격이 더 비쌉니다.");
        }

        System.out.println("즐거운 쇼핑하세요~");
    }
}

 

(3) Application

public class Application {
    public static void main(String[] args) {
        ExceptionTest exceptionTest = new ExceptionTest();

        try {
            /* 상품 가격보다 가진 돈이 적은 경우 
            exceptionTest.checkEnoughMoney(50000, 30000);
            */

            /* 상품 가격이 음수인 경우 
            exceptionTest.checkEnoughMoney(-50000, 30000);
            */
 
            /* 가진 돈이 음수인 경우 
            exceptionTest.checkEnoughMoney(50000, -30000);
            */

            /* 정상적으로 구매 가능한 돈을 가진 경우 */
            exceptionTest.checkEnoughMoney(30000, 50000);

            /* 예외 상황별로 catch 블럭을 따로 작성해서 처리할 수도 있고,
            상위 타입의 Exception을 이용해서 통합적으로 처리할 수도 있다. */
        } catch (PriceNegativeException e) {
            System.out.println(e.getMessage());
        } catch (MoneyNegativeException e) {
            System.out.println(e.getMessage());
            
        /* 아래와 같이 |을 이용해서 동일한 레벨의 예외를 하나의 catch 블럭에서 처리할 수 있다.
        } catch (PriceNegativeException | MoneyNegativeException e) {
            System.out.println(e.getMessage());
        }
        아래와 같은 경우는 동일한 레벨의 예외가 아니므로 하나의 catch 블럭에서 처리할 수 없다.
        */
            
        } catch (NotEnoughMoneyException e) {
            System.out.println(e.getMessage());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("finally 블럭의 내용이 동작함");
        }

        System.out.println("프로그램을 종료합니다.");
    }
}

 

 

 

<출처>

https://wikidocs.net/229

 

07-04 예외 처리

프로그램을 만들다 보면 수없이 많은 예외 상황이 발생한다. 물론 예외가 발생하는 것은 프로그램이 오동작을 하지 않게 하기 위한 자바의 배려이다. 하지만 이러한 예외 상황을 무시하…

wikidocs.net

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-%EB%AC%B8%EB%B2%95-%EC%9D%91%EC%9A%A9-%EC%A0%95%EB%A6%AC

 

☕ 자바 예외 처리(try catch) 문법 & 응용 정리

예외(Exception) 처리하기 예외 처리(exception handling) 이란, 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는것이다. 자바의 코드를 예외 처리를 한다고 해서 프

inpa.tistory.com