[Java] 예외 처리

김나우·2022년 1월 13일
0

Java

목록 보기
11/15

참고 도서
Do it 자바 완전정복

예외

개발자가 프로그램을 작성하는 과정에서 실수를 하거나 사용자가 잘못된 값을 입력하면 오류가
발생할 수 있다. 다양하게 발생하는 오류 중 개발자가 해결할 수 있는 오류'예외',
이러한 예외가 발생했을 때 이를 적절히 처리하는 것을 '예외 처리'라고 한다

예외와 에러의 차이점

예외는 '연산 오류', 숫자 포맷 오류등과 같이 상황에 따라 개발자가 해결할 수 있는
'오류'를 말한다.
에러는 자바 가상 머신 자체에서 발생하는 오류로 '개발자가 해결할 수 없는 오류'를 말한다.

예외 클래스의 상속 구조

Throwable 클래스를 상속받은 Exception 클래스는 다시 일반 예외 클래스와 실행 예외 클래스로 나뉜다.

Exception 클래스에게서 직접 상속받은 예외 클래스들이 처리하는 일반 예외
컴파일 전에 예외 발생 문법을 검사하며, 예외 처리를 하지 않으면 문법 오류가 발생 한다.

RuntimeException 클래스를 상속받은 예외 클래스들이 처리하는 실행 예외
컴파일 전이 아니라 실행할 때 발생하는 예외로, 예외 처리를 따로 하지 않더라도
문법 오류가 발생하지 않는다.

일반 예외 클래스

일반 예외는 예외 처리를 하지 않으면 문법 오류를 발생시켜 컴파일 자체가 불가능하다.

InterruptedException

Thread.sleep(시간) 메서드는 일정 시간 동안 해당 쓰레드를 일시정지 상태로 만드는 Thread 클래스의
정적 메서드이다. 이 메서드는 일반 예외가 발생할 수 있기 때문에 반드시 예외 처리를 해야 한다

public class A{
	pulbic static void main(String[] args){
    	// Thread 실행 중 예외 발생 가능 interruptedException처리 필요
    	Thread.sellp(1000);	
    }
}

ClassNotFoundException

class.forName("패키지명.클래스명")은 클래스를 동적으로 메모리에 로딩하는 메서드로,
해당 클래스의 정보를 담고 있는 Class 타입의 객체를 리턴한다. 만일 클래스를 메모리에
동적으로 로딩하는 과정에서 해당 클래스가 존재하지 않을 때는 ClassNotFoundException이
발생하므로 이에 대한 예외 처리를 반드시 포함해야 한다.

public class A{
	public static void main(String[] args){
    	//class가 없을 때 예외 발생 가능 ClassNotFoundException 처리 필요
    	Class cls = Class.forName("java.lang.Object");
    }
}

IOException

IOException은 자바 입출력 부분에서 자주 보게 될 일반 예외로, 콘솔이나 파일에 데이터를
쓰거나(write()) 읽을(read()) 때 발생하며, 반드시 IOException에 대한 예외처리를 해야한다.

public class A{
	public static void main(String[] args){
    	InputStreamReader isr = new InputStreamReader(System.in);
        //입출력을 실행할 대 예외 발생 가능 IOException 처리 필요
        isr.read();
    }
}

FileNotFoundException

파일을 읽을 때 해당 경로에 파일이 없으면 FileNotFoundException이 발생한다.
실제 파일의 존재 유무와는 상관없이 파일이 존재하지 않을 가능성이 있는 코드이기 때문에
반드시 예외 처리를 해야 문법 오류가 발생하지 않는다.

public class A{
	public static void main(String[] args){
    	//File이 없을 때 예외 발생 가능 FileNotFoundException 처리 필요
    	FileInputStream fis = new FileInputStream("text.txt");
    }
}

실행 예외

일반 예외는 예외 처리를 해 주지 않으면 문법 오류가 발생하기 때문에 실행 자체가 불가능한 반면
실행 예외는 문법 오류가 발생하지 않는다. 그렇기 때문에 예외 처리 없이 컴파일과 실행이
가능하지만, 행 중 실행 예외가 발생하면 프로그램은 강제 종료된다. 실행 예외를 처리하는
클래스는 Exception의 자식 클래스인 Runtime Exception 클래스의 자식 클래스들이다

Arithmetic Exception

Arithmetic의 사전적인 뜻은 '산술', '연산'이다 Arithmetic Exception은 연산 자체가
불가능 할 때 발생하는 실행 예외다.
수학식에서 절대 존재할 수 없는 대표적인 연산은 분모가
0일 때다.

