기초단계/JAVA

2022.11.25-1 JAVA 예외 처리

춘핑이 2022. 11. 25. 17:18

11. 예외 처리

11.1 예외와 예외클래스

에러 : 불가항력적인거 갑자기 전원이 꺼지거나 메모리 부족으로 꺼지는거 등등 외부요인으로 발생하는거
예외: 잘못된사용, 잘못코딩 발생시 곧바로 꺼짐.
정상x 비정상적인거 '내가' 코드를 잘못짜서 발생하는거 예측가능 발생시 정상적으로 돌아가도록하기

프로그램을 아무리 잘작성해도 발생할수있다. 예외가 발생해도 정상적으로 될수잇게끔

일반예외(exception) 컴파일러가 예외 처리 코드 여부를 검사하는 예외
코드작성하다 이미 알수잇음. 작성할때 표시되서 알수있음.
실행예외(Runtime exception) 컴파일러가 예외처리코드 여부를 검사하지 않는 예외
실행하면서 발생하는 거 잘몰라서 경험에 의해서 거를 필요가 있음.
예외가 발생시 예외 객체가 만들어짐. 이 객체는 예외 처리시 사용된다.
자바의 모든 에러와 예외 클래스는 Throwable을 상속받아 만들어지고 추가적인 예외 클래스는
java.lang.exception 클래스를 상속받는다.
사진안에 있는 예외는 거의 다 사용하는 거라 알아두는 것이 좋다.

11.2 예외처리 코드

예외가 발생햇을때 정상흐름으로 가기위한 코드
try-catch-finally블록 으로 구성됨
try 실행코드 -> 예외발생x -> finally있으면 실행(없어도됨)
try 예외발생 -> 건너뛰고 catch블록 실행 -> finally있으면 실행(없어도됨)
구분안해두면 그냥 프로그램 종료시킴.
예외가 발생할거 같으면 try블록으로 감싸고 처리방법인 catch 를 만들어야함.

메인 메소드 static으로 정의 메인안에서 바로호출하려고 만들어놓음.
문자열을 안들어오면 .length가 실행안됨.

public static void printLength(String data) {

    int result = data.length();  //data가 null일 경우 NullPointerException 발생함.
    System.out.println("문자 수: " + result);
}

public static void main(String[] args) {

    System.out.println("[프로그램 시작]\n");
    printLength("ThisIsJava");
    printLength(null); //data가 null일 경우 NullPointerException 발생함.
    System.out.println("[프로그램 종료]");


Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "data" is null
main 쓰레드에서 NullPointerException 예외가 발생했다.Cannot invoke 호출할수없어요 String.length()을 이거의 데이터가 null이기대문에
at ch11.sec02.ExceptionHandlingExample1.printLength(ExceptionHandlingExample1.java:17)
17라인에서 발생했다.(주석으로 메모중이라 그럼) -> 예외발생시 맨첫줄과 어디서 발생했는지를 봐야함.
at ch11.sec02.ExceptionHandlingExample1.main(ExceptionHandlingExample1.java:25)
-> 이거는 실행해야 발생하는거라 실행예외임. 올바른 값이 들어올거라는 장담이 없으니 이를 처리해야함.

 package ch11.sec02.exam02;

public class ExceptionHandlingExample2 {

public static void printLength(String data) {
try {
    int result = data.length();  //data가 null일 경우 NullPointerException 발생함.
    System.out.println("문자 수: " + result);
} catch(NullPointerException e) {
//변수선언하듯이 예외 넣어줌. 예외 발생시 객체로 생성됨 -> 클래스 변수로 객체 생성하듯이 
//-> 예외 변수 쓰면 변수에 예외의 위치정보를 변수가 가지게됨.
System.out.println(e.getMessage()); //예외객체로가서 메소드 getMessage()로 왜 예외가 발생했는지 출력
//System.out.println(e.toString()); // 예외의 종류와 예외의 사유까지도 나옴.
//e.printStackTrace(); 
//예외가 어디서 발생하였는지 위 두개는 출력문없어서 print해줘야하고 얘는 출력문이 포함되어있음.
//뭐가발생 + 몇라인에서 발생했는지 얘많이사용함.
//예외가 어디서 발생했느지 아는 것이지 예외를 처리하는 코드는 아님. 예외가 어디서 발생했는지를 아는 것.
//개발끝나고는 올바른 예외처리 코드를 넣고 주석처리하기. 
//그래서 개발단계에서는 catch에 e.printStackTrace();를 많이 넣고 개발함.
} finally {
    //finally부분은 넣어도 안넣어도되는데 try나 catch후 실행
    System.out.println("[마무리 실행]\n"); 
    }
}
public static void main(String[] args) {

    System.out.println("[프로그램 시작]\n");
    printLength("ThisIsJava");
    printLength(null); //data가 null일 경우 NullPointerException 발생함.
    System.out.println("[프로그램 종료]");
    }
}

package ch11.sec02.exam02;

public class ExceptionHandlingExample {

public static void main(String[] args) {
// 다음 예제는 코드를 작성할 때부터 컴파일러가 찾아줌
    try {
        Class.forName("java.lang.String");
        //사실 java.lang.String이 String의 전체이름임. 기본적으로 자바가 제공하니 당연히     있음.
        //Class.forName 클래스가가진forName이라는 정적메소드를 사용하겟다.
        //()안의 이름을 가진 클래스가 있느냐? 있으면 로딩해라 없으면 오류발생
        //작성하자마자 오류발생 Unhandled exception type ClassNotFoundException 처리되지 않는 것이다 오류나옴
        System.out.println("주어진 클래스가 있습니다.");
    } catch(ClassNotFoundException e) {
        System.out.println("주어진 클래스가 없습니다.");
    }

    System.out.println();

    try {
        Class.forName("java.lang.String2"); 
        //Class.forName 클래스가가진forName이라는 정적메소드를 사용하겟다.
        ()안의 이름을 가진 클래스가 있느냐? 있으면 로딩해라 없으면 오류발생
        //작성하자마자 오류발생 Unhandled exception type ClassNotFoundException 처리되지 않는 것이다 오류나옴
        System.out.println("주어진 클래스가 있습니다.");
    } catch(ClassNotFoundException e) {
        System.out.println("주어진 클래스가 없습니다.");
        e.printStackTrace();
    }
}
}

11.3 예외 종류에 따른 처리

프로그램 실행시 여러가지 예외가 발생할 수 있다. 예외1 발생 예외처리1 예외2 예외처리2 예외의 수만큼 catch를 작성가능하다
catch는 타입으로 구분가능 하나만 처리하고 다른건 발생안함. 동시에 두가지 예외 발생x
예외 작성할때 Array 누르고 스페이스 누르면 예외종류나옴

package ch11.sec03.exam01;

public class ExceptionHandlingExample {

public static void main(String[] args) {
    String[] array = {"100", "1oo"};

    for(int i = 0 ; i <=array.length; i++){
        try {
            int value = Integer.parseInt(array[i]);
            System.out.println("array[" + i + "]:" + value);
        } catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("배열 인덱스가 초과됨: "+ e.getMessage()); //1oo이 숫자가 아니나 예외발생
        } catch(NumberFormatException e) {
            System.out.println("숫자로 변환할 수 없음: "+ e.getMessage()); //인덱스 2번까진데 3번하니 예외발생
        }
    }
}
}

