jacketList

[Java] 예외처리 본문

Java

[Java] 예외처리

ukkkk7 2024. 5. 7. 17:46
728x90
반응형

학습할 것 (필수)

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

예외(Exception)와 에러(Error)의 차이

Error

  • 오류(Error)는 시스템의 비정상적인 상황이 생겼을 때 발생 
    • 시스템 레벨에서 발생하기 때문에 심각한 수준의 오류이다.
  • 런타임에 발생하여 컴파일 단계에서 체크하지 못함
  • java.lang.Error 패키지에 정의되어 있음
  • OutOfMemory, StackOverflow 가 대표적인 예

 

Exception

  • 확인되는 유형(Checked)과 확인되지 않는 유형(Unchecked)이 포함되어 있다.
    • 확인되는 유형(Checked)은 컴파일 시점에 체크가능 
    • 확인되지 않는 유형(Unchecked)는 런타임 시점에 체크가능
  • 프로그램 자체가 예외를 발생시킴
  • 복구가 가능
  • java.lang.Exception 패키지에 정의

 

예외 처리 방법

try - catch 

 

예외 처리를 위해서는 try-catch 구문을 이용하며 아래와 같은 구조를 갖는다.

try {
    /// 예외가 발생할 가능성이 있는 코드/
} catch (Exception1 e1) {
    /// Exception1이 발생했을 때, 이를 처리하기 위한 코드/
} catch (Exception2 e2) {
    /// Exception2가 발생했을 때, 이를 처리하기 위한 코드/
} catch (ExceptionN eN) {
    /// ExceptionN이 발생했을 때, 이를 처리하기 위한 코드/
}

 

try 블럭은 여러개의 catch블록이 올 수 있고 try를 실행하며 발생한 예외에 맞는 catch 블록만 실행한다.

public class ExceptionDemo {

    public static void main(String[] args) {
        try {
            /// 1을 0으로 나눴으므로 예외가 발생한다./
            System.out.println(1 / 0);

        } catch (IllegalArgumentException e) {
            System.out.println(e.getClass().getName());
            System.out.println(e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println(e.getClass().getName());
            System.out.println(e.getMessage());
        } catch (NullPointerException e) {
            System.out.println(e.getClass().getName());
            System.out.println(e.getMessage());
        }
    }
}

 

Task :ExceptionDemo.main()
java.lang.ArithmeticException
/ by zero

BUILD SUCCESSFUL in 243ms
2 actionable tasks: 2 executed

 

try블록에서 0으로 1을 나눠 이에 해당되는 예외인 ArithemeticException이 발생하며 다른 catch문은 실행되지 않는다.

여러개의 catch 문을 구성할 때 주의할 점은 catch블럭 작성 시 부모클래스가 자식클래스보다 먼저 catch에서 쓰이면 에러가 발생한다.

 

 

  • printStackTrace()  
    • 예외 발생 당시 호출스택에 있었던 메소드 정보와 예외 메시지를 하면에 출력 -> 많은 시스템 자원을 소모하기 때문에 필요한 경우가 아니라면 사용을 자제하는 것이 좋다.
  • getMessage()  
    • 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
  • toString()
    • 예외메시지를 String형태로 제공받는다.
    • 예외 클래스 이름도 같이 제공한다.

 

Multicatch block

JDK 1.7부터 여러 catch block을 하나로 합칠 수 있게 되었다.

public class ExceptionDemo {

