이것이 자바다 9일차 - Chapter11 예외 처리

Seo-Faper·2023년 1월 19일
0

이것이 자바다

목록 보기
11/20
post-custom-banner

예외와 예외 클래스

컴퓨터 하드웨어의 고장으로 인해 응용프로그램 실행 오류가 발생하는 것을 자바에서는 에러 라고 한다. 프로그램을 아무리 견고하게 만들어도 개발자는 이런 에러를 대처할 방법이 없다.

자바에는 에러 이외에 예외(Exception)이라고 불리우는 오류가 있다. 예외란 잘못된 사용 또는 코딩으로 인한 오류를 말한다. 예외가 발생하면 프로그램이 곧바로 종료된다는 점에서 에러와 동일하지만 예외 처리를 통해 계속 실행 상태를 유지할 수 있다.

  • 일반 예외 : 컴파일러가 예외 처리 코드 여부를 검사하는 예외(체크 예외)를 말한다.
  • 실행 예외 : 컴파일러가 예외 처리 코드 여부를 검사하지 않는 예외(언체크 예외)를 말한다.

자바는 예외가 발생하면 예외 클래스로부터 객체를 생성한다. 이 객체는 예외 처리 시 사용된다.
자바의 모든 에러와 예외 클래스는 Throwable을 상속받아 만들어지고, 추가적으로 예외 클래스는 java.lang.Exception 클래스를 상속받는다.

실행 예외는 RuntimeException과 그 자식 클래스에 해당한다. 그 밖의 예외 클래스는 모두 일반 예외이다. 자바는 자주 사용되는 예외 클래스를 표준 라이브러리로 제공한다.

즉 정리하자면 체크 예외는 컴파일 하기 전에 반드시 throws든 try-catch든 예외처리를 해줘야 실행이 가능한 예외들이고 그 외에 RuntimeException 하위 예외들은 언체크 예외로, 필요하면 그 때 throws나 try-catch로 잡아주면 된다.

예외 처리 코드

package ch11.sec02.exam01;

public class ExceptionHandlingExample {
    public static void printLength(String data){
        int result = data.length(); //data가 null일 경우 예외 발생
        System.out.println("문자 수 : "+result);
    }
    public static void main(String[] args) {
   		System.out.println("[프로그램 시작]");
        printLength("ThisIsJava");
        printLength(null);
        System.out.println("[프로그램 종료]");
    }
}

이런 경우에 null이 들어왔을 때 프로그램은 실행 예외인 NullPointerException을 발생시키고 예외가 발생한 시점에서 프로그램이 종료된다. 그래서 아래의 [프로그램 종료]는 출력되지 않는다.
이 때 예외가 발생 해도 종료되지 않게 하려면 try - catch 문을 활용하면 된다.

package ch11.sec02.exam02;

public class ExceptionHandlingExample2 {
    public static void printLength(String data){
        try{
            int result = data.length();
            System.out.println("문자 수 : "+result);
        }catch (NullPointerException e){
            System.out.println(e.getMessage()); // 예외 출력 방식 1
            //System.out.println(e.toString()); 예외 출력 방식 2
            //e.printStackTrace();				예외 출력 방식 3
        } finally {
            System.out.println("마무리 실행");
        }

    }
    public static void main(String[] args) {
    	System.out.println("[프로그램 시작]");
        printLength("ThisIsJava");
        printLength(null);
        System.out.println("[프로그램 종료]");
    }
}

try 블록에서 NullPointerException이 발생하면 catch 블럭을 실행해 예외를 처리하고,
finally블럭을 통해 예외와 관계 없이 코드를 실행해 마무리 작업을 할 수 있다.

e.toString(), e.getMessage(), e.printStackTrace() 의 차이점은 다음과 같다.

  1. e.toString()은 에러의 Exception 내용과 원인을 출력.
  2. e.getMessage()는 에러의 원인을 간단하게 출력.
  3. e.printStackTrace()는 에러의 발생 근원지를 찾아 단계별로 에러를 출력.

finally 블록은 예외에 관계 없이 실행되는 부분인데, 여기에 return을 넣게 되면
try-catch는 return을 만났을 때 finally 블록을 거쳐 정상 종료 되는 반면 finally 블록은 try안에 발생한 Exception이 무시되고 finally 블록으로 제어가 전달되어 결국 정상종료가 되지 않을 가능성이 있다.

예외 종류에 따른 처리

try 블럭에는 다양한 종류의 예외가 발생할 수 있다. 이 경우 다중 catch를 이용하면 예외에 따라 예외 처리 코드를 다르게 작성할 수 있다.

package ch11.sec03.exam01;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        String[] array = {"100","1oo"};

        for(int i = 0; i<array.length; i++){
            try{
                int value = Integer.parseInt(array[i]);
            }catch (ArrayIndexOutOfBoundsException e){
                e.printStackTrace();
                System.out.println("배열 길이 초과됨");
            }catch (NumberFormatException e){
                e.printStackTrace();
                System.out.println("숫자로 변환할 수 없음");
            }
        }
    }
}

리소스 자동 닫기

리소스란 데이터를 제공하는 객체를 말한다. 리소스는 사용하기 위해 열어야(open) 하며, 사용이 끝난 다음에는 닫아야(close)한다. 예를 들어 파일 내용을 읽기 위해서는 파일을 열어야 하며, 다 읽고 난 후에는 파일을 닫아야 다른 프로그램에서 사용할 수 있다.

