language/java

8.예외 처리

wooweee 2023. 3. 10. 20:40
728x90

1. 프로그램 오류

  • 프로그램 오류, 프로그램 에러
    • 프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우
    • 발생시점
      1. 컴파일 에러: 컴파일 시 발생하는 에러
        * compiler: 구문체크, 최적화, 번역, 생략 코드 추가
      2. 런타임 에러: 실행 시 발생 모든 에러
      3. 논리적 에러: 실행은 되지만 의도와 다르게 동작

 

  • 에러와 예외

    컴파일러가 소스코드 *.java 검사 후 오류가 없을 시 *.class 파일이 생성되고 프로그램이 실행된다.
    프로그램 실행시 발생하는 에러가 존재할 수 있으므로 해당 모든 경우의 수를 고려하여 대비하기 위해 에러와 예외를 둔다.
    1. 에러 : 프로그램 코드에 의해 수습될 수 없는 심각한 오류 - 복구 불가
      • OutOfMemoryError
      • StackOverflowError
    2. 예외 : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류 - 복구 가능

출처: 자바의 정석 기초편 8장

  • 예외 처리: Progrem 실행 시 발생할 수 있는 예외를 대비해서 코드를 작성하는 것
  • 예외 처리 목적: Progrem의 비정상 종료를 막고, 정상적인 실행상태 유지

 

2. 예외 클래스의 계층 구조

출처: https://velog.io/@jwoo/after-java-semina-4

  • Exception class: 모든 예외의 최고 조상
    1. IOException: 입출력 잘못할 때 터짐, Input/OutputError 
    2. ReflectiveOperationException
      • ClassNotFoundException: *.class를 찾지 못할 때 터짐
    3. ... 생략
    4. RutimeException:프로그래머의 실수로 터짐
      1. ArithmeticException: 산술계산 실수로 터짐
      2. ClassCastException: 형변환 잘못하면 터짐
      3. NullPointException: 널 들어가면 터짐
      4. ... 생략
      5. IndexOutOfBoundsException: array 범위 벗어나면 터짐

자주 볼 것들 위주로만 간략히 설명을 적었다.

더보기

참고

  • OutOfMemoryError : 메모리가 부족한 경우 발생(Error)
  • FileNotFoundException : 존재하지 않는 파일에 접근하려고 할 때 발생
  • SocketException : 네트워크에 오류가 발생할 때 발생
  • IllegalArgumentException : 잘못된 인자 전달 시 발생
  • InputMismatchException : Scanner 클래스의 nextInt() 메서드를 호출하여 정수를 입력받으려고 했을 때, 정수가 아닌 문자가 입력으로 들어온 경우 발생. NoSuchElementException을 상속받음.
  • NumberFormatException : 문자열이 나타내는 숫자와 일치하지 않는 타입의 숫자로 변환 시 발생
  • ArrayIndexOutOfBoundsException : 배열의 인덱스 범위를 초가할 경우 실행 예외.

 

3. Runtime 오류 분류

  1. error
  2. 예외
    예외의 분류기준은 사용자의 실수 및 외적인 요인인지 개발자의 실수인지에 따라 분류한다.
    1. "ExceptionClass 와 그 자손들" - 여러 exception들을 묶어서 임의의로 명한 이름이다.
      • 사용자의 실수 및 외적인 요인
      • IOException, ReflectiveOperationException,cloneNotSupportedException, InterruptedException 과 자손들
      • RuntimeException과 그 자손을 제외한 모든 예외부분이다.
    2. RuntimeException과 그 자손들
      • 개발자의 실수

 

4. try-catch문

  • 예외 처리 위한 try-catch 코드
    • {} 생략불가
    • 위에서부터 차례대로 내려가고 단 한개의 catch 블럭만 수행된다.
    • catch문 예외 넣을 때 구체적인 예외부터 넣는다.
