✔️ 사용자 실수와 같은 외적인 요인에 의해서 발생하는 예외
존재하지 않는 파일 이름을 입력했다던가 (FileNotFoundException)
실수로 클래스 이름을 잘못 적었다던가(ClassNotFoundException)
입력한 데이터 형식이 잘못된 경우(DataFormatException)
✔️ 주로 프로그래머의 실수에 의해서 발생할 수 있는 예외
배열의 범위를 벗어난다던가 (ArrayIndexOutOfBoundsException)
값이 null인 참조변수의 멤버를 호출하려 했다던가 (NullPointerException)
클래스간의 형변환을 잘못했다던가 (ClassCastException)
정수를 0으로 나누려 했다던가(ArithmeticException)
예외처리란?
프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것, 목적은 프로그램의 비정상적인 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것!
예외를 처리하기 위해서
try {
//예외가 발생할 가능성이 잇는 문장들을 넣는다.
try{} catch(Exception1 e) {}
//try안에 다시 try -catch문이 들어오는 것도 가능하다.
} catch(Exception1 e1) {
// Exception1이 발생했을 경우 이를 처리하기 위한 문장
//catch안에 다시 try-catch문이 들어갈 수 있다.
try {} catch(Exception1 e2) {}
//내용이 없으면 {} 빈 괄호
// catch블러 내 참조변수 e2는 e1와 이름이 겹치지 않도록
} catch (Exception2 e1) {} // catch블럭 내 참조변수는 지역변수라 생각하기
▶ try 블럭 내에서 예외가 발생하는 경우
1. 발생한 예외와 일치하는 catch블럭 있는지 확인
2. 있는 경우 그 catch블럭 내에 있는 문장들을 수행하고 try-catch문 빠져나가서 그 다음 문장 수행 , 만일 일치하는 catch문이 없는 경우는 예외는 처리 되지 못한다.
▶ try 블럭 내에서 예외가 발생하지 않는 경우
1. catch블럭을 거치지 않고 try-catch문 빠져나가기
class ExceptionEx5 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0);
System.out.println(4);//실행되지 않음
}catch(ArithmeticException ae) {
System.out.println(5);
}
System.out.println(6);
}
}
모든 예외 클래스는 Exception 클래스의 자손이므로 catch블럭의
괄호안데 Exception e넣으면 모든 종류의 에러가 발생하더라고 이 catch블럭에서 처리된다.
class ExceptionEx5 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0);
System.out.println(4);//실행되지 않음
}catch(Exception ae) {
System.out.println(5);
}
System.out.println(6);
}
}
class ExceptionEx5 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0);
System.out.println(4);//실행되지 않음
}catch(ArithmeticException ae) {
if(ae instanceof ArithmeticException )
System.out.println("true");
System.out.println(5);
}catch (Exception ae) {
System.out.println("Exception");
}
System.out.println(6);
}
}
👆 위 예시에서 ArithmeticException이 발생하여 첫번째 catch문이 실행되었기 때문에 두번째 catch문은 실행되지 않고 try-catch문을 빠져나오게 된다. 그러나 만약에 ArithmeticException이 아니 다른 Exception이었다면 첫번째 catch문이 아닌 두번째 catch문을 실행한 후 try-catch문을 빠져나왔을 것이다.
예외가 발생했을 때 생성되는 예외클래스의 인스턴스에 담겨져 있는 정보들은
getMessage()와 printStackTrac()를 통해서 접근할 수 있다.
이 메서드는 catch블럭 괄호 안에 있는 선언된 참조변수를 통해서 이 인스턴스에 접근할 수 있다.
▶ printStackTrace()
예외발생 당시의 호출스텍에 있었던 메서드의 정보와 예외메시지를 화면에 출력한다.
▶ getMessage()
발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
class ExceptionEX8 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0);
System.out.println(4);
}catch(ArithmeticException e) {
e.printStackTrace();
System.out.println("예외메세지: "+e.getMessage());
}
System.out.println(6);
}
}
여러 catch블럭을 '|'기호를 이용해서 , 하나의 catch블럭으로 합칠 수 있다.
try{
} catch (ExceptionA e) {
e.printStackTrace();
} catch (ExceptionB e2) {
e2.printStackTrace();
}
를 멀티 catch블럭으로 바꾸기
try{
} catch (ExceptionA | ExceptionB e) {
e.printStackTrace();
}
근데 만약에 ExceptionA(Parent)와 ExceptionB(Child)사이 관계가 상속관계라면
컴파일 에러가 발생한다.
당연한 말이다. 그냥 부모 클래스를 적는거랑 같은 말이기 때문이다.
멀티 catch는 하나의 catch블럭으로 여러가지 에외를 처리하기 때문에 catch블럭은 어떤 예외가 발생했는지 알 수 없다. 따라서 catch블럭 ()에 선언된 참조변수로 사용할 수 있는 멤버는 블럭 내 선언된 에외들의 조상클래스에 있는 멤버만 사용할 수 있다. 그렇지만 어떤 예외가 발생했는지 확인하는 intanceof로 호출하여 사용하였다면 그 예외에 있는 멤버를 사용할 수 있다.
또한 멀티 catch블럭에 선언된 참조변수 e는 상수이기 때문에 값을 변경할 수 있다.
키워드 throw를 사용하여 프로그래머가 고의로 예외를 발생시킬 수 있다.
- 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
Exception e = new Exception("고의로 발생시켰음");- 키워드 throw를 이용해서 예뢰를 발생시킨다
throw e;
class ExceptionEx9 {
public static void main(String[] args) {
try {
Exception e = new Exception("고의로 발생시켰음");
throw e;
} catch(Exception e) {
System.out.println("에러 메시지: "+e.getMessage());
e.printStackTrace();
}
System.out.println("프로그램이 정상 종료되었음.");
}
}
class ExceptionEx9 {
public static void main(String[] args) {
throw new Exception();
}
}
👆 일부러 발생시킨 에러를 해결하지 않으면 이렇게 컴파일조차 되지 않는다.
class ExceptionEx9 {
public static void main(String[] args) {
throw new RuntimeException();
}
}
👆 일부러 RuntimeException()을 발생시키고 예외처리를 하지 않는 상황이다.
하지만 컴파일 에러는 발생하지 않고 실행하면 위의 실행결과처럼 RuntimeException이 발생하여 비정상정으로 종료된다.
중요한 것은 컴파일은 성공적으로 되었다는 것!
앞서 배웠지만 RuntimeException와 그 자손은 프로그래머가 실수로 발생시키는 것들이기 때문에 예외처리를 강제하지 않는다.
만약에 예외처리를 강제했다면
RuntimeException이 속하는 예외가 발생할 가능성이 있는 코드 모든 곳 예를 들어 배열이 있는 것이다 참조변수가 사용되는 곳 모든 곳에 예외처리를 해줘야했을 것이다.
컴파일러가 예외처리를 확인하지 않는 RuntimeException클래스들은 unchecked예외라고 부르고, 예외처리를 확인하는 Exception클래스들은 checked예외라고 부른다.
예외를 처리하는 방법
① try-catch문을 사용하는 것
② 예외를 메서드에 선언하는 것
두번째 예외를 메서드에 선언하려면
메서드의 선언부에 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주기만 하면 된다. 예외가 여러개인 경우네는 쉼표로 구분한다.
void method() throws Exception1, Exception2, ... ExceptionN {
//메서드의 내용
}
일부러 예외를 발생시키는 throw와 메서드의 선언부에 예외를 선언하는 throws를 잘 구분하자.
만일 선언부애 모든 예외의 최고 조상인 Exception클래스를 메서드에 선언하면
이 메서드의 모든 종류의 예외가 발생할 가능성이 있다는 뜻
이 말은 이 클래스의 자손타입의 예외까지도 발생할 수 있다는 것을 의미한다.
메서드의 선언부에 예외를 선언한다는 것
= 이 메서드를 사용하기 위해서는 이 예외가 발생할 가능성이 있으니 어디선가는 처리를 해줘야 한다를 의미한다.
이는 Java API문서에 있는 Object클래스의 wait 메서드인데
선언부에 throws InterruptedException이 발생하니 이 메서드를 호출하고자 하는 메서드에서는 이 예외를 처리해줄 것을 말하고 있다.
wait메서드의 Throws를 보면 이 메서드에 발생하는 3종류의 예외를 볼 수 있는데
메서드 선언부에는 InterruptedException만 선언되어져 있다.
그 이유는 InterruptedException은 Exception의 자손이므로 무조건 예외처리를 해줘야(cheched예외) 하지만 나머지 2개는 RuntimeException클래스의 자손이므로 예외처리를 해주지 않아도 된다.(unchecked예외)