불안정한 리소스 사용을 막기 위해 try-catch-finally 문을 사용할 수 있다.

package ch11.sec04;

public class MyResource implements AutoCloseable{

    private String name;

    public MyResource(String name){
        this.name = name;
        System.out.println("[MyResource("+name+")열기");
    }
    public String read1(){
        System.out.println("[MyResource("+name+")읽기");
        return "100";
    }
    public String read2(){
        System.out.println("[MyResource("+name+")열기");
        return "abc";
    }

    @Override
    public void close() throws Exception {
        System.out.println("[MyResource("+name+")닫기");
    }
}

AutoCloseable의 구현체를 만들어서 close()를 오버라이딩 하면 되는데,
이후에 finally 블록에서 close()를 호출해 안전하게 닫는 방법이 있다.

FileInputStream fis = null;
try{
	fis = new FileInputStream("file.txt");
    ...
}catch(IOException e){
	...
}finally{
	fis.close();
}

좀 더 편한 방법으로 try-with-resources 문이 있다.
try 안에 리소스를 여는 코드를 작성하면 try 블록이 정상 종료 되거나 예외가 발생하면 자동으로 리소스의 close() 메소드가 호출된다.

try(FileInputStream fis = new FileInputStream("file.txt")){
	...
} catch(IOException e){
	...
}

예외 떠넘기기

try-catch 블록 이외에도 throws 를 이용한 예외처리가 가능하다.

리턴타입 메소드명(매개변수, ...) throws 예외클래스1, 예외클래스2, ... {

}

throws 키워드가 붙어있는 메소드에서 해당 예외를 처리하지 않고 떠넘겼기 때문에 이 메소드를 호출하는 곳에서 예외를 받아 처리해야 한다.

package ch11.sec05;

public class ThrowsExample1 {
    public static void main(String[] args) {
        try {
            findClass();
        } catch(ClassNotFoundException e){
            System.out.println("예외처리: "+e.getMessage());
        }
    }
    public static void findClass() throws ClassNotFoundException{
        Class.forName("java.lang.String2");
    }
}

나열해야 할 예외 클래스가 많으면 throws Exception 또는 throws Throwable만으로 모든 예외를 떠넘길 수 있다.
main()에서도 throws를 쓸 수 있는데, 이 때는 JVM에서 예외처리를 진행한다.

package ch11.sec05;

public class ThrowsExample2 {
    public static void main(String[] args) throws Exception {
        findClass();
    }
    public static void findClass() throws ClassNotFoundException{
        Class.forName("java.lang.String2");
    }
}

사용자 정의 예외

상황에 따라 개발자가 사용자 정의 예외를 만들어야 할 때도 있다.
통상적으로 일반 예외는 Exception의 자식 클래스로 선언하고, 실행 예외는 RuntimeException의 자식 클래스로 선언한다.

package ch11.sec06;

public class InsufficientException extends Exception{
    public InsufficientException(){

    }
    public InsufficientException(String message){
        super(message);
    }
}

이렇게 Exception을 상속받은 후에

package ch11.sec06;

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 void withdraw(int money) throws InsufficientException{
        if(balance < money){
            throw new InsufficientException("잔고 부족: "+(money-balance)+" 모자람");
        }
        balance -= money;
    }

이렇게 사용자 정의 예외를 만들 때 catch를 하지 않고 throw new InsufficientException로 넘겼기 때문에 예외를 메소드 선언부에 throws InsufficientException 를 선언해 줘야 한다.

체크 예외는 이렇게 모든 예외를 throws하거나 catch 해줘야 하기 때문에 의존관계에 문제가 생길 수 있다. 어차피 체크 예외들은 거의 대부분 복구 불가능한 예외들이 많은데 처리하지 못할 예외에 throws가 계속 붙으면 강한 의존상태가 되어 코드 수정이 어려워 진다.

그래서 throws Exception 이렇게 극단적으로 쓸 수도 있는데 이렇게 하기 보다는 그냥 런타임 예외를 구체적으로 잘 짜는게 더 좋다.

package ch11.sec06;

public class AccountExample {
    public static void main(String[] args) {
        Account account = new Account();

        account.deposit(10000);

        System.out.println("예금액: "+account.getBalance());

        try{
            account.withdraw(30000);
        }catch (InsufficientException e){
            String message = e.getMessage();
            System.out.println(message);
        }
    }
}

연습문제

4번 사설 예외도 처리할 수 있죠.

3번, finally는 예외 발생 여부와 상관없이 무조건 실행된다. try안에서 return을 해도 finally는 실행된다.

4번, throws는 말 그대로 예외를 전달하는데 목적이 있습니다.

2번, 생성자에는 불가능하고 메소드만 됩니다.

3번, method1()에서 throws 하는 저 2개의 예외에 대한 catch를 해줘야 합니다.

10
숫자로 변환할 수 없음
10
인덱스를 초과했음.
10



1번 괄호 super(message); 
2번 괄호 super(message); 
3번 괄호 throws NotExistIdException
4번 괄호 throws new NotExistIdException("아이디가 존재하지 않습니다.");
5번 괄호 throws new WrongPasswordException("패스워드가 틀립니다.");

try (FileWriter fw = new FileWriter("file.txt") ){
	fw.write("java");
} catch (IOException e){
	e.printStackTrace();
}
profile
gotta go fast
post-custom-banner

0개의 댓글