본문 바로가기
Java/Java 기초문법

[JAVA 기초] 자바 예외처리 2 - 예외의 종류(Checked, Unchecked)와 예외 발생(throws, throw)

by dev수니 2021. 4. 11.
반응형

 

 

 

 1  예외의 종류

 

 

예외에는 두가지 종류가 있다.

 

  • "Checked Exception" : 컴파일 단계에서 '체크'되는 예외이다.
    --> 반드시 처리해줘야 컴파일이 된다.
    --> 안 그러면 빨간줄.
    --> 대표적으로 "IOException" : Input,Output 의 자식들 대표 "FileNotFound"
  • "Unchecked Exception" : 컴파일 단계에서 '체크'되지 않는 예외이다.
    --> 우리가 보게 될 대부분의 예외가 여기에 해당된다.
    --> "Runtime Exception" 상속받는 자식들 : "Arithmetic, ArrayIndexOutOfBounds 등등 .."

 

아래사진의 Exception에서 Runtime ExceptionUnchecked Exception이고

그 이외의 것들이 Checked Exception이다. 대표적인 게 IOException이다.

 

 

 

출처 : https://javakickoff.blogspot.com/2018/09/checked-vs-unchecked-exception-in-java.html

 

 

우리가 전 포스팅에서 살펴봤던 예외들이 바로 RutimeException 이다. 해당 예외들은 Unchecked Exception 으로 컴파일러가 실행전에 미리 체크해주지 않았다.

 

 

 

 

그리고 자주 발생되는 예외는 다음과 같다. 다음 Exception 은 기억해두면 좋을 것이다.

 

  • IllegalArgymentException : 매개변수가 의도치 않은 상황을 유발시킬때
    - 인자로 전달된 값이 메소드에서는 적합하지 않을 때 발생한다.
  • IllegalStateException : 메소드를 호출하기 위한 상태가 아닐때
    메소드(또는 객체의 생성자)를 호출했을 때, 그것이 현재 동작할 수 있는 상황을 충족시키지 못하였을 때 발생한다.
  • NullPointerException : 매개변수 값이 Null일때
  • IndexOutOfBoundsException : 인덱스 매개변수 값이 범위를 벗어날 때
  • ArithmeticException : 산술적인 연산에 오류가 있을 때 

 

 

 

 

 

 

 

 

 2  IOException 

 

 

이번에 알아볼 것은 Checked Exception 의 대표인 IOException 이다.

다음 예제를 보자.

 

 

[입출력함수]

Filewriter 를 사용한 예.

1
2
3
4
5
6
7
8
9
package exception;
 
import java.io.FileWriter;
 
public class Exception_FileWriter {
 