처리해야할 예외 클래스들이 상속 관계에 있을 때는 하위 클래스 catch 블록을 먼저 작성하고 상위 클래스 catch 블록을 나중에 작성해야한다.
1센션의 상속관계 사진 확인 예외가 발생하면 catch 블록은 위에서부터 차례대로 검사해서 상위클래스catch블록이 먼저 검사대상이되면 밑에가 실행이안됨.
예를들어 Exception e를 해버리면 가장 상위 클래스이기 때문에 밑의 catch문이 발생이 안됨.

catch(Exception e) {
System.out.println("배열 인덱스가 초과됨: "+ e.getMessage()); 
} catch(NumberFormatException e) {
    System.out.println("숫자로 변환할 수 없음: "+ e.getMessage()); 
}
//Unreachable catch block for NumberFormatException. 밑에가 절대 실행될수없는데 왜적엇냐고 나옴.
//어떠한 예외든 Exception e의 하위이기때문!

//예외가 많을 때 원하는 것은 1로 해결하고 나머지는 모두 같게 처리하고싶으면
catch(NumberFormatException e) {
    System.out.println("숫자로 변환할 수 없음: "+ e.getMessage()); 
}catch(Exception e) {
    System.out.println("배열 인덱스가 초과됨: "+ e.getMessage()); }
// 순서를 바꿔서 작성하기
    catch(NullPointerException | NumberFormatException e){
    System.out.println("데이터에 문제가 있음")
}
// 2가지 예외를 동일하게 처리할 때는 |을 넣는다.

11.4 리소스 자동 닫기

