Java Study #14 - 예외처리

allzeroyou·2022년 2월 28일
0

Java

목록 보기
14/14

프로그램을 작성하는 과정에서 실수, 사용자가 잘못된 값을 입력하면 오류가 발생한다.
다양하게 발생하는 오류 중 개발자가 해결할 수 있는 오류를 '예외(exception)'이라고 한다.

이 예외가 발생했을 때 적절히 처리하는 것을 예외처리!

예외 vs 에러

예외는 '연산 오류, 숫자 포맷 오류 등과 같이 상황에 따라 개발자가 해결할 수 있는 오류'이다.
이때 '해결할 수 있는'의 의미는 오류 자체를 수정할 수 있다는 것이 아닌 차선택(대체방안)을 선택하는 것.

에러는 자바 가상 머신 자체에서 발생하는 에러로, '개발자가 해결할 수 없는 오류'이다.

int a = 1/0으로 0으로 나누려는 상황을 생각해보자. 분모는 0이 될 수 없기에 예외가 발생하고 a에는 어떤 값도 대입을 하지 못한다.
이 상황에서 1/0을 계산할 수 있게 하는 것이 아닌 a = -1, a = 1000처럼 a에 특정 값을 직접 대입하는 것을 예외처리라고 한다.

이렇게 개발자가 예외처리를 하면 프로그램은 종료되지 않고 계속 실행된다.

에러는 차선책을 선택하는 것 자체도 불가능할 때를 말하며 대표적인 예로는 메모리가 꽉 찼을때, 쓰레드가 죽었을 때 등을 들 수 있다.

즉, 예외는 개발자가 처리할 수 있는 오류, 에러는 개발자가 처리할 수 없는 오류.

자바에서 예외의 최상위 클래스는 Exception 클래스, 에러의 최상위 클래스는 Error 클래스다.
모두 Object의 Throwable을 상속하고 있으며 따라서 모든 에러와 예외가 Throwable 클래스의 모든 기능을 포함.

예외 클래스의 상속 구조

1. 일반 예외(Checked Exception)

문법을 체크하고 오류 발생시킴. 따라서 예외처리 필수!

1) InterruptedException

일정시간 도안 해당 쓰레드를 일시정지 상태로 만드는 Thread 클래스의 정적 메서드임.
이때 메서드는 일반 예외가 발생 가능하지만 문법적으로 예외처리를 하지 않았기에, 오류 발생


2)ClassNotFoundException

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

3) IOException

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

4) FileNotFoundException

5) CloneNotSupportedException


clone 호출 및 다운캐스팅도 했지만 오류가 나는 이유는 예외처리를 안했기 때문이다.

2. 실행 예외(UnChecked Exception=Runtime Exception)

예외처리를 하지 않아도 컴파일 가능하되 실행 중 예외가 발생하면 바로 프로그램 종료.

1) ArithmeticException

연산 자체가 불가능 할 때 발생하는 실행 예외
예외처리를 하지 않으면 실행 중인 프로그램은 예외 발생 상황을 출력후 강제 종료됨.

2) ClassCastException

상속에 있어 클래스 간의 업캐스팅은 가능하나 다운캐스팅은 가능할 수도 불가능할 수도 있음.
ClassCastException은 다운캐스팅이 불가능한 상황에서 다운캐스팅을 시도할 때 발생함.

3) ArrayIndexOutBoundsException

배열의 인덱스를 잘 못 사용했을 때 발생.
배열의 인덱스는 항상 0~ (배열의 길이 -1)까지의 값만 사용가능, 이 범위 밖 인덱스를 사용하면 예외 발생.

4) NumberFormatException

문자열을 정수값으로 변환하고자 할때 Integer.parseInt("문자열"), 실수값으로 변환은 Double.parseDouble("문자열")을 사용.
이때 문자열을 숫자, 실수로 변환할 때 문자열이 변환하고자 하는 숫자 형식이 아니면 변환 실패. 이때 발생하는 실행 예외가 NumberFormatException임.

5) NullPointerException

참조변수가 실제 객체를 가리키고 있지 않은 상황에서 필드나 메서드를 호출할 때 발생.
이때 null 값은 위치값을 저장하는 참조 변수의 초기값으로 사용 가능. 현재 가리키고(pointing)하고 있는 객체가 없다는 것을 의미.
객체를 가리키고 있지 않은데 해당 위치에 가서 객체 안에 있는 멤버를 실행하라고 명령하니 수행할 수 없음