    public static void main(String[] args) {
        FileWriter f = new FileWriter("data.txt")){
        f.write("Hello");
cs

 

위와 같이 작성하면 컴파일러가 Unhadled exception type IOException 이라고해서 IOException 타입의 예외를 처리해야 한다고 할 것이다.

 

 

이는 FileWriterIOException을 발생시킬 수 있으며 IOException 은 Checked Exceptiont 이기 때문에 꼭 예외처리를 해줘야 함을 의미한다.

 

따라서 아래와 같이 try/catch 로 예외처리를 해주었다.

 

IOException 을 발생시키는 입출력 함수들은 checked Exceptiont 이기 때문에 반드시 예외처리를 해줘야한다. 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package exception;
 
import java.io.FileWriter;
 
public class Exception_FileWriter {
 
    public static void main(String[] args) {
        try(FileWriter f = new FileWriter("data.txt")){
            f.write("Hello");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
cs

 

 

그리고 추가적으로,

파일을 입출력할 때는 외부에 있는 데이터를 가져오는 것이기 때문에 자바는 파일을 작업할 동안 다른 프로그램에서 사용하지 못하도록 잡아주게된다. 이렇게 파일을 붙잡아두다가 작업이 모두 끝난 후 잡고 있던 것들을 놓아줘야 하는데 이때 써야 하는 것이 close()이다. 

 

 

 

하지만 위의 경우 close()를 써주지 않았다. 

close()를 써서 try catch를 작성하게 되면 코드가 굉장히 복잡해지기 때문이다. 이같은 이유때문에 만들어진 것이 바로 try with resource statements 이다. 위의 예제도 이를 사용한 것이다.

 

try(예외발생 가능성이 있는 구문){
} catch ( Exception e ){
   예외발생시 실행할 구문;
}

 

만약 try with resource statements 를 사용하지 않고 코드를 작성할 경우 다음과 같이 코드가 길어진다.

 

finally{
    if(f != null){ // 만약 f가 null이 아니라면 close();
        try{
            f.close();
        } catch(IOException e){
            e.printStackTrace();
        }
    }
}

 

 

 

 

 

 

다음은 입력버퍼로 파일을 읽는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package exception;
 
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
 
public class Exception_BufRe {
    public static void main(String[] args) {
        try {
            FileReader fr = new FileReader("C:\\AAA\\testFR.txt");
            BufferedReader br = new BufferedReader(fr);
            while(true) {
                String str = br.readLine();
                if(str == null) {
                    break;
                }
                System.out.println("파일을 읽어온 문장 : "+ str);
            }
        } catch(FileNotFoundException e){
            System.out.println("파일을 찾을 수 없습니다.");
        } catch(IOException e) {
            System.out.println("예외 발생.");
        }
    }
}
cs

 

위와같이 IOException 과 FileNotFoundException 을 import 해주고 FileReader 와 BufferedReader 도 import 해준 모습이다.

 

파일을 FileReader 로 읽어오고 BufferedReader 로 입력버퍼를 이용하여 파일의 문장을 읽어온다.

 

FileReaderBufferedReader 같은 경우도 IOException을 발생할 수 있는 입출력 함수이기 때문에 예외처리반드시 해줘야 한다. 따라서 위와 같이 try catch로 예외를 처리해주었다.

 

만약 파일이 존재하지 않을 경우 줄20의 예외처리가 실행되고 이외의 예외가 발생했을 경우는 줄22의 예외가 발생된다.

 

 

다음, 파일을 생성하지 않고 실행을 한 결과를 보면 파일을 찾지 못해 줄20의 구문이 실행되었다.

파일을 찾을 수 없습니다.

 

 

그리고 c드라이브의 AAA폴더에 testFR.txt 파일을 생성하고 다음과 같이 내용을 작성한다.

 

그리고 에디터로 돌아가 실행시켜주면 다음과 같이 파일내의 내용이 출력된다.

파일을 읽어온 문장 : 자바
파일을 읽어온 문장 : 예외처리
파일을 읽어온 문장 : FileReader 를 이용한
파일을 읽어온 문장 : 구문입니다.
파일을 읽어온 문장 : 연습파일
파일을 읽어온 문장 : 입니다.

 

 

 

 


 

 3  throws

 

 

우리가 자바로 프로그램을 만들면서 일부러 예외를 발생시켜야 할 때가 있다.

이때 사용하는 것이 바로 throws이다. 사용자를 향해 던진다는 의미로 해석하면 된다.

 

main메서드에서 throws 를 선언하면 main()메서드에서 발생하는 '예외'를 main 에서 처리하지 않고 이 메서드를 호출한 JVM에게 예외처리떠넘기는 것이다. 

 

 

다음 예제는 위의 예제를 throws 를 사용하여 수정해준 것이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package exception;
 
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
 
public class Trows {
    public static void main(String[] args) throws FileNotFoundException, IOException {
        BufferedReader br = null;
 
        br = new BufferedReader(
                    new FileReader("c:\\AAA\\testFR.txt")
                );
        for(int i=1; i<=4; i++) {
            String str = br.readLine();
            System.out.println("파일로부터 읽어온 문장 : " + str);
        }
        br.close();
    }
}
cs

 

 

따라서 예외가 발생했을 경우 프로그램의 실행을 중지한다. (printStackTrace)

Exception in thread "main" java.io.FileNotFoundException: c:\AAA\testF.txt (지정된 파일을 찾을 수 없습니다)
     at java.base/java.io.FileInputStream.open0(Native Method)
     at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
     at java.base/java.io.FileInputStream.(FileInputStream.java:153)
     at java.base/java.io.FileInputStream.(FileInputStream.java:108)
     at java.base/java.io.FileReader.(FileReader.java:60)
     at exception.Trows.main(Trows.java:12)

 

 

 

그리고 다음 코드를 보자.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
on;
 
import java.io.IOException;
 
class EE {
    void run() throws IOException, Exception{}
}
class GG {
    void run() throws Exception {
        EE ee = new EE();
        try {
            ee.run();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class Throws {
    public static void main(String[] args) {
        GG gg = new GG();
        try {
            gg.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
cs

 

 

클래스 EE의 run()메소드에 throws로 예외를 발생시키고 run() 메서드 던져 이 메서드의 사용자에게로 예외처리의 책임을 전가시켰다. 

그리고 GG클래스을 생성하고 run()메서드를 만들어 내부에 EE 인스턴스 ee 를 생성하고 ee.run()메서드를 호출했다. ee.run() 을 호출했더니 예외를 처리하라는 경고표시가 뜬다. 하지만 여기서 예외를 처리하지 않고 다시 throws 로 던져 사용자에게 예외처리를 넘겼다.

따라서 Throws 클래스 내에서 GG 인스턴스 gg 를 생성하고 gg.run()을 호출하여 거기서 try/catch 로 예외처리를 해준 모습이다.

 

 

이렇게 예외처리의 책임을 메서드의 사용자와 가깝게 또는 멀게 설정할 수 있다.

 

 

 

 

 


 

 

 4  throw

 

Exception 클래스를 생성하여 throw 시키는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package exception;
 
public class Throw {
    public static void main(String[] args){
        try {
            Exception e = new Exception("고의적 예외");    // 예외객체 생성
            throw e;                            // 예외 객체를 던짐.
//            throw new Exception("고의적 예외.");  // 예외객체 생성과 동시에 던짐.
        } catch(Exception e) {
            e.printStackTrace();  // '고의적 예외' 예외메시지를 확인
            System.out.println(e.getMessage());    // 예외객체의 값으로 담아준 "고의적 예외"출력
        }
    }
}
cs

 

Exception 은 클래스 형태이다. 따라서 Exception객체생성이 가능하다.

 

위와 같이 메인 메서드 안에서 try 구문으로 Exception 객체를 생성하고 throw 로 예외를 발생시켰다.

그리고 catch문에서 발생시킨 예외를 처리하였다.

이 구조를 자세하게 파악해보자.

 

먼저 줄6에서 Exception 객체의 값으로 "고의적 예외"를 넣어주고 새 객체를 생성했으며, 이 객체를 타입Exception변수 e에 담아주었다. 그리고 줄7에서 e 객체를 throw 로 사용자에게 던져 예외처리의 책임을 전가시켰다.

그리고 e 객체와 데이터 타입이 같은 줄9의 catch 문을 실행하게 되는 것이다.

 

그리고 줄10,11에 e 객체에 값으로 "고의적 예외"를 담아줬으므로 getMessage() 를 호출하면 "고의적 예외" 를 출력하게 되고, printStackTrace() 했을 때 다음과 같이 출력되는 것이다.

 

그리고 줄6,7부분을 줄8로 줄여서 사용할 수 있다.

 

java.lang.Exception: 고의적 예외
    at exception.Throw_Class.main(Throw_Class.java:6)
고의적 예외

 

 

 

 

 

그리고 다음은 한 클래스 안에 메서드A, B, C 를 선언하고 A안에서 B메서드를 호출 B안에서 C메서드를 호출, C 안에서 예외객체의 생성과 동시에 던지고 이를 main메서드에서 처리해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package exception;
 
public class Throw_Method {
    static void methodA(){
        methodB();
    }
    static void methodB(){
        methodC();
    }
    static void methodC(){
 
    }
    public static void main(String[] args){
 
        }
    }
}
cs

 

먼저 위와같이 한 클래스 안에 메서드A, B, C 를 선언하고 A안에서 B메서드를 호출, B안에서 C메서드를 호출하였다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package exception;
 
public class Throw_Method {
    static void methodA(){
        methodB();
    }
    static void methodB(){
        methodC();
    }
    static void methodC(){
        throw new Exception();
    }
    public static void main(String[] args){
 
        }
    }
}
cs

 

그리고 위와 같이 메서드 C안에 예외객체를 생성하고 throw를 통해 예외를 던져주었다.

 

저렇게 작성하게 되면 컴파일러가 Unhadled exception type IOException 이라고 경고를 표시한다. throw를 사용하여 객체를 생성해주었으니 메서드상에서 한번 더 던져줘야 하는 것이다. 따라서 줄10 에 throws Exception 해준다.

 

이렇게 처리해주면 메서드 CB가 사용하고 메서드 BA가 사용하기 때문에 해당 메서드들도 전부 throws Exception 해줘야 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package exception;
 
public class Throw_Method {
    static void methodA() throws Exception {
        methodB();
    }
    static void methodB() throws Exception {
        methodC();
    }
    static void methodC() throws Exception {
        Exception e = new Exception();
        throw e;
    }
    public static void main(String[] args) {
        
    }
}
cs

 

이렇게 해주면 더이상 컴파일러가 경고메세지를 띄우지 않는다. 하지만 아직 예외를 던지기만 했을 뿐 처리해주지 않았다. 우리는 메인에서 이 예외를 처리해주기로 했기 때문에 try catch로 다음과 같이 처리한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package exception;
 
public class Throw_Method {
    static void methodA() throws Exception {
        methodB();
    }
    static void methodB() throws Exception {
        methodC();
    }
    static void methodC() throws Exception {
        Exception e = new Exception();
        throw e;
    }
    public static void main(String[] args) {
            try {
                methodA();
            } catch (Exception e) {
                System.out.println("메인메서드에서 예외처리함.");
            }
    }
}
cs
메인메서드에서 예외처리함.

 

메서드 A호출하여 예외처리해주었다.

 

 

 

 

 

 

 

 4  try catch 문의 중첩

 

try 문 안에 try 문을 작성할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package exception;
 
//
public class TryCatch_2 {
    public static void main(String[] args) {
        try {
            System.out.println("외부 try영역");
            try {
                System.out.println("내부 try영역");
                Exception e = new Exception();
                throw e;
            }catch(Exception e) {
                System.out.println("내부:"+e);
                throw e;    // "예외를 다시 던짐." (여기서 예외가 다시 발생)
                            // "예외를 복구함." 이라고 표현할 수도 있음
            }finally {
                System.out.println("내부finally영역.");
            }
        }catch(Exception e) {
            System.out.println("외부"+e);
        }
        System.out.println("프로그램 종료.");
    }
}
cs

 

외부 try영역
내부 try영역
내부:java.lang.Exception
내부finally영역.
외부java.lang.Exception
프로그램 종료.

 

줄6에서 try구문이 실행된다. 그러면 줄7부분이 출력되고 줄8의 try구문으로 들어간다.

 

try구문으로 들어가서 줄9의 "내부try영역이 출력되고 Exception 객체를 생성한다. 그리고 생성된 객체는 throw로 인해 던져지고 던져진 e를 줄12의 내부 catch문이 받게된다.

 

따라서 줄13이 실행되고나서 다시 e가 throw로 던져진다.

일단 내부 finally 구문을 실행해야 하기 때문에 던져진 e 객체는 main() 에 일단 저장되고 funally 구문 실행 후 외부로 빠져나온다.

 

외부로 빠져나오고 나서 main 에 저장된 e 와 데이터타입과 동일한 줄19의 catch문으로 들어가 줄20을 실행하게 되고 모든 예외처리를 완료해서 프로그램 종료를 출력하게 된다.

 

 

 

 

 

 

반응형

댓글