try{
    // 예외 발생 가능성 존재하는 문장 넣기1
    // 예외 발생 가능성 존재하는 문장 넣기2 -> 예외 발생 -> catch문에서 예외찾기 //Exception3 예외라 가정
    // 예외 발생 가능성 존재하는 문장 넣기3 -> 2번 code 예외 발생으로 실행 안됨
}catch (Exception1 e1){
    // Exception1이 발생했을 경우, 이를 처리하기 위한 문장 작성 -> 해당 예외 있을 경우만 실행
}catch (Exception2 e2){
    // Exception2이 발생했을 경우, 이를 처리하기 위한 문장 작성
}catch (Exception3 e3){
    // Exception3이 발생했을 경우, 이를 처리하기 위한 문장 작성 -> 예외 실행 -> 실행 후 try catch문 loop 벗어남
}catch (ExceptionN eN){
    // ExceptionN이 발생했을 경우, 이를 처리하기 위한 문장 작성
}

 

5. 예외 처리하기

  • 예외 처리: Progrem 실행 시 발생할 수 있는 예외를 대비해서 코드를 작성하는 것
  • 예외 처리 목적: Progrem의 비정상 종료를 막고, 정상적인 실행상태 유지
  • 예외 처리를 못했을 때: 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외는 JVM의 예외처리기가 받아서 예외 원인을 출력한다.

 

6. 예외 흐름 보기

  1. try블럭 내에서 예외 발생
  2. 발생한 예외에 해당하는 예외 클래스(Exception class들 혹은 NullPointException) 찾음
    • Q
      • 어떻게 발생 예외어떤 예외 클래스인지 알까?
      • 일단은 모든 runtime 예외에 관해서 class로 만들어 놓았다는 것은 알겠는데 class를 다 뒤지는 건가?
    • A - 아래와 같은 원리로 작동을 한다.
      • 예를 들어서 0/0 이 있다고 하면 / -> 연산자도 method이다. divide(x,y);
      • 해당 divide method에 어떤 경우일 때 나타나는 예외를 던진다. divdie(x,y) {... throw new Arr~}
  3. 해당 예외 타입의 instance 생성
    • 발생한 예외와 일치하는 catch블럭 찾기
      발생 예외가 생성한 예외 클래스 타입의 인스턴스에 instanceof 연산자 이용해서 검사
      1. catch블럭에 일치 예외 존재
        • 해당 catch 블럭 수행
        • 전체 try-catch문 벗어나고 try-catch문 이후 코드 수행
      2. catch블럭에 일치 예외 없음
        • 프로그램 비정상적 종료
        • JVM 예외처리가 작동
  4. try블럭 내에서 예외 없을 시
    • catch블럭 거치지 않고 전체 try-catch문 빠져나가서 이후 코드 수행

예제

class Practice{
    public static void main(String[] args){
        System.out.println(1);
        try {
            System.out.println(0/0); // 예외 -> 예외의 type이 ArithmeticException 인 것을 확인 -> 해당 타입의 instance 생성
            System.out.println(3); // 실행 안됨
        } catch (ArithmeticException e) { // instanceof 연산자를 이용해서 검사
            System.out.println(4); // 실행 됨
        } catch (Exception e) {
            System.out.println(5); // 실행 안됨
        }// try-catch 끝
        
        System.out.println(6); // 실행됨
        // return;
    }
}

 

7. printStackTrace(), getMessage()

  • 예외가 발생하여 생성된 예외 클래스의 인스턴스에는 발생항 예외에 대한 정보가 담겨있다.
  • 발생한 예외의 정보를 이용할 때 자주 사용되는 method
    1. printStackTrace(): 예외발생 당시의 호출스택에 있었던 메서드의 정보와 예외 메시지를 준다
    2. getMessage(): 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
class Practice{
    public static void main(String[] args){
        System.out.println(1);
        try {
            System.out.println(0/0);
        } catch (ArithmeticException ae) {
            System.out.println("printStackTrace");
            ae.printStackTrace();
            System.out.println("getMessage");
            ae.getMessage();
        }// try-catch 끝
        
        System.out.println("프로그램 정상 종료");
    }
}

 

