국비/Java

2023.03.21 38일차 Java

춘핑이 2023. 3. 21. 16:41

11.예외 처리

exception이 발생햇을때 어떻게 코드를 짜야하는지

에러 : 코드로 어쩔수 없는 상황
예외 : 발생하면 프로그램이 종료된다.
일반예외(exception) : 컴파일러가 예외처리코드 여부를 검사하는 예외 checked exception
실행예외(runtime exception) : 실행하면서 예외발생 unchecked exception

예외가 발생하면 Exception객체가 만들어지고 던져진다.

11.1 예외

일반예외 (Exception, checked exception)
Exception 클래스와 그 하위 클래스의 객체

실행예외 (RuntimeException, unchecked exception)
RuntimeException 클래스와 그 하위 클래스의 객체

11.1.1 runtime exception

예외가 발생하면 api를 가서 왜 발생했는지 알아보아야한다.
IndexOutOfBindingException
ClassCastException 등등
RuntimeException은 실행할때 발생한다.
다음은 0으로 나누어서 예외가 발생하는 것이다.

public class C02RuntimeException {
    public static void main(String[] args) {
        int a = 0;
        int b = 3;

        int c = b / a;

        System.out.println("실행 흐름 이어감.");
    }
}

11.1.2 CheckedException

Class.forName("java.lang.Object");
이런 코드는 예외가 발생하지 않지만 발생할 수도 있으니
다루는 코드를 작성해달라고 컴파일러가 체크한다.

위 코드는 ClassNotFoundException이 발생할 수 있다.
에러를 체크하기 위해 try - catch에 담아주어야한다.