public class A{
	public static void main(String[] args){
    	//연산 불가(분모가 0)로 Arithmetic Exception 발생
    	System.out.println(3 / 0);
    }
}

ClassCastException

상속 관계에 있는 클래스 간의 업캐스팅은 항상 가능하지만, 다운캐스팅은 가능할 수도, 불가능할 수도 있다고 했다. ClassCastException은 다운캐스팅이 불가능한 상황에서 다운캐스팅을
시도할 때 생한다.

class A{}
class B extends A{}

public class Test{
	public static void main(String[] args){
    	 A a = new A();
         //Class 캐스팅이 불가능하면 ClassCastException 발생
         B b = (B) a;
    }
}

ArrayIndexOutOfBoundsException

ArrayIndexOutOfBoundsException은 배열의 인덱스를 잘못 사용했을 때 발생한다.
배열의 인덱스는 항상 0 ~ (배열의 길이 -1)까지의 값만 사용할 수 있다.
만일 이 범위 밖에 있는 인덱스를 사용하면 이 예외가 발생한다.

public class A{
	public static void main(String[] args){
    	int[] a = {1, 2, 3};
        //인덱스의 범위를 넘어섰을 때 ArrayIndexOutOfBoundsException 발생
        System.out.println(a[3])
    }
}

NumberFormatException

문자열을 정숫값으로 변환하고자 할 때는 Integer.parseInt("문자열")
실수값으로 변환하고자 할 때는 Double.parseDouble("문자열")을 사용한다
이렇게 문자열을 숫자 또는 실수로 변환할 때 문자열이 변환하고자 하는 숫자 형식이 아니면
반환이 실패하는데 이때 발생하는 예외가 NumberFormatException이다.

public class A{
	public static void main(String[] args){
    	//숫자가 아닌 것을 숫자로 바꿀 때 NumberFormatException 발생
    	int num = Integer.parseInt("10!");
    }
}

예외 처리

예외 처리는 예외가 발생했을 때 처리하는 방법을 제공하는 문법 요소로, 예외 처리 구문이
포함되면 예외가 발생하더라고 프로그램이 계속 실행 된다.

예외 처리 문법

예외 처리 문법은 다음과 같이 3가지 요소(try, catch, finally)로 구성돼 있다.

try{
	//일반 예외, 실행 예외 발생 가능 코드
	catch(예외 클래스명 참조 변수명){
    	//예외가 발생했을 떄 처리
    } finally{
    	//예외 발생 여부에 상관없이 무조건 실행
    }
}

try 블록 안에는 에외가 발생할 수 있는 코드가 포함되어 있다

catch 블록 안에는 예외가 발생했을 때 처리할 코드가 포함되어 있다.
catch 블록이 모든 예외를 처리하는것이 아니라 소괄호 안의 예외 타입에
해당되는 예외에 한에서만 처리할 수 있다.

finally 블록은 예외가 발생하든, 발생하지 않든 무조건 실행된다

try{
	System.out.println(3 / 0);
    System.out.println("프로그램 종료");
} catch(ArithmeticException e){
	System.out.println("숫자는 0으로 나눌 수 없습니다");
} finally{
	System.out.println("프로그램 종료");
}

예외 처리 과정

실제 내부적으로 예외가 처리되는 메커니즘은
위 예제는 분모가 0인 연산을 수행해 ArithmeticException 실행 예외가 발생하는 코드를try{} 블록으로 감싸고
catch{} 블록에서는 이 예외를 처리하는 구문을 작성했다.
finally{} 블록에서는 "프로그램 종료"라는 문자열을 출력하고 프로그램을 종료했다.

try{
	//예외 발생
    //-> 발생한 예외 클래스 객체 생성
	System.out.println(3 / 0);
} catch(ArithmeticException e){		//catch 블록으로 전달
	System.out.println("숫자는 0으로 나눌 수 없습니다");
} finally{
	System.out.println("프로그램 종료");
}

먼저 try{} 구문이 실행된다. 만일 예외가 발생하지 않는다면 catch(){}
블록은 실행되지 않을 것이고, finally{}블록이 있다면 이 블록은 실행된다.

try{} 블록에서 예외가 발생하면 자바 가상 머신(JVM)이 가장 먼저 인지한다.
이후 자바 가상 머신은 발생한 예외 타입의 객체를 생성해 catch(){} 블록의 매개변수로 전달한다.

위 예제처럼 만일 ArithmeticException이 발생하면 자바 가상 머신은
ArithmeticException 객체를 생성하고, 생성 객체를 catch{}블록의
매개변수로 전달하는 것이다

다중 예외 처리