8. 멀티 catch블럭

  • | 기호 이용
  • 하나의 catch 블럭에 예외 type을 합칠 수 있다.
  • 예외 클래스의 개수에 제한 없다.
  • 조상관 자손 관계의 예외 클래스면 컴파일 에러 발생 -> 그냥 조상 class 사용하면 되기 때문
  • 멀티 catch블럭에 잡힌 예외 객체는 실제로 어떤 예외가 발생한 것인지 알 수 없다.
  • 알고싶으면 if(e instanceof Exception class)를 통해서 찾기

예제

public static void main(String[] args){
    try {
        System.out.println(0/0);
    } catch (Exception1 | Exception2 e) {
        if (e instanceof Exception1) {
            Exception1 e1 = (Exception1) e;
            e1.method1();
        } else{
            ...
        }
    }// try-catch 끝
    System.out.println("프로그램 정상 종료");
}

 

9. 예외 발생시키기

  • throw 키워드를 사용해서 고의로 예외를 발생시킬 수 있다.
  • 순서
    1. 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다
      • Exception e = new Exception("고의로 예외 발생");
        • 생성자 내부에 string을 넣으면 예외class instance의 메세지로 저장된다.
        • getMessage로 추출 가능
    2. 키워드 throw를 이용해서 예외 던진다.
      • throw e;
    3. 축약
      • throw = new Exception("고의로 발생시키기");

예제

public static void main(String[] args){
    System.out.println(1);
    try {
        throw new Exception("예외 고의 발생");
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }// try-catch 끝

    System.out.println("프로그램 정상 종료");
}

 

10. checked예외, unchecked예외

  • checked 예외
    • ExceptionClass들과 그 자손들
    • try-catch 안하면 컴파일 에러 발생
  • unchecked 예외
    • RuntimeException class와 그 자손들
    • try-catch 안해도 컴파일 에러 발생 안함
    • runtime 때 에러 발생
    • 개발자의 실수로 나타나기 때문에 예외처리를 강제하지 않는다.
      • 코드가 너무 복잡해지는 것을 방지
      • 대신 스스로 주의해서 코드 짜야함

 

11. 메서드에 예외 선언하기(throws)

  • 예외 처리 방법
    1. try-catch
      • 직접 예외 처리
    2. throws: 예외던지기
      • 자신이 try-catch 안할 때 해당 method를 호출한 method에게 예외를 떠넘긴다.
      • Unchecked 예외는 method에 따로 throws를 명시하지 않아도 예외를 던질 수 있다.
    3. 은폐
      • big data 같이 값 1,2개 틀렸다고 프로그램이 멈추면 일이 안되는 경우 필요

 

void method() throws Exception1, Exception2, ... ExceptionN{
    //method 내용
}
  • throws
    1. , 를 통해서 여러가지 예외를 선언할 수 있다.
    2. 예외 발생시 자신을 호출한 method에게 예외를 던진다.
    3. overriding의 규칙 3가지 중에서 조상 class의 method보다 많은 예외를 가질 수 없다.
      • 이는 자식 class가 예외를 조상에게 던질수 있기 때문에 조상보다 많은 예외를 가지게 되면 조상이 예외를 처리하지 못할 수가 있다.
      • 조상 메서드보다 적은 예외를 작성하더라도 조상이 가지고 있는 예외를 작성하지 않으면 컴파일 에러가 발생하지 않더라도  런타임때 에러가 발생한다. 
    4. method 선언부에 예외 처리를 적어줌으로써 어떤 예외들이 처리되어져야 하는지 미리 파악을 할 수 있게 된다.
      • 예외발생 가능성에 대해 예측이 쉽다.

Overriding 참고

더보기

java에서 메서드 오버라이딩을 할 때, 하위 클래스의 메서드가 상위 클래스의 메서드와 예외를 다르게 선언할 수 없는 이유는 다음과 같습니다.

