자바의 예외와 예외처리

TIL·2023년 1월 7일
0

Java (최은빈)

목록 보기
25/27

1. 에러 (핸들링 불가)

  • JVM 문제 발생
  • StackOverFlowError
    • JVM 고유의 스택 공간 범위 벗어났을 때 발생 (보통은 잘못된 재귀호출에 의해 발생)
  • VirtualMachineError
    • JVM 손상되었거나 작동하는데 필요한 자원이 부족했을 때 발생하는 예외
  • OutOfMemoryError
    • JVM의 힙 공간이 과도한 요구 또는 지속적인 누수로 인해 더이상 요청한 메모리를 할당할 수 없거나 OS의 이유로 스레드 생성하는데 실패했을 때 발생

2. 예외 (핸들링 가능)

  • 개발자의 구현 실수나 사용자의 입력 실수에 의해 발생
    • NullPointException
    • ClassCastException
  • 개발자가 미리 예측하여 미리 방지하거나 발생했을 경우 핸들링 가능
  • 예외 발생되면 프로그램은 바로 종료. 다음 문장 실행되지 않음
  • 종료를 막고 정상 실행 상태 유지 <사용자 입장>
  • 로그 처리해서 왜 문제가 생겼는지를 개발자가 리팩토링 하기 위함 <개발자 입장>
    • UserInputException() -> UserInputTypeException(), UserInputRangeException(), .. 세부적 핸들링 가능



계층도

1. Object - Throwable - Exception/Error

  • 예외 발생 원인을 메시지로 담을 수 있음
    public IOException(String message, Throwable cause) { super(message, cause) }
    • cause에 해당 예외를 발생시킨 예외도 같이 전달 가능 (Exception이 Trowable 상속하므로)
  • 예외가 연결될 때 (chained exception) 연결된 예외 정보들을 기록하기도 함
  • getMessage()printStackTrace() 메소드 정의되어 있음

2. Unchecked (RuntimeException)

  • try catch 하지 않아도 컴파일 에러 안뜨지만, 프로그램 중단의 가능성이 있음
  • Exception 밑의 RuntimeException 상속
    • NullPointException : null인 참조 변수의 멤버를 호출시 발생
    • ClassCastException : 클래스 간의 형을 잘못 변환 했을 때 발생
    • IndexOutOfBoundException : 배열 범위 벗어났을 때 발생
    • ArithmeticException : 정수를 0으로 나누려고 했을 때 발생
  • 대부분 if문으로 제어 가능해서 굳이 try~catch 사용 안함

3. Checked

  • 개발자가 체크(try ~ catch) 안하면 컴파일 에러
    • => too strict 하여 개발 도중 불편함 -> unchecked exception 으로 wrapping 하여 사용 (ORM)
    • SQLException -> DatabaseAccessException (RuntimeException. 컴파일 에러X)
  • RuntimeException 상속 안하고 그냥 Exception만 상속
  • Java application 내에서 발생하지 않고, 외부 자원 접근과 관련
    • SQLException, IOException (파일, 디비)
    • FileNotFoundException : 파일 읽으려고 하는데 파일 시스템에 해당하는 파일이 없다면
      => 다른 전략을 세워야함 (디폴트 이미지나 엑스박스 같은 이미지로 대신함)



try, catch, finally

try {
    // 예외가 발생할 가능성이 있는 코드
} catch (Exception1 e1) {
    // Exception1 발생시 실행되는 코드
} catch (Exception2 e2) {
    
...
} catch (ExceptionN eN) {
    
} finally {
		// 예외 발생과 상관없이 실행되는 코드
        // ex) 리소스 자원을 닫는 코드
		// finally 구문은 생략 가능
}



throws

  • 해당 exception 객체를 메서드 호출부로 전달하여 거기서 try catch
  • 계속 상위 메서드로 throws 하다가 main() 에서도 try catch 하지 않는다면 프로그램 종료
public static void main(String[] args) {
		try {
				method();
		} catch (Exception e) {
				// Exception 발생시 실행되는 코드
		} finally {
				// 예외 발생 유무와 상관없이 실행되는 코드
				// finally 구문은 생략 가능
		}
}

public void method() throws Exception {
		// 예외가 발생할 가능성이 있는 코드 (=> new Exception())
}



throw

  • throw exception : 해당 함수가 발생시킴
  • method() throws Exception: 해당 함수가 호출 된 곳으로 전달
  • try catch 해주면 프로그램 종료 되지 않고 에러 메시지 출력 후 다음 코드 실행됨
    • 계속 throw 하여 아무것도 핸들링 하지 않은 채 올라가는 것은 좋지 않음