예외 처리

  • 예외 처리 문법
try{
	//예외 발생 가능 코드(ex. 1/0)
    //일반 예외, 실행 예외 발생 가능 코드
}
catch(예외클래스 이름 참조변수명){
	//해당 예외가 발생한 경우 처리 블록
}
finally{ //생략가능
	//예외 발생 여부와 상관없이 무조건 실행블록
}
  • 예외 처리 프로세스
  1. try에서 예외 발생
  2. jvm는 발생한 예외 클래스 객체 생성
  3. 발생한 예외 클래스 객체를 받을(catcher) catch 블록으로 전달
  4. finally는 예외 발생 여부와 상관없이 실행.


jvm은 예외처리블록(catch)을 가지고 있으면 개발자가 catch 블록에서 예외처리를 잘 했겠거니 라고 생각하고 프로그램을 강제 종료시키지 않고 다음 코드 블록으로 넘어간다.


위의 try 블록과 catch 블록 내 공통된 코드는 예외 발생 여부와 상관없이 항상 실행됨.
따라서 finally 블록을 사용하면 코드의 중복을 제거할 수 있음

다중 예외 처리

if else if else if.. 처럼 catch 블록 역시 여러개 작성 가능.

  1. 단, catch 블록은 if-else if 구문처럼 위에서부터 순차적으로 확인해 하나의 catch 블록 만을 실행.

catch 문은 여러개 사용할 수 있지만 finally 문은 최대 1개만 작성가능.
try 블록에서 예외가 발생할 수 있는데, 이때 어떤 예외가 발생하느냐에 따라 어떤 catch블록이 실행되느냐가 결정!

+ 이때 catch 블록은 가장 먼저 발생하는 예외 단 1개만을 실행.


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

if else if/else if...에서도 위에 있는(순차적으로) else if을 실행하는 것처럼, 위에 예외처리에서 이미 수행을 해서 밑에 있는 catch 블록은 필요 없어지기에 오류 발생.

다중 catch 블록에서 순서가 매우 중요하다.
작은 조건부터 시작해 큰 조건으로 배치해야 함.

if else if/else if..구문에서 exception은 else에 해당한다고 생각하면 편하다.
if elseif/elseif..구문에서 위의 elseif조건에 걸리지 않은 조건은 else에서 처리하듯이, 위에서 해결 안된 예외는 exception에서 처리하면 된다.


2. 다중 catch 블록에서 처리 내용이 동일할 때 OR(|) 연산자를 이용해 하나의 catch 블록에서 두 개 이상의 예외 처리가 가능하다.

  • 리소스 자동해제 예외 처리(try with resource)

try(리소스를 사용하는 코드){
	//리소스를 사용하지 않는 예외 발생 가능 코드
}
catch(예외클래스 이름 참조변수명){
	//해당예외가 발생한 경우 처리블록
}
finally{
	//예외 발생여부와 상관없이 무조건 실행 블록
}

finally 블록은 항상 실행해야 하는 기능이 있을 때 사용하는 블록.
대표적인 기능은 리소스 해제.
리소스 해제는 더 이상 사용하지 않는 자원을 반납하는 것.

예외 발생 여부와 관계없이 try-catch 완료 후 자동으로 is.close() 실행(자동해제)
자동해제 리소스 객체는 반드시 close 메서드를 포함해야함
=자동해체 리소스 객체는 AutoCloseable 인터페이스 구현해야!


try catch문에 try뒤에는 원래 소괄호가 없었으나, 자동 리소스 해제를 위해 try뒤에 소괄호를 작성.
이때 소괄호는 try가 종료된 후 소괄호에 있는 리소스를 해제시킨다.

데이터 읽거나 쓸 때 예외처리는 IOException으로!
예외가 발생했을 때 printStackTrace메소드를 통해 e의 객체에서 예외가 발생한 경로를 출력한다.

콘솔에다가 사용자가 입력하는 것들을 isr1라고 리소스를 지정함.
isr1은 heap메모리를 가르키다가 try catch가 끝나면 메모리에서 heap메모리의 가르키고 있는 객체를 삭제함.