    public static void main(String[] args) {
        try {
            System.out.println(1 / 0);
        } catch (RuntimeException | ArithmeticException e) {
                 /// 에러 발생: ArithmeticException은 RuntimeException을 상속받는 클래스이다./
            System.out.println(e.getMessage());
        }
    }
}

 

위와같이 코드를 하나의 catch블록 안에 ' | ' 를 통해 여러개의 catch 블럭을 묶어서 처리할 수 있다.

하지만 처리하려는 예외가 부모 - 자식 관계라면 에러가 발생한다.

ArithmeticException => RuntimeException 상속   = 에러 발생

 

 

finally

필수 블록은 아니다.

finally 블록을 사용하면 블록안에 작성된 내용은 예외 발생 유무에 상관없이 무조건 수행된다.

-> 만일 try 블럭에서 예외가 발생하면 바로 catch블럭으로 넘어가기 때문에 꼭 필요한 코드가 수행되지 않을 수 있다. 이 경우 finally를 통해 해당 코드를 수행하게 한다.

try {
    // 예외가 발생할 가능성이 있는 문장을 넣는다.
} catch {
    // 예외 처리를 위한 문장을 넣는다.
} finally {
    // 예외 발생 여부와 상관없이 항상 실행되어야 할 문장을 넣는다.
}

 

 

throw

throw키워드를 사용하면 프로그래머가 고의적으로 아래와 같이 예외를 발생시킬 수 있다.

public class Person {

    private String userName;
    
    public String getUserName() {
        return userName;
    }
    
    public void setUserName(String userName) {
        this.userName = userName;
    }
    
    public void throwExam() {
        Person person = jpaRepository.findById("userName");
    
        if (person == null) {
            throw new NullPointerException(); // 조회한 person 객체가 비어있을 때 예외 발생
        } else {
            System.out.println(person.getName());
        } 
    }
}

 

 

throws

예외처리를 다른곳으로 위임하는 역할을 한다.

public static void method1(){
        try{
            method2();
        }catch (ClassNotFoundException e){ // method2()의 ClassNotFoundException 시행
            e.printStackTrace();
        }finally {
            System.out.println("finish");
        }
    }

    private static void method2() throws ClassNotFoundException { // 바로 method1 catch문으로 ㄱ
        Class clazz = Class.forName("java.lang.절대없는class");
    }

    public static void main(String[] args) {
        method1();
    }

 

위와같은 코드에서 method2()는 예외처리를 throws를 통해 본인을 호출한 곳으로 위임한다. 해당 예외는 method1에서 try-catch 문을 통해 처리하게 된다.

throws를 통해 예외처리를 위임하는 것은 다른 팀원이 메소드 사용시 상황에 맞게 예외처리를 할 수 있도록 도와줄 수 있지만 계속해서 throws를 통해 위임하게 되면 main메소드 또한 예외를 throws하여 JVM이 대신 처리하게 되는 상황이 발생할 수 있고 이는 예외처리를 하지 않은 것과 다름없기 때문에 상황에 맞게 쓰는것이 중요하다.

 

 

 

 

자바가 제공하는 예외 계층 구조

 

Exception과 Error는 Throwable이라는 클래스를 상속받고 있으며 Throwable은 Object를 직접 상속받고 있다.

 

 

RuntimeException과 RE가 아닌 것의 차이점?

위 그림에서 Unchecked Exception과 Checked Exception이 나눠져 있는 것을 볼 수 있는데 Runtime시에 예외가 발생하는 것이 Unchecked Exception이고 그 이전에 예외가 발생하는 것이 Checked Exception이다.

 

RuntimeException

  • RuntimeException 클래스를 상속받는 자식 클래스들은 주로 치명적인 예외 상황을 발생시키지 않는 예외들로 구성되어 있다.
  • try-catch문을 사용해서 해당 예외를 다 처리하기 보다는 예외가 발생하지 않도록 프로그래밍 하는것이 좋다.

CheckedException

  • CheckedException 클래스인 Exception 클래스에 속하는 자식 클래스들은 치명적인 예외 상황을 발생시키기 때문에 사전에 try-catch문을 사용하여 예외처리를 해야한다.
  • 컴파일러는 RuntimeException클래스 이외의 Exception 클래스의 자식 클래스에 속하는 예외가 발생할 가능성이 있는 구문에는 반드시 예외를 처리하도록 강제하고 있다.
  • 컴파일 단계에서 확인 가능

 

커스텀한 예외를 만드는 방법

기존 정의된 예외 클래스 외에 필요에 따라 새로운 예외를 정의할 수 있다. Exception클래스를 상속받거나, 필요에 따라 알맞은 예외 클래스를 상속받아 만들 수 있다.

public class ExceptionDemo {