catch(){} 블록도 예외 타입에 따라 여러 개를 포함할 수 있다.

try{
	catch(예외 타입 e1){
    
    }
    
    catch(예외 타입 e2){
    
    }
    
    catch(예외 타입 e3){
    
    }
}
 try {
        System.out.println(3 / 0);
        } catch (ArithmeticException e) {
            System.out.println("숫자는 0으로 나눌 수 없습니다");
        }finally {
            System.out.println("프로그램 종료");
        }

        try {
            int a = Integer.parseInt("20A");
        } catch (NumberFormatException e) {
            System.out.println("숫자로 변환할 수 없습니다");
        } finally {
            System.out.println("프로그램 종료");
        }

첫 번째는 분모가 0일 떄 ArithmeticException을 처리하는 예외 처리 구문,
두 번쨰는 숫자가 아닌 문자열을 숫자로 변환하는 과정에서 발생하는
NumberFormatException을 처리하는 예외 처리 구문이다

각각의 예외 처리를 위해 각 부문마다 별도의 예외처리 구문을 작성 하는 것은
비효율적이다. 각각의 catch() 블록을 하나의 예외 처리 구문으로 묶어
다음예와 같이 표현할 수 있다.

public class Main {
    public static void main(String[] args) {

        try {
            System.out.println(3 / 0);
            int a = Integer.parseInt("20A");
        } catch (ArithmeticException e) {
            System.out.println("숫자는 0으로 나눌 수 없습니다");
        } catch (NumberFormatException e) {
            System.out.println("숫자로 변환할 수 없습니다");
        }
        finally {
            System.out.println("프로그램 종료");
        }
    }

이런 다중 예외 처리 구문을 작성할 때 반드시 주의해야 할 사항은
try{} 블록에서 예외가 발생하고, 여러 개의 catch(){} 블록이 있을 때
실행할 catch(){} 블록의 선택 과정은 항상 위에서부터 확인한다는 것이다

catch(){} 블록에 2개의 예외 동시에서 처리

try{
	
}catch(예외 타입 A | 예외 타입 B 참조 변수명)
public class Main {
    public static void main(String[] args) {

        try {
            System.out.println(3 / 1);
            int a = Integer.parseInt("10A");
        } catch (ArithmeticException | NumberFormatException e) {
            System.out.println("숫자는 0으로 나눌 수 없습니다");
        } finally {
            System.out.println("프로그램 종료");
        }
    }
}    

리소스 자동 해제 예외 처리

finally(){} 블록은 '항상 실행해야 하는 기능이 있을 때 사용하는 블록'
finalyy(){} 블록의 가장 대표적인 기능은 리소스를 해제하는 것이다.
리소스 해제는 더이상 사용하지 않는 자원을 반납하는 것이다.

자바에서 리소스 해제가 꼭 필요한 대표적인 예로 자바 입출력 리소스를 들 수 있다

public class Main {
    public static void main(String[] args) {

        InputStreamReader is = null;
        try {
            is = new InputStreamReader(System.in);
            System.out.println(is.read());
        } catch (IOException e) {
            //예외 처리
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    //예외 처리
                }
            }
        }
    }
}

try{} 블록에는 문자를 단위로 입력을 수행하는 InputStreamReadr 객체를
생성해 사용하고 finally{} 블록에서는 자원을 반납 했따.

리소스 자동 해제 예외 처리

try(리소스 자동 해제가 필요한 객체 생성){
	//예외 발생 코드
} catch(예외 클래스명 참조 변수명){
	//해당 예외가 발생했을 때 처리하는 블록
} finally{
	//예외 발생 여부에 상관없이 무조건 실행하는 블록
}

기존 예외 처리 구문과의 유일한 차이점은 try(){} 구문에도 소괄호(())가
포함된다는 것이다.
소괄호 안에서 자동으로 리소스를 반납해야 할 객체를 생성하면 예외 처리 구문의
실행이 끝났을 때 리소스가 자동으로 해제 된다.

리소스를 자동으로 해제하기 위해서는 반드시 try(){} 블록의 소괄호 안에서
생성한 객체의 내부에 close() 메서드가 포함되어 있어야 한다.

public class Main {
    public static void main(String[] args) {

        try (InputStreamReader is = new InputStreamReader(System.in);) {
            System.out.println(is.read());
        } catch (IOException e) {
            //예외 처리
        }
    }
}

이렇게 되면 리소스 자동 해제 예외 처리 구문을 빠져나갈 때 리소스 객체의
close()메서드를 자동으로 호출해 리소스를 해제해 준다.


예외 전가