메서드를 호출하는 코드에서는 해당 메서드가 선언한 예외들을 모두 처리해야 합니다. 만약 하위 클래스의 메서드가 상위 클래스의 메서드보다 예외를 더 많이 선언하게 된다면, 상위 클래스의 메서드를 호출하는 코드에서는 예외 처리를 하지 못할 수 있습니다. 따라서 하위 클래스의 메서드가 상위 클래스의 메서드와 예외를 다르게 선언하면, 하위 클래스의 객체를 사용하는 코드에서 컴파일 에러가 발생하게 됩니다.

예를 들어, 다음과 같은 코드가 있다고 가정해 봅시다.

 
class SuperClass { public void doSomething() throws Exception { // ... } class SubClass extends SuperClass { public void doSomething() throws Exception, RuntimeException { // ... } }

위의 SubClass에서는 RuntimeException 예외도 던질 수 있도록 선언하였습니다. 이렇게 되면, SuperClass를 상속받은 다른 하위 클래스에서는 RuntimeException 예외를 처리하지 않을 수 있습니다. 이는 예기치 않은 예외 발생으로 이어질 수 있으므로, 컴파일러는 이를 허용하지 않습니다.

따라서 하위 클래스의 메서드는 상위 클래스의 메서드보다 예외를 적게 선언하거나, 같은 예외를 선언할 수 있지만, 더 많은 예외를 선언할 수는 없습니다.

  • method 예외 살펴보는 방법
    1. Throws에 해당 method의 모든 예외를 적어놓았다.
    2. method 옆에 InterruptedException만 적은 이유는 checked 예외 이기 때문이다.
    3. IllegalMonitorStateException은 NullPointException으로 unchecked 예외 이기 때문에 굳이 넣지 않은 것이다.

java api

 

12. 메서드 예외 예제

  • 다 떠넘길 때
    1. method2에서 부터 예외가 터지고 try-catch가 없어서 method2를 호출한 method1로 예외 넘기고
    2. method1도 try-catch없어서 다시 method1을 호출한 main에게 예외 넘기고
    3. main조차 try-catch없어서 main method가 그대로 종료 되고
    4. 최종 jvm에게 예외가 넘어가서 jvm이 예외를 처리하고 프로그램을 비정상적으로 종료하게 된다.
class Practice{
    public static void main(String[] args) throws Exception{
        method1();
    }
    static void method1() throws Exception{
        method2();
    }
    static void method2() throws Exception {
        throw new Exception();
    }
}
  • main method가 예외를 처리한 경우
class Practice{
    public static void main(String[] args) throws Exception{
        try {
            method1();
        } catch (Exception e) {
            System.out.println("catch");
        }
    }
    static void method1() throws Exception{
        method2();
    }
    static void method2() throws Exception {
        throw new Exception();
    }
}

 

13. finally 블럭

try {
    // 예외 가능성 존재하는 문장
} catch(Exception e1){
    // 예외처리 위한 문장
} finally{
    // 예외 발생 관계없이 항상 수행되는 문장
    // finally 블럭은 try-catch 문의 마지막에 위치
}
  • 예외 발생 없을 시
    • try -> finally
  • 예외 발생 시
    • try -> catch -> finally

 

14. 자동 지원 반환 - try-with-resources문

  • try-catch의 변형문
  • 입출력과 관련된 클래스를 사용할 때 유용
  • 사용
try(resources 첨부){
    /*code 작성*/
} catch(Exception e){
    /*code 작성*/
  }
  • 장점
    1. try(resource1; resource2) 에서 resource 객체들은 따로 close() 작성하지 않아도 자동으로 close() 호출 된다.
      - 입출력에 사용되는 class 중 사용한 후에 꼭 close() 해야 하는 것이 존재 - resource 반환 때문
    2. close()이전에 예외가 발생 되고 close() 예외도 동시 발생 시, 예외문구에 Suppressed를 통해서 억제된 close()의 예외를 보여준다.
      exception들은 동시에 발생할 수 없기 때문에 try-catch 같은 경우 close()이전 예외가 무시되는 현상이 나타난다.

 

15. 사용자 정의 예외 만들기