public int nextInt(int radix) {
        if ((typeCache != null) && (typeCache instanceof Integer)
            && this.radix == radix) {
            int val = ((Integer)typeCache).intValue();
            useTypeCache();
            return val;
        }
        setRadix(radix);
        clearCaches();
        try {
            String s = next(integerPattern()); // 그냥 next()로 받고
            if (matcher.group(SIMPLE_GROUP_INDEX) == null)
                s = processIntegerToken(s);
            return Integer.parseInt(s, radix); // parseInt()로 Integer로 변환 -> throws NumberFormatException
        } catch (NumberFormatException nfe) { // NumberFormatException 발생하는 경우
            position = matcher.start();
            throw new InputMismatchException(nfe.getMessage());
            // 새로운 객체로 wrapping 하여 전달
            // NumberFormatException 이름이 모호하여 이름 바꿔치기함
            // = mapping
        }
    }

  • nextInt() 이 parseInt()에서 발생할 수 있는 예외를 랩핑 후 throw 했으므로 nextInt() 호출부에서(상위 메서드에서) 핸들링함
try {
    age = scanner.nextInt();
} catch(InputMismatchException e) {
    System.out.pringln("[ERROR] please input int only.");
}



NullPointException

  • 자바에서 제공
  • RuntimeException 상속
public class NullPointeExceptionExample {
    public static void main(String[] args) {
        String str = null;

        // if
        // 실행하기 전에 핸들링 할 수 있으면 하는게 좋음 (try catch <<<< if)
        if (str != null) { // null check
            System.out.println(str.length());
        }
        System.out.println("finally block");
        System.out.println("main method exit...");

        // try catch
        // 일단 실행하고 나중에 처리 하겠다
        try {
            System.out.println(str.length()); // 여기서 예외 발생 하여 프로그램 종료 -> 아래 코드 실행 안됨
        } catch (NullPointerException e) {
            e.printStackTrace(); // 몇번줄에서 발생했는지 알려줌
        } finally {
            System.out.println("finally block");
        }
        System.out.println("main method exit...");
    }
}

ArrayIndexOutOfBoundException

  • 개발자 실수 (코드를 직접적으로 수정해야 함. 실무에서는 이런 경우를 try~catch 사용 안함)
  • "main exit..." 이 예외보다 먼저 출력되는 이유
    • main, try ~ catch 두개의 스레드가 도는 것
    • printStackTrace() 시간이 오래 걸리기 때 (오호 그런거였구만)
public class ArrayIndexOutOfBoundExceptionExample {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5}; // 0 ~ 4
        try {
            for (int i = 0; i <= arr.length; i++) { // 0 ~ 5
                System.out.println(arr[i]); // 5 까지 출력되고
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("main exit ..."); // 여기도 출력됨
    }
}



NumberFormatException

public class NumberFormatExceptionExample {
    public static void main(String[] args) {
        String string1 = "100";
        String string2 = "10o"; // String 이 int 로 바꿀 수 없는 형태라면 NumberFormatException

        try {
            int intValue1 = Integer.parseInt(string1); // parseInt() 가 throws => 호출부에서 try catch
            int intValue2 = Integer.parseInt(string2); // NumberFormatException 발생 => 아래 두줄 실행 안
            System.out.println("intValue1 = " + intValue1);
            System.out.println("intValue2 = " + intValue2);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        System.out.println("main exit ...");
    }
}



ClassCastException

  • 부모가 자식으로 캐스팅
  • 상속 관계가 아닌 객체 간의 캐스팅

abstract class Animal {
    protected abstract String getName();
}

class Mammal extends Animal {
    @Override
    protected String getName() {
        return "Mammal";
    }
}
class Amphibian extends Animal {
    @Override
    protected String getName() {
        return "Amphibian";
    }
}

class Dog extends Mammal {
    @Override
    protected String getName() {
        return "Dog";
    }
}
class Frog extends Amphibian {
    @Override
    protected String getName() {
        return "Frog";
    }
}

public class ClassCastExceptionExample {
    public static void main(String[] args) {
        Frog frog = new Frog();

        // 호출부에서 try catch
        try {
            birth(frog);
        } catch (ClassCastException e) {
            e.printStackTrace();
        }
        System.out.println("main exit ...");
    }


    public static void birth (Animal animal) throws ClassCastException { // 호출부로 전달

        // 캐스팅 할 객체의 타입 체크 또는 호출부로 전달
        // unchecked exception => "개발자 실수" (if-else)
//        if (animal instanceof Mammal) {
//            Mammal mammal = (Mammal) animal;
//            System.out.println(mammal.getName() + " give birth to young");
//        } else if (animal instanceof Amphibian) {
//            Amphibian amphibian = (Amphibian) animal;
//            System.out.println(amphibian.getName() + " lay eggs");
//        }

        // animal의 타입 체크 하지 않고 캐스팅 하면 예외 발생 가능
        Mammal mammal = (Mammal) animal; // Frog cannot be cast to class Mammal
        System.out.println(mammal.getName() + " give birth to young");

        Amphibian amphibian = (Amphibian) animal; // Dog cannot be cast to class Amphibian
        System.out.println(amphibian.getName() + " lay eggs");

    }
}



상속 관계의 예외 객체 catch하기

  • 모든 예외를 최상위 부모가 한번에 잡는 건 편하지만, 어떤 예외가 발생했는지 로그를 찍을 수 없다
  • 부모 예외 객체의 로그 그대로 사용 (하나로 공통) => 자식 클래스의 캐치 블록을 최상단으로 올리면 구체적인 로깅 가능

public class InheritedExceptionBadCode {
    public static void main(String[] args) {
        try {
            throw new ExceptionC(); // 최하위 객체는 // ExceptionA e = new ExceptionC() (다형성)

        // A, B, C 전부로 잡을 수 있다
        } catch (ExceptionA e) {
            System.out.println(ExceptionA.class.getName() + " => catch");
//        } catch (ExceptionB e) { // C는 A로 잡히기 때문에 도달하지 않음 (컴파일 에러)
//            System.out.println(ExceptionB.class.getName() + " catch");
//        } catch (ExceptionC e) {
//            System.out.println(ExceptionC.class.getName() + " catch");
        }
    }
}
public class InheritedExceptionGoodCode {
    public static void main(String[] args) {
        try {
            throw new ExceptionC(); // 최하위 객체

        // 자식 클래스의 캐치 블록을 최상단으로 올림
        } catch (ExceptionC e) { // C만 여기서 잡힘
            System.out.println(ExceptionC.class.getName() + " => catch");
        } catch (ExceptionB e) { // B만 여기서 잡힘
            System.out.println(ExceptionB.class.getName() + " => catch");
        } catch (ExceptionA e) { // A만 여기서 잡힘
            System.out.println(ExceptionA.class.getName() + " => catch");
        }
    }
}



사용자 정의 예외

  • 예외 직접 만들기
    • extends Exception/RuntimeException
    • message, cause
    • 사용자 정의 예외 클래스는 특정 어플리케이션에서만 사용하는 예외로서
      특정 어플리케이션에 대한 내용을 알아야만 사용할 수 있으므로외부 리소스와 관련된 예외로 취급함
    • 강제로 체크하라고 만든 예외 클래스 이므로 또는 강제로 체크하고 싶은 예외인 경우
    • 회사마다 다름
  • 직접 만든 예외 사용하기
    • throw, throws 둘다 해야함

public class InvalidNumberRangeException extends Exception {
    private final static String MESSAGE = "InvalidNumberRangeException"; // 출력할 메시지

    public InvalidNumberRangeException() { super(MESSAGE); }
    public InvalidNumberRangeException(String message) {
        super(message);
    }

    // 메시지, 원인 예외 객체 받음
    public InvalidNumberRangeException(String message, Throwable cause) {
        super(message, cause);
    }
}
public class UserDefinedExceptionExample {
    public static void main(String[] args) throws InterruptedException {
        Scanner scanner = new Scanner(System.in);
        int money;

        while (true) { // 무한정의 기회
            try {
                System.out.print("input money: ");
                money = scanner.nextInt();
                isValidMoneyValue(money);
                break; // 예외 발생하지 않으면 반복 블록 종 => to line 34
            } catch (InputMismatchException e) { // by nextInt() // 숫자 아닌 데이터
                scanner.nextLine(); // flush buffer in scanner
                e.printStackTrace();
            } catch (InvalidNumberRangeException e) { // 내가 만든 예외 // 숫자인데 음수인
                e.printStackTrace();
            } finally {
                System.out.println("finally block"); // 각 입력마다 예외 발생과 상관없이 실행
            }
            Thread.sleep(1000); // sleep 1 sec for printing stack trace
        }

        System.out.print("you can go kimbab paradise "); // line 34
        System.out.println("with your money = " + money + " won");
        System.out.println("main exit ...");
        scanner.close();
    }

    public static boolean isValidMoneyValue(int money) throws InvalidNumberRangeException {
        if (money < 0) {
            throw new InvalidNumberRangeException();
        }
        return true;
    }
}

0개의 댓글

관련 채용 정보