예외가 발생했을 때 바로 처리할 수도 있지만, 자신을 호출한 지점으로
예외를 전가할 수도 있다.

예외를 전가하면 예외 처리의 의무를 호출한 메서드가 갖게 된다.

예외 전가 문법

예외를 전가할 때는 메서드의 소괄호와 중괄호 사이에 전가시키고자 하는
예외타입을 throws 키워드와 함께 삽입하는 방법을 사용한다.

리턴 타입 메서드명(입력매개변수) throws 예외 클래스명 {
	//예외 발생 코드
}
public class Main {
    public static void main(String[] args)  throws ClassNotFoundException{
        Class cls = Class.forName("a");
    }
}

위는 그냥 예시일 뿐이다

사용자 정의 예외 클래스

자바가 모든 예외 클래스를 제공하는 것은 불가능하다.
자바에 필요한 예외 클래스가 존재하지 않는다면 예외 클래스를 직접 정의해
사용하면 된다.

사용자 정의 예외 클래스 생성 방법

사용자 예외 클래스를 정의해 사용하는 과정은 3단계로 이루어져 있다.
첫 번째는 예외 클래스를 사용자가 직접 정의하고
두 번째는 작성한 예외 클래스를 이용해 객체를 생성한다.
세 번째는 고려하는 예외 상황에서 예외 객체를 던지면 된다.

사용자 정의 예외 클래스 작성

사용자 예외 정의 클래스를 정의하는 방법은
1. Exception을 바로 상속해 일반 예외 클래스로 만드는 방법
2. RuntimeException을 상속해 실행 예외 클래스로 만드는 방법

//일반 예외
class MyException extends Exception{
}

//실행 예외
class MyRuntimeException extends RuntimeException{
}

사용자 정의 예외 객체 생성

앞에서 정의한 예외 클래스로 예외 객체를 생성한다. 객체를 생성하는 방법은
일반 예외든, 실행 예외든 상관없이 일반 클래스로 객체를 생성하는 방법과 동일하다.

MyException me = new MyException();

MyRuntimeException mre = new MyRuntimeException();

예외 상황에서 예외 객체 던지기

예외 객체를 던진다는 것은 실제 자바 가상 머신에서 예외 객체를 만들어 전달한다는 의미이다
예외 객체를 던지면 곧바로 예외가 발생한다. 그러면 자바 가상 머신은 그 예외를 처리할 수 있는 catch(){} 블록에게 받았던 예외 객체를 전달한다.

예외 객체를 전달할 때는 'throw 예외 객체'의 형식을 사용한다
이때 사용하는 throw 키워드는 예외 객체를 던지는 기능을 수행하는 것으로,
예외를 전가하는 throws와 혼동하지 말고 반드시 구분해야 한다.

//예외 발생시키기
throw me;
throw mre;

예외 객체를 throw 키워드로 던졌을 때 내부적으로 이루어 지는 처리 과정

던져진 예외 객체는 자바 가상 머신으로 전달되고, 자바 가상 머신은 해당
예외 객체를 처리할 catch(){} 블록을 찾는다. 따라서 throw 이후에
예외 객체를 직접 처리하거나 예외를 전가하는 구문을 반드시 작성해야 한다.

해당 메서드가 직접 예외를 처리할 때는 자바 가상 머신이 전달받은 예외 객체를
해당 메서드 내의 예외 처리 블록으로 전달하고, 예외를 전가했을 때는
예외 객체를 상위 메서드 내의 예외 처리 블록으로 전달한다.

예외를 해당 메서드 안에서 직접 처리

public class Main {
    public static void main(String[] args) {
        
        void abc(int age){
            trt{
                if (age >= 0) {
                    System.out.println("정상값");
                } else {
                    //MyException 발생
                    throw new MyException();
                } catch(MyException e){
                    //예외 처리 구문 수행
                    System.out.println("예외 처리");
                }
            }
        }
        
        void bcd(){
            abc(-2);
        }
    }
}

예외를 상위 메서드로 전가해 예외 처리

public class Main {
    public static void main(String[] args) {
        //MyException 발생
        void abc(int age) throws MyException{
            if (age >= 0) {
                System.out.println("정상값");
            } else {
                //예외 전가
                throw new MyException
            }
            
            void bcd(){
                try {
                    abc(-2);
                } catch (MyException e) {
                    System.out.println("예외 처리");
                }
            }
            
        }
    }
}

이렇게 throw로 예외를 던지면 곧바로 예외가 발생하므로 적절한 예외 처리 구문을 반드시 작성해 놓아야 한다.

profile
안녕하세요

0개의 댓글