수동리소스 해제에서 finally 구문의 isr1이 아직 null이 아니라면(리소스 해제가 안되었다면) 리소스를 수동으로 해제함.
또한,
실제 try(){} catch(){}로 리소스가 해제되는지 확인하기 위해 System.in을 사용.
첫 번째 블록의 코드가 실행 => 입력받은 문자를 그대로 출력
try-catch 구문을 끝낼 때 리소스 해제=> 이후 콘솔 입력을 사용할 수 없음.
두 번째 코드를 실행하는 과정에서 예외발생.

참고) System.in 리소스를 해제(반납)하면 더이상 콘솔 입력 불가.



close를 사용하려면 class A에서 AutoCloseable 인터페이스를 포함하고 있어야!
인터페이스는 미완성메서드(추상메서드)를 가지고 있어야 하고 그 메서드가 바로 close 메서드이다.

throw한 Exception을 받아서 예외처리를 각각 해줘야 한다.

리소스 수동해제에 있어 close 메서드를 포함하고 있어야 하고 이때 close 메서드는 AutoCloseable 인터페이스를 구현해야 한다.
AutoCloseable인터페이스를 구현한 후 close() 메서드 내부에서 오버라이딩한 클래스를 정의한다. 메서드 내부에 필드를 null값으로 초기화하고 리소스가 해제됨을 나타내는 구문을 출력한다.


결국 수동 리소스 해제하는 법과 자동 리소스 해제하는 법이 같다.
이때 AutoCloseable을 상속한 클래스만 try의 중괄호 안에서 객체를 생성할 수 있다.

예외(Exception)의 전가(throws)

내가 예외를 처리하는 것이 아닌 나를 호출한 메서드에서 예외를 처리하는것.


첫번째는 bcd()메서드가 직접 예외 처리를 했다.
이때는 abc()메서드가 bcd()를 호출해 사용하는데 아무런 문제가 발생하지 않는다.
반면 두번째를 봐보자.
bcd()메서드가 예외를 처리하지 않고, 자신을 호출한 메서드로 예외를 전가하고 있다.
이렇게 되면 이제 예외를 전가받은 abc() 메서드는 전가된 예외를 처리할 의무를 갖게 되는 것이다.
다시 말해 예외를 전가하고 있는 bcd()메서드 자체가 예외 처리가 필요한 구문이 된 것.
bcd()메서드를 호출한 abc()메서드에서 bcd()가 전가한 예외 클래스 타입에 대한 예외 처리 구문을 작성해야 하는 것.

Thread.sleep()메서드를 사용하려면 예외처리를 해주어야 했는데, 그 이유는 자바 API 문서에서 실제 예외가 발생하는 구문은 Thread.sleep() 메서드 안에 포함되어 있기 때문이다.
그런데 이 메서드가 예외를 전가했으므로 어떤 메서드이든 Thread.sleep()메서드를 호출하는 메서드가 예외 처리를 해야만 하는 것.

최상위 메서드인 main() 메서드가 예외를 전가했을 때


main()메서드 마저도 예외를 전가하면 이 main() 메서드를 실행한 자바 가상 머신이 직접 예외를 처리.
자바 가상 머신의 예외 처리 방식은 간단함.
발생한 예외의 정보를 화면에 출력하고 프로그램을 강제 종료시킴.


예외를 처리할 때 여러개의 catch(){} 구문을 포함하는 다중 예외 처리가 가능했던 것처럼 하나의 메서드 안에 발생할 수 있는 예외가 여러 종류일 때는 여러개의 예외를 한번에 전가할 수 있다.
쉼표(,)로 구분해 나열.

예외 클래스 사용자 정의

자바가 모든 예외상황에 맞춰 예외 클래스를 만들어 논게 아니기 때문에, 특정 상황에서 예외를 발생시키기 위해서는 사용자가 정의 해야 함.

생성 방법

1) 예외 클래스를 사용자가 직접 정의
2) 작성한 예외 클래스를 이용해 객체 생성
3) 고려하는 예외 상황에서 예외 객체를 던진다(throw)

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

자바에서 제공하는 예외 클래스와 마찬가지로 Exception 상속해 일반 예외 클래스로 만드는 방법과 RuntimeException을 상속해 실행 예외 클래스로 만드는 방법으로 나눌 수 있음.