리소스랑 데이터를 제공하는 객체 리소스는 파일을 이야기도 하고 프로그램에서 데이터를 가지고 있는 것을 의미한다.
리소스를 사용하기 위해서는 open 해야하고 사용이 끝나면 close 해야한다.
a에서 사용중인데 b에서 편집하기는 불가능함. 닫아줘야 다른프로그램에서 사용가능.
리소스를 사용하다가 예외가 발생될 경우에도 안전하게 닫는 것이 중요하다. 닫지않으면 리소스가 불안정한 상태로 남아있게됨.
try문에서 닫으면? 중간에 오류가 생기면 안닫히고 캐치문 발생. 캐치문에 클로즈를 쓰면? 중복의 문제 finally에 클로즈를 작성해서 안전하게 닫도록하기
그러나 자동으로 닫게 하는 기능이 있다.
try-with-resources 블록을 사용하면 예외 발생여부와 상관없이 리소스를 자동으로 닫아준다.
try 괄호에 리소스를 여는 코드를 작성하면 try블록이 정상적으로 실행을 완료햇거나 도중에 예외가 발생하면 자동으로 close()메소드가 호출된다.
try(FileInputStream fis = new FileInputStream("file.txt"){...} catch{..}

try-with-resources 블록을 사용하기 위해서는 AutoCloseable 인터페이스를 구현해서 close()메소드를 재정의해야한다.
public class 클래스명 implements AutoCloseable{
@Override
public void close() throws Exception {...}

복수의 리소스를 사용하면 try()괄호안에 세미콜론으로 구분해서 리소스를 여는 코드를 작성하면된다.

package ch11.sec04;

public class MyResource implements AutoCloseable { 
// AutoCloseable 자바가 제공하는 기본 인터페이스임.
    private String name;

    public MyResource(String name){
        this.name = name;
        System.out.println("[MyResource(" + name + ") 열기");
    }
    public String read1() {
        //read1()했을 때는 100을 리턴값으로 줌
        System.out.println("[MyResource(" + name + ") 읽기");
        return "100";
    }
    public String read2() {
        //read1()했을 때는 abc를 리턴값으로 줌
        System.out.println("[MyResource(" + name + ") 읽기");
        return "abc";
    }    
    @Override
    public void close() throws Exception {
        System.out.println("[MyResource(" + name + ") 닫기");    
    }
    }

try {
//리소스열기
MyResource res3 = new MyResource("res3");
//리소스읽기
    System.out.println(res3.read1());
} catch(Exception e){
    e.printStackTrace();
} finally {
    //리소스 닫기
    //res3.close(); 무조건 닫아주는게 정석임.
}

try (MyResource res = new MyResource("A")){
    String data = res.read2();
    int value = Integer.parseInt(data);
} catch(Exception e) {
    System.out.println("예외처리: " + e.getMessage()); //예외가 발생하든 아니든 close실행
}
System.out.println();

MyResource res1 = new MyResource("A"); //try안에 넣으면 오픈소스가 너무 길어져서 정의해두고 밑에 넣음.
MyResource res2 = new MyResource("B");
try (res1; res2){
    String data1 = res1.read1();
    String data2 = res2.read1();
} catch(Exception e) {
    System.out.println("예외처리: " + e.getMessage()); //예외가 발생하든 아니든 close실행
}
}
}

11.5 예외떠넘기기

어떤 메소드가 실행하다가 예외가 발생할때 메소드 호출한 곳으로 예외를 떠넘길 수도 있다.
이때 사용하는 키워드고 throws 이다. throws는 메소드 선언부 끝에 작성하는데 떠넘길 예외 클래스를 쉼표로 구분해서 나열한다.
리턴타입 메소드명(매개변수,...) throws 예외클래스1, 예외클래스2 ...{}
자바 표준 api의 메소드에는 다부분 throws 같은 키워드가 들어가있음. 따라서 내가 사용할때 예외처리를 알아서 해야함.

try {
    findClass(); //밑에서 던져서 빨간줄나와서 누르고 하면 try catch알아서 나옴.
} catch(ClassNotFoundException e) {
    System.out.println("예외처리: " + e.toString());
}
}

public static void findClass() throws ClassNotFoundException{
    Class.forName("java.lang.String2");
}

public static void main(String[] args) throws ClassNotFoundException {}
메인에 throws해버릴 수도있다. -> 자바 가상 머신 jvm에 예외를 던져버려서 오류 발생해버림. 좋지 못한 코드임.
리턴타입 메소드명(매개변수,...) throws Exception 하면 종류구분없이 예외를 던지겟다느 뜻

11.6 사용자 정의 예외

인터넷 뱅킹 프로그램에서 잔고보다 더 맣은 출금 요청이 들어온 경우에는 잔고 부족예외를 발생시킬 필요가있다.
그러나 잔고 부족 예외는 표준 라이브러리에는 존재하지 않기 때문에 직접 예외 클래스를 정의해서 사용해야한다.
이것을 사용자 정의 예외라고 한다.

사용자 정의예외는 컴파일러가 체크하는 일반 예외로 선언할수도 잇고 컴파일러가 체크하지 않는 실행예외로 선언할 수도잇다.
통상적으로 일반 예외는 Exception의 자식클래스로 선언하고 실행예외는 RuntimeException의 자식클래스로 선언한다.

 package ch11.sec06;

public class InsufficientException extends Exception{
//매개변수가 없는 기본 생성자 만들기
public InsufficientException() {}

//괄호를 열고 예외 발생이유를 받아서 프로그램을 사용할 수있도록 부모의 생성자를 호출해서 처리
public InsufficientException(String message) {
    super(message);
}
}

package ch11.sec06;

public class Account {
private long balance;

public Account() {}

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;
}
}

package ch11.sec06;

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);
}
}
}