  • 2가지만 기억하면 된다.
    1. Exception 혹은 RuntimeException class를 상속 받는다. -> 보통 RuntimeException(unchecked 예외)를 조상으로 받는다
      • Unchecked 예외, checked 예외 상관 없이 모두 extends 해야된다.
    2. Error 메세지 넣기
      1. 생성자로 String을 매개변수로 받는다. -> 조상 생성자의 메세지로 넣어주기 위함 
      2. @Override public String getMessage(){} 를 통해서 자신이 직접 message를 넣어줄 수 있다.
    3. 사용자 예외 객체 생성 -> 에러 메시지 작성(2가지 방법) -> 객체 생성됨 -> throws를 통해서 날림
class MyException throws RuntimeException{
    MyException(String msg){
        super(msg);
    }
}

class MyException throws RuntimeException{
    String errorMessage;
    
    MyException(String msg){
        super();
        errorMessage = msg;
    }
    
    @Override
    public String getMessage(){
        return errorMessage +" 원하는 문구 ";
    }
    
}

 

16. 예외 되던지기 - 거의 사용 X

class Practice {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("예외 되던지가 당함");
        }
    }
    static void method1() throws Exception{
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.println("예외 처리함");
            throw e; // 예외를 다시 살려버림
        }finally {
            System.out.println("예외 살려서 던지기 시작");
        }
    }
}

 

17. 예외의 return;

  • return
    • 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아간다.
    • 예외의 try와 catch문 마지막에 return을 작성 가능하다
    • 작성하지 않을 시 try catch문의 method의 마지막에 자동으로 들어가진다.
    • finally: return 여부와 상관 없이 항상 try catch loop 마지막에 실행된다.
class Practice {
    static void method(boolean b) {
        try {
            System.out.println(1);
            if(b) throw new ArithmeticException();
            System.out.println(2);
            // return; 작성시 System.out.println(6)은 항상 실행이 안되므로 compile error 발생
        } catch(RuntimeException r) {
            System.out.println(3);
            return; // return 작성안하면 try-catch loop만 빠져나오는 것이므로 System.out.println(6) 실행됨
        } catch(Exception e) {
            System.out.println(4);
            return;
        } finally {
            System.out.println(5);
        }
        System.out.println(6);
    }
    public static void main(String[] args) {
//        method(true);
        method(true);
    } // main
}

 

 

 

18. 예외 핵심 정리

헷갈린것 위주로 정리한 것. 핵심

  • return
    1. try
    2. catch
    3. method 마지막
  • finally - return 영향 안받음
  • System.exit(0); // 걍 프로그램 자체를 꺼버림
class Practice8 {
        public static void main(String args[]) {
            System.out.println("try-catch 외부 - 처음");
            try {
                System.out.println("예외없을 때까지 try 정상 작동");
                System.out.println(0/0); //ArithmeticException 발생
                System.out.println(2);
                return; // try문에 예외없이 끝까지 오면 return으로 인해 main method 빠져나옴
            } catch (ClassCastException ae)    {
                System.out.println("예외와 관련 없는 catch문의 구현부는 작동 안함");
            }catch (ArithmeticException ae)    {
                System.out.println("예외와 관련 있는 catch문의 구현부만 작동함");
                return; // 현재 예외 받고 구현부 수행하고 return; 작동함으로써 main method 자체를 벗어남 
            }finally {
                System.out.println("finally 작동");
            }
            System.out.println("try-catch 외부 - 끝 <try-catch에 return 존재시 작동 안함>");
            // return; try-catch에 return이 존재하지 않을 때 컴파일러가 자동 주입
        }
    }

 

 

19. 연결된 예외

 

사용 목적

  1. 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위함
    • 복잡한 예외를 재사용할 때 (조상-자손 관계)
  2. checked예외를 unchecked 예외로 바꿀 수 있도록 하기 위함

 

 

1. 여러가지 예외를 하나의 큰 분류의 예외로 묶기

  • 사용 method
    1. Throwable initCause(Throwable cause==원인 예외) : 지정한 예외를 원인 예외로 등록
    2. Throwable getCause(): 원인 예외를 반환