1) 사용자 클래스를 정의하는 과정에서 기본 생성자문자열을 입력받는 생성자 2개 추가

2) 예외 메시지를 전달받아 예외 객체를 생성하는 String 타입의 생성자로, 내부에서는 부모 클래스인 Exception 또는 RuntimeException 클래스의 생성자를 호출해 사용.

사용자 정의 예외 객체 생성

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

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

고려하는 예외 상황이 발생하면 생성한 객체를 던진다(throw)
throw의 의미는 jvm에게 예외 객체를 만들어 전달한다는 의미이다.
예외 객체를 던지면 곧바로 예외가 발생.
그러면 jvm은 그 예외를 처리할 수 있는 catch(){}블록에게 받았던 예외 객체를 전달.
예외 객체를 전달할때는 throw 예외 객체의 형식을 사용.
이때 사용하는 throw의 키워드는 예외 객체를 던지는 기능을 수행.
예외를 전가하는 throws 와 구분하자.

@1 예외를 직접 처리

메서드에서 입력매개변수로 넘어온 num 값이 70보다 작거나 같을 때 예외를 발생시키고, abc_1 메서드 안에서 예외 처리 수행.

@2 예외 전가

앞과 동일한 조건에서 예외가 발생 시 상위 메서드로 예외를 전가해 처리

예외 클래스의 메서드

사용자 정의 예외 클래스는 Exception 또는 RuntimeException 클래스 상속.
따라서 추가 메서드를 정의하지 않아도 부모 클래스의 메서드를 고스란히 내려 받음.

1. getMessage() 메서드

예외가 발생했을 때 생성자로 넘긴 메시지를 문자열 형태로 리턴하는 메서드

2. printStackTrace() 메서드

예외 발생이 전달되는 경로, 즉 예외가 전가된 과정을 한 눈에 확인하는 메서드

class A에는 3개의 메서드가 있고, 메서드 내부에는 NumberFormatException이 이 발생하는 코드가 포함돼있다.
3개의 메서드는 abc() => bcd() => cde() 순으로 호출하고 있으며 모든 예외는 상위 메서드로 전가(throws)하고 있음.

main()메서드 내에서 클래스 A의 객체를 생성하고 abc()메서드를 호출했을 때를 살펴보자.

내부적으로는 bcd()메서드 => cde() 메서드가 호출될 것.
마지막에 호출되는 cde()메서드에서는 NumberFormatException 예외 발생.
이를 bcd()메서드 => abc() 메서드 => main() 메서드까지 전가.

이때 catch(){}블록 안에 printStackTrace() 메서드를 호출하면 최초로 예외가 발생한 위치에서 해당 예외가 전달된 경로 확인가능.

즉, Integer.parseInt() => cde() => bcd() => abc() => main()순으로 출력

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

점수를 저장하는 score 변수에는 정수 0부터 100까지 대입가능.
이외의 값(음수 / 100보다 큰 값)을 대입했을 때는 일반 예외를 발생시키려고 함.
이때 두개의 예외 상황을 구분할 수 있도록 score값이 ㅇ므수일때를 고려한 MinusException 클래스, 100을 초과할 때를 고려한 0verException 클래스를 각각 작성.

클래스 A에는 내부에 score 값을 넘겨받아 정상적인 범위(0~100)일 때는 "정상적인 값입니다."을 출력하고, 음수 또는 100을 초과할때는 각각의 예외 객체를 생성해 던지는 기능을 가진 checkScore(int score)메서드를 정의했다.

내부에서 두 종류의 예외를 생성해 던지고 있으므로 다중 예외 처리 구문이 필요하겠지만, 여기에서는 두 예외를 모두 상위 메서드로 전가.
마지막으로 main()메서드에서는 A 클래스의 객체를 생성해 checkScore()메서드를 호출했다.

checkScore() 메서드가 2개의 예외를 전가했기 때문에 main() 메서드는 2개의 예외를 처리하거나 자바 가상 머신으로 전가해야 한다. 여기서는 다중 예외 처리구문을 이용해 예외 처리를 직접 수행.

profile
모든 건 zero 부터, 차근차근 헛둘헛둘

0개의 댓글