    public static void main(String[] args) throws SpaceException {
        methodA(5);
    }

    static void methodA(int space) throws SpaceException {
        if (space < 1) {
            throw new SpaceException(“공간 부족”);
        }
    }
}

class SpaceException extends Exception {
    public SpaceException(String message) {
        super(message);    /// 조상 클래스인 Exception의 생성자 호출/
    }
}

 

 

 


강의 시청 후

try- with-resource 

resource란 외부의 데이터(DB, Network, File)를 일컫는다. 이러한 resource는 내부에 위치한 요소들이 아니기 때문에 프로세스 외부에 있는 데이터에 자바 코드로 접근하려고 할 때 문제(예외)가 발생할 수 있는 여지가 있다.

때문에 resource에 접근해 사용하고 나면 마지막에 닫아주는 것이 굉장히 중요하다 -> 어떤 resource를 사용하다가 다른곳에서 같은 resource에 접근해 사용하다 보면 꼬일 수 있기 때문

 

자바 7 이전의 try-catch-finally

사용 후에 반납해야 할 자원들은 closable인터페이스를 구현하고 있으며, 사용 후에 close 메소드를 호출해주어야 했다.

자바7 이전에는 close를 호출하기 위해 try-catch-finally를 이용해서 Null검사와 함께 직접 호출해야 했음

import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        FileWriter file = null;
        try {
            file = new FileWriter("data.txt");
            file.write("Hello World");
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
        	// close()에서 발생하는 예외를 처리하기 위해서 아래와같이 바꿀수도 있지만 코드가 복잡해져서 좋지않다.
            try {
                file.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 

이러한 과정은 여러가지 단점을 가지고 있다.

  • 자원 반납에 의한 코드가 복잡해짐
  • 작업이 번거로움
  • 실수로 자원을 반납하지 못하는 경우 발생
  • 에러로 자원을 반납하지 못하는 경우 발생
  • 에러 스택 트레이스가 누락되어 디버깅이 어려움

 

try-with-resources

Java는 이러한 문제점을 해결하고자 Java7부터 자원을 자동으로 반납해주는 try-with-resources 문법을 추가했다.

try-with-resources문은 주로 입출력(I/O)와 관련된 클래스를 사용할 때 굉장히 유용하다.

try (파일을 열거나 자원을 할당하는 명령문) {
     ...
}

위와같이 try 괄호 안에 파일을 열거나 자원을 할당하는 명령문을 명시하면, 해당 try 블록이 끝나자마자 자동으로 파일을 닫거나 할당된 자원을 해제해 준다.

 

Java는 AutoClosable 인터페이스를 구현하고 있는 자원에 대해 try-with-resources를 적용 할 수 있도록 하여, 코드가 유연해지고 누락되는 에러없이 모든 에러를 잡을 수 있게 되었다.

 

위에 try-catch-finally 코드를 try-with-resources 코드로 리팩토링 하면 아래와 나타낼 수 있다.

import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (FileWriter file = new FileWriter("data.txt")) { // 파일을 열고 모두 사용되면 자동으로 닫아준다
            file.write("Hello World");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

또한 괄호 안에 IO객체 문장을 두개 이상 넣어줄수도 있다. 이때는 세미콜론으로 각 문장을 구분해 주어야 한다.

// try 괄호 안에 두문장 이상 넣을 경우 ';'로 구분한다.
try(
	FileInputStream fis = new FileInputStream("a.txt");
    DataInputStream dis = new DataInputStream(fis)
) {
	
    while(true){
    	score - dis.readInt();
        System.out.println(score);
        sum += score;
    }
    
} catch (EOFException e){
    System.out.println("점수의 총합은 " + sum + "입니다.");
} catch (IOException ie){
    ie.printStackTrace();
}

 

 

 

references

https://wisdom-and-record.tistory.com/46

https://sujl95.tistory.com/62

https://mangkyu.tistory.com/217

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-Try-With-Resource-%EB%AC%B8%EB%B2%95

728x90
반응형