현재 catch해야되는 것이 SpaceException과 memoryException 2개뿐이지만 만약 7개 8개라고 하고 install()을 많이 써야되는 경우면 고통스러워진다.

// 연결된 예외를 사용하지 않는 경우

public class NoUse연결된 예외{

    static void install() throws InstallException {
        startInstall();
        copyFiles();
    }

    public static void main(String[] args){
        try{
            install();
        } catch(SpaceException e){
        	e.printStackTrace();
        } catch(MemoryException e){
        	e.printStackTrace();
        } catch(Exception e){
        	e.printStackTrace();
        }
        ...
        이제 install(); 사용할때 마다 계속 try-catch 작성해야함
        ...
    } // main 끝
}// class 끝
  • 고통을 없애기 위해서 애초가 install() 메서드 자체에 try catch를 넣고 모든 예외 경우를 작성
  • 이들을 하나로 묶을 수 있는 예외를 throws로 던진다.
  • 이때 initCause()로 개별 예외들을 한 번 감싸서 (현재는 InstallException()로 감쌌다.) 하나만 던져준다.

 

  • install() method에 SpaceException과 memoryException catch 안하고 그냥 InstallException만 받고 던져도 되지만 그렇게 되는 경우 해당 예외가 SpaceException인지 MemoryExceptioin인지 알 길이 없다.

정리

  • InstallException -> catch
  • InstallException(내부 상세 예외) -> catch 
// 연결된 예외를 사용하는 경우

public class Use연결된_예외 {

    static void install() throws InstallException {
        try{
            startInstall();
            copyFiles();        
        } catch (SpaceException se){
            // 조상예외 instance 생성 -> 실제로 method를 호출한 main()에게 InstallException을 호출한다.
            InstallExceptin ie = new InstallException("설치중 예외 발생"); 
            ie.initCause(se); // 연결된 예외
        } catch (MemoryException me){
            InstallExceptin ie = new InstallException("설치중 예외 발생"); // 조상예외 instance 생성
            ie.initCause(me); // 연결된 예외
        } catch (Exception e){
            e.printStackTrace("여기서 먼저 작동");
        }

    }

    public static void main(String[] args){
        try{
            install();
        } catch(InstallException e){
        	e.printStackTrace();
        } catch(Exception e){
        	e.printStackTrace("얘는 작성 필요 없음 - 만약 catch문이 아예 없으면 형식적으로 작성");
        }
        ...
        이제 install(); 사용할때 마다 계속 try-catch 작성시 짧게 작성 가능
        ...
    } // main 끝
}// class 끝

 

2. checked예외를 unchecked 예외로 바꿀 수 있도록 하기 위함

 

  • 이전부터 사용되온 checked 예외가 현재에는 필요가 없는 경우가 존재한다. - java가 30년 전부터 만들어져서 컴퓨터 예외상황이 많이 바뀌었기 때문
  • 하지만 checked예외를 함부로 없앨수 없는게 서로 상속되면서 얽힌관계가 너무 복잡하기 때문이다.
  • 그래서 checked예외를 Unchecked 예외로 감싸서 Unchecked 예외처럼 사용하게 된다.

 

// checked예외 그대로 사용
static void startInstall() throws MemoryException{
    if(!enoughMemory()){
        throw new Memoryexception("메모리가 부족합니다.");
    }
}

// unckecked예외에 주입
static void startInstall(){ // throws 작성 팔요 없음
    if(!enoughMemory()){
        throw new RuntimeException(new Memoryexception("메모리가 부족합니다."));
    }
}
  • 추측
    • RuntimeException의 Instance 내부에 원인 예외를 넣는 필드 혹은 method 공간이 존재
    • RuntimeException(Throwable cause)

 

이전 발행글 : 7. oop(object-oriented Programming) 객체지향 프로그래밍

다음 발행글 : 9. java.lang 패키지와 유용한 클래스