고코딩

[JAVA] throw, throws와 Exception Handle(예외처리) 개념 및 설명 본문

JAVA

[JAVA] throw, throws와 Exception Handle(예외처리) 개념 및 설명

고코딩 2020. 12. 30. 11:27

JAVA를 어느정도 공부하다보면 예외처리 오류를 해줘야 한다는 구문을 많이 만날 수 있었다. 입력할때마다 예외가 발생해 항상 try~catch구문으로 예외처리를 해주었는데 사실 정확한 의미와 구성 방식을 몰랐다. 심지어 throws와 throw는 누구도 알려준적이 없어서 처음으로 자바프레임워크를 사용했을 때 처음 보는 수식어 때문에 많이 당황했었다. 이 포스팅으로 예외처리에 대한 간단한 이해를 해보자.

try~catch와 throws의 차이

try~catch의 의미를 검색해보면 발생한 예외를 처리한다. 라고 되어있고, throws는 예외처리가 발생한 메소드를 호출한 메소드에게 예외를 처리한다, 라고 되어있다. 처음 이 글을 읽었을때는 이게 무슨소리인가 싶었다. 하지만 잘 곱씹어 읽어보면 처리하는 순간이 다른것을 알 수 있었다. 그리고 보통 처리한다라는 것을 try~catch문을 사용한다는 뜻으로 해석할 수 있다. 그럼 throws는 예외가 발생하는 메소드를 호출한 메소드안에서 try~catch문을 작성해준다 라고 생각할 수 있다. 그럼 실제로 코드로 작성해 보자. 근데 예외처리 테스트를 해보려면 예외를 강제로 발생시켜야 한다. 그럼 예외는 어떻게 발생시킬 수 있을까? throw가 예외를 강제로 발생시켜주는 선언문이다.

import java.io.IOException;

public class Main {
    public static void main(String[] args){
        try {
            throw new IOException();
        }catch(IOException e) {
            e.printStackTrace();
        }
        System.out.println("예외 처리 완료");
    }
}

main()함수안에 throw new IOException();을 선언했다. 참고로 예외처리의 계층적인 구조를 보면 Exception Interface를 가져다 쓰기 때문에 new로 예외처리 오브젝트를 생성해 예외를 강제로 발생시켰다.

현재는 try~catch구문으로 예외 처리를 했다. try{}안에서 예외가 발생하면 catch(){}에 있는 구문이 실행되게 된다. e.printStackTrace();는 예외상황을 console에 보여준다.

java.io.IOException
    at Main.main(Main.java:6)
예외 처리 완료

e.printStackTrace()의 출력값이다. 읽어보면 java.io.IOException이라는 클래스의 예외가 발생했고 발생 위치는 Main.main()함수라는 것을 알려준다. 그리고 예외 처리 완료 라는 구문을 출력해줌으로써 try

catch가 끝나면 그 이후의 코드를 실행한다는 것을 알 수 있었다. 정리하자면 main()함수 안에서 예외가 발생해 main()이 try

catch를 써서 스스로 예외처리를 하였다고 해석할 수 있다. 그럼 throws를 사용해보자

import java.io.IOException;

public class Main {
    public static void main(String[] args){
        TException te = new TException();
        te.read();
        System.out.println("Running main method...");
    }
}

class TException{
    public void read()  {
        try {
            throw new IOException();
        }catch(IOException e) {
            e.printStackTrace();
        }
        System.out.println("Running read method...");
    }
}

TException.classread()가 예외를 발생하게 해주었다. 예외는 read()안에서 처리하게 해주기 위해 try~catch()를 사용해 주었다. 이 후 실행결과는

java.io.IOException
    at TException.read(Main.java:14)
    at Main.main(Main.java:6)
Running read method...
Running main method...

결과를 보면 예외가 처리된후 이후 println() 함수들이 순차적으로 수행된것을 볼수 있다.

try~catch 구문은 예외를 처리한 후 이후의 코드를 실행한다는 것을 알수 있었다.


그럼 throws로 예외처리를 해보자. throws는 기본적으로 예외가 발생한 함수를 호출한 함수에서 예외처리를 하기때문에 Main.main()이 예외가 발생하는 함수를 호출하도록 만들었다.

import java.io.IOException;