public class C06CheckedException {
    public static void main(String[] args) {
        try {
            Class.forName("java.lang.Object");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("continue....");
    }
}

exception이 발생하냐 안하냐에 따라가 아니라 어떤 예외가 발생하냐에 따라 작성해주어야한다.
런타임 exception은 체크를 안해서 실행할때 발생하는 것을 알고
checkedException은 예외가 발생하든 안하든 예외처리를 해줘야한다.

런타임 exception가 발생하면 출력되는 내용을 자세하게 살펴보고
힌트를 얻어서 관련된 코드들을 수정해야한다.

12.2 try-catch

exception이 발생할 수도 있는 코드를 try라는 블록안에 감싸야한다.
그 앞뒤 코드는 같이 들어가도 되고 안들어가도된다.

try와 짝을 이루는것이 catch구문이다.
발생한 exception을 잡아서 실행하는 코드 작성하면된다.

예외가 발생하면 throw한다. 이 던진 예외를 잡는 것이 catch구문이다.
던져진 예외 객체의 참조값이 catch구문에 저장이된다.
그래서 이 예외객체가 가진 메소드를 사용할 수 있다.

catch블록이 실행되고 흐름을 이어나가게 된다.

public class C01TryCatch {
    public static void main(String[] args) {
        try { // exception 발생할 수 있는 코드 작성
            int a = 0;
            int b = 3;
            int c = b / a;
        } catch (ArithmeticException e) {
            // 발생한 exception을 잡아서 실행하는 코드 작성
            e.printStackTrace();
        }

        System.out.println("contine...");
    }
}

그럼 try - catch블록을 꼭써야하나?
그건 예외 종류에 따라 다르다.
checked exception에서는 무조건 작성해줘야한다.

12.2.1 printStackTrace()

exception이 발생하면 catch구문으로 넘어간다.
exception이 발생했는데 아무일도 하지 않으면 문제 업는 코드처럼 보인다.
그런데 나중에 와서 고칠 수 잇도록 예외가 발생한것을 나타내느 흔적을 남겨주어야한다.

exception이 발생햇을때 내용을 알려주는게 exception 객체의 메소드인 printStackTrace이다.
쓰지않으면 문제없는 것처럼 보여서 나중에 고치기 어려워진다.

흔적을 남기는 방법을 e.getMessage, e.toString도 있는데 별일없으면 printStackTrace를 사용하자.

public class C03PrintStackTrace {
    public static void main(String[] args) {
        try {
            int a = 0;
            int b = 3;
            int c = b / a;
            System.out.println("try block continue....");
        } catch (ArithmeticException e) {
            // catch block
            System.out.println("예외발생!!!");
            e.printStackTrace();
        }
        System.out.println("program contine...");
    }
}

checked exception은 무조건 작성해줘야해서 이클립스가 자동완성 힌트를 제공한다.

public class C04PrintStackTrace {
    public static void main(String[] args) {
        //checked exception
        try {
            Class.forName("java.lang.String");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

12.2.2 MultipleException

어떤 코드는 여러가지 예외를 발생시킬 수도 있다.
그럴땐 catch블록을 여러개 작성하면된다.

public class C05MultipleException {
    public static void main(String[] args) {
        try {
            int[] a = {0,1,2};

            //ArithmeticException
            //ArrayIndexOutOfBoundsException
            //두 예외 발생가능
            int c = 3 / a[0];
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("contine..");
    }
}

12.2.3 다형성

예외들은 Throwable Exception 등의 상속받는다.
그래서 다형성을 이용해서 상위의 클래스를 적어주면
여러 catch문을 작성하지 않아도 한번에 예외를 처리할 수 있다.
하는일이 같다면 상위타입으로 한번에 담는게 효율적이다.
Object는 던질 수없어서 Object타입으로는 받을 수 없다.

public class C06MultipleException {
    public static void main(String[] args) {
        try {
            int[] a = { 0, 1, 2 };

            // ArithmeticException
            // ArrayIndexOutOfBoundsException
            int c = 3 / a[0];

        } catch (RuntimeException e) {
            e.printStackTrace();
        }
        System.out.println("contine..");
    }
}

12.2.3 다형성 -2

다형성을 이용할때 주의할점이 있다.
Exception이 발생하면 첫번째 catch문부터 확인하고 다음 catch문으로 넘어가는 흐름을 가진다.
그래서 상위 타입의 예외를 먼저 작성하면 안되고 마지막에 작성해줘야한다.
상위타입이 먼저 나오면 다음 catch문들이 uncatchable code가 되버린다.

public class C08MultipleException {
    public static void main(String[] args) {
        try {
            //여러 exception발생가능
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        } catch (ClassCastException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("continue");
    }
}

12.2.4 MultipleException-2

예외가 하는 일이 같다면 상위 타입으로 한번에 잡아도되고
|를 사용해도 된다.

public class C09MultipleException {
    public static void main(String[] args) {
        try {
            // 여러 exception발생가능

        } catch (ArithmeticException e) {
            e.printStackTrace();
        } catch (NumberFormatException | NullPointerException e) {
            e.printStackTrace();
        }
        System.out.println("continue");
    }
}

12.3 finally

예외 발생유무와 상관없이 실행시키고 싶은 코드가 있다면
finally블록에 작성해줘야한다.

public class C01Finally {
    public static void main(String[] args) {
        try {
            int a = 0;
            int c = 3 / a;

            System.out.println("try block continue...");
        } catch (ArithmeticException e) {
            System.out.println("exception!!!");
        } finally {
            //exception 발생 유무와 상관없이 항상 실행
            System.out.println("finally block @@@@ ");
        }
        System.out.println("continue...");
    }
}

12.3.2 finally -2

finally block은 try문이나 catch문에서 어떤것이 일어나도 상관없다.
심지어 try문에서 return이 되더라도 finally block은 무조건 실행된다.

public class C02Finally {
    public static void main(String[] args) {
        try {
            boolean a = true;

            if (a) {
                return;
            }
            System.out.println("try block..");
        } catch (NullPointerException e) {
            System.out.println("exception block...");
        } finally {
            // 심지어 return 되어도 실행되는 블럭
            System.out.println("finally block");
        }
        System.out.println("continue...");
    }
}

finally block은 catch블록이 없어도 작성이 가능하다.
catch블록이 있다면 순서를 지켜야한다.

12.3.3 확인문제 6번

다음 코드의 실행 순서

public class Example {
    public static void main(String[] args) {
        String[] strArray = {"10", "2a"};
        int value = 0;
        for (int i = 0; i <= 2 ; i++) {
            try {
                value = Integer.parseInt(strArray[i]);
            } catch (ArrayIndexOutOfBoundsException e){
                System.out.println("인덱스를 초과했음");
            } catch (NumberFormatException e) {
                System.out.println("숫자로 변환할 수 없음");
            } finally {
                System.out.println(value);
            }
        }
    }
}

답 :
for문 3번반복
try10 finally출력 -> try 2a 숫자변환x finally10출력
-> try 인덱스초과 finally 10출력

12.4 Throw 예외 던지기

예외가 발생하면 잡는 것을 했는데 던져지니까 잡는것이다.
던지는 코드를 작성해보자.

Throwable 타입의 객체라면 던질 수 있다.
객체를 던지면 메소드에 객체를 던진다는 표시를 해줘야한다.

public class C01Throw {
                                    // 던지는 객체 타입명시
    public static void main(String[] args) throws Throwable {
        // Throwable 타입의 객체를 던질 수 있다.

        Throwable a = new Exception();

        //객체 던지기
        throw a;
    }
}

12.4.1 Throw -2

객체를 던지면 예외를 던진 메소드를 실행하는 곳에서 예외처리를 해줘야한다.

public class C02Throw {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void method1() throws Exception {
        throw new Exception();
    }
}

12.4.2 Throw -3

unchecked exception을 만들어서 던져보자.
그런데 컴파일러가 체크하지 않기 때문에 컴파일러가 던졌는지를 확인하지 않는다.
그래서 throws를 써도되고 안써도 된다.
메소드를 사용하는 곳에서 감지를 못하기때문에 잘 보고 작성해주자.

public class C04Throw {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void method1() {
        throw new RuntimeException();
    }
}

12.4.3 Throw -4

여러개라면 여러개 throws에 ,를 기준으로 작성해주면된다.

public static void method1() throws ClassNotFoundException, FileNotFoundException{
    int a = 1;
    if (a == 1) {
        throw new ClassNotFoundException();
    } else {
        throw new FileNotFoundException();
    }
}

다형성으로 Exception하나로 받을 수도 있다.

public static void method2() throws Exception {
    int a = 1;
    if (a == 1) {
        throw new ClassNotFoundException();
    } else {
        throw new FileNotFoundException();
    }
}

12.4.4 Throw -5

throw된 예외를 사용하는 곳에서는 다양한 방식으로 예외처리를 할 수 있다.

public static void main(String[] args) {
    try {
        method1();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

    try {
        method1();
    } catch (FileNotFoundException | ClassNotFoundException e) {
        e.printStackTrace();
    }

    try {
        method1();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

12.4.5 Throw -6

실제 발생하는 예외가 잇어도 던지는 것이 Exception을 던진다고 해보자.
Exception이 ClassNotFoundException이다라고 할 수 없으니 받을때 ClassNotFoundException로 예외처리를 할 수 없다.

public static void method1() throws Exception {
    boolean a = true;
    if(a) {
        throw new ClassNotFoundException();
    } else {
        throw new FileNotFoundException();
    }
}

//불가능
try {
    method1();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

12.4.6 Throw -7

메소드를 사용하는 곳에서 잡지 않으면 또 던질 수 있다.
throw하는 메소드를 호출한다면 잡거나 던지거나 결정을 해야한다.

public class C10Throw {
    public static void main(String[] args) throws ClassNotFoundException {
        method2();
    }

    public static void method2() throws ClassNotFoundException {
        method1();
    }

    public static void method1() throws ClassNotFoundException{
    }
}

현실에서 예외처리는 무조건 그냥 던지는 경우가 많다.

12.5 사용자 정의 예외

코드를 짜다가 exception을 발생시키고 싶다.
이미 있는 것 중에서 발생시키고 싶다. 그러면 필요한것을 가져다 사용하면된다.
그런데 많아도 내 마음에 안드는게 있을 수 있다.
Exception이나 RuntimeException을 상속받아서 직접 만들면된다.

public class NonExistMenuException extends Exception{
}

public class C01CustomException {
    public static void main(String[] args) {
        String menu = null;
        try {
            menu = getMenu(3);
        } catch (Exception e) {
            System.out.println("알 수 없는 메뉴를 선택했습니다.");
            e.printStackTrace();
        }
        System.out.println("선택된 메뉴: " + menu);
    }

    public static String getMenu(int num) throws Exception {
        if (num ==1 ) {
            return "선택";
        } else if (num ==2) {
            return "종료";
        } else {
            throw new NonExistMenuException();
        }
    }
}

12.5.1 사용자 정의 예외-2

Exception을 만들때 생성자에 문자열을 파라미터로 넣으면
문자열이 예외가 발생할때 같이 전달된다.
getMessage의 값으로 지정되게 된다.

public class C02CustomException {
    public static void main(String[] args) throws Exception {
        Exception e = new Exception();
        Exception e2 = new Exception("some message");

        System.out.println(e.getMessage());
        System.out.println(e2.getMessage());

        throw e2;
    }
}

우리가 만든 예외 클래스에 적용시키려면 생성자를 재정의해줘야한다.

class MyException1 extends Exception {
    public MyException1() {
    }

    public MyException1(String message) {
        super(message);
    }
}

public class C03CustomException {
    public static void main(String[] args) throws Exception {
        MyException1 e = new MyException1();
        MyException1 e1 = new MyException1("some message");

        throw e1;
    }
}

12.5.2 확인문제 6번

예금보다 출금 금액이 많다면 예외발생

public class InsufficientException extends Exception {
    public InsufficientException() {
    }

    public InsufficientException(String message) {
        super(message);
    }
}

public class Account {
    private long balance;

    public long getBalance() {
        return balance;
    }

    public void deposit(int money) {
        balance += money;
    }

    public void withdraw(int money) throws InsufficientException {
        if (balance < money) {
            throw new InsufficientException("잔고 부족: " + (money - balance) + " 모자람");
        }
        balance -= money;
    }
}

public class AccountExample {
    public static void main(String[] args) {
        Account account = new Account();

        // 예금하기
        account.deposit(10000);
        System.out.println("예금액: " + account.getBalance());

        // 출금하기
        try {
            account.withdraw(30000);
        } catch (InsufficientException e) {
            String message = e.getMessage();
            System.out.println(message);
        }
    }
}

12.5.3 확인문제 7번

login()메소드에서 존재하지 않는 ID를 입력하면
NonExistIDException을 발생시키고
잘못된 비밀번호르 입력하면 WrongPasswordException을 발생시키자.

public class NonExistIDException extends Exception {
    public NonExistIDException() {
    }

    public NonExistIDException(String message) {
        super(message);
    }
}

public class WrongPasswordException extends Exception {
    public WrongPasswordException() {
    }

    public WrongPasswordException(String message) {
        super(message);
    }
}

public class LoginExample {
    public static void main(String[] args) {
        try {
            login("white", "12345");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        try {
            login("blue", "54321");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    public static void login(String id, String password) throws NonExistIDException, WrongPasswordException {
        // id가 blue가 아니라면 NotExistIDException을 발생
        if (!id.equals("blue")) {
            throw new NonExistIDException("아이디가 존재하지 않습니다.");
        }
        if (!password.equals("12345")) {
            throw new WrongPasswordException("패스워드가 틀립니다.");
        }
    }
}

2023.03.21 후기

예외 처리는 이론상으론 배우기 쉽다.
그런데 강사님 말로는 그냥 다 던져버리는 경우가 실제로는 많다고 해서 충격이었다.
메인에 던져서 JVM이 처리하게 하는 것은 좋지 않다고 들었는데 크지 않은 이슈라 그냥 던져버리나보다.
그래도 잘 처리할 수 있도록 신중을 가할 필요는 있어보인다.