public class Main {
    public static void main(String[] args){
        TException te = new TException();
        te.read(); //에러
        System.out.println("Running main method...");
    }
}

class TException{
    public void read() throws IOException  {

        throw new IOException();

        System.out.println("Running read method..."); //Error Unreachable code
    }
}

위와 같이 코드를 작성해주니 총 2가지의 에러가 발생했다

  • te.read() : 예외 처리를 해주라는 구문이 나왔다.
  • System.out.println("Running read method..."); : 코드를 지우라는 구문이 나왔다. 아무래도 throws를 사용해서 예외를 넘기면 그 이후의 코드는 실행되지 않는것 같다

System.out.println("Running read method...");를 지우고 te.read()부분을 try~catch로 처리해준 후 실행해 보았다.

import java.io.IOException;

public class Main {
    public static void main(String[] args){
        TException te = new TException();
        try {
            te.read();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } //에러
        System.out.println("Running main method...");
    }
}

class TException{
    public void read() throws IOException  {    
        throw new IOException();

    }
}
java.io.IOException
    at TException.read(Main.java:18)
    at Main.main(Main.java:7)
Running main method...

예외가 실제로 발생하는 부분은 TException클래스의 read()부분이지만 throws 구문을 통해서 read()를 호출한 main()에서 예외를 처리해 주었다. main()은 Main클래스에서 발생한 예외가 아니지만 예외처리를 해주는 모습을 볼수 있었다. 그럼 Main.main()도 예외를 throws 해주면 어떻게 될까? 아까 throws를 사용하니 System.out.println("Running read method...");구문을 사용하지 못하였다.

코드를 수정해서 테스트를 해보았다.

import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException{
        TException te = new TException();
        te.read();
        System.out.println("Running main method...");
    }
}

class TException{
    public void read() throws IOException  {    
        throw new IOException();

    }
}
Exception in thread "main" java.io.IOException
    at TException.read(Main.java:13)
    at Main.main(Main.java:6)

신기하게도 throws를 사용했지만 System.out.println("Running main method...");에는 아무런 에러가 뜨지 않았고 System.out.println("Running main method...");은 실행되지 않는 것을 볼수 있었다. 예외 발생시 throws로 예외처리를 던지게 되면 그 다음 구문은 절대로 실행되지 않는것 같다. Main클래스에서 System.out.println("Running main method...");에 에러가 발생하지 않은 이유는 Main입장에서는 te.read();에서 예외가 발생할지 말지 모르기때문에 그 이후 코드가 실행될 가능성이 있어 오류를 발생시키지 않는것 같다.


정리

throws는 예외를 넘기고 try~catch는 예외를 처리한다는 것을 알수 있었다. 또 throws는 예외 발생 이후의 코드는 실행하지 않고, try~catch는 예외 발생 이후의 코드를 실행한다는 점도 알았다. 둘다 예외처리를 하는것은 똑같은데 굳이 2가지 방법으로 예외를 처리하게 한 이유는 무엇일까? 그 이유는 개발자가 원하는 예외처리방법을 만들 수 있기 위함이다. 만약에 내가 라이브러리를 만들면서 A함수의 예외처리를 try~catch로 정의해버리면 라이브러리를 사용하는 다른 개발자는 A함수에서 예외가 발생해 버렸을 때 내가 만든 예외처리를 만날 수 밖에 없다. 개발자가 다른 예외처리를 만들고 싶어도 만들수가 없게 되어진다. 실제로 이러한 점 때문에 대부분의 라이브러리에서의 예외가 발생할수 있는 함수는 특별한 경우를 빼고 throws로 사용자가 예외처리를 하게 만들어 놓았다. 사용자는 예외를 만나면 try~catch로 예외를 처리해줄 수 있게 만들면 된다.

논문을 읽고 코드로 실습을 해보니 생각보다 throws와 throws가 어려운 개념이 아니란 것을 알 수 있었다. 만약 이 글을 읽으면서도 이해가 되지 않는다면 스스로 예외를 발생시켜 예외를 처리해 보길 바란다. 바로 이해될 수 있을 것이다. throws가 이해된다면 다른 개발자가 짠 코드를 해석하는 능력도 올라갈 것이다.

참고자료: