예외란 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류를 말한다.
- 공통점 : 코드나 조작이 잘못된 경우 프로그램은 곧바로 종료된다.
- 차이점 : 에러는 곧바로 종료되지만, 예외는 예외 처리를 통해 프로그램을 종료하지 않고 정상 실행 상태가 유지되도록 할 수 있다.
예외는 2 가지 종류가 있다.
- 일반 예외(Exception)
- 실행 예외(Runtime Exception)
일반 예외는 컴파일러 체크 예외라고도 한다.
- 자바 소스를 컴파일하는 과정에서 예외 처리 코드가 필요한지 검사하기 때문이다.
- 따라서 만약 예외 처리 코드가 없다면 컴파일 오류가 발생한다.
실행 예외는 컴파일하는 과정에서 예외 처리 코드를 검사하지 않는 예외를 말한다.
일반 예외는 Exception을 상속받지만 RuntimeException을 상속받지 않는 클래스들이다.
실행 예외는 RuntimeException을 상속받은 클래스들이다.
실행 예외는 자바 컴파일러가 체크를 하지 않기 때문에 오로지 개발자의 경험에 의해서 예외 처리 코드를 삽입해야 한다.
public class NullPointerExceptionExample {
public static void main(String... args) {
String data = null;
System.out.println(data.toString());
}
}
public class ArrayIndexOutOfBoundsExceptionExample {
public static void main(String... args) { //인덱스 크기를 지정 안했는데
String data1 = args[0]; //인덱스 0번을 지정하면 -> 에러 발생 !
String data2 = args[1];
System.out.println("args[0]: " + data1);
System.out.println("args[1]: " + data2);
}
}
java ArrayIndexOutOfBoundsExceptionExample 값1 값2
실행반환 타입 | 메소드명(매개변수) | 설명 |
---|---|---|
int | Integer.parseInt(String s) | 주어진 문자열을 정수로 변환해서 리턴 |
double | Double.parseDouble(String s) | 주어진 문자열을 실수로 변환해서 리턴 |
public class NumberFormatExceptionExample {
public static void main(String... args) {
String data1 = "100";
String data2 = "1a00"; //숫자 외에 다른 문자가 포함되어 있으면
int value1= Integer.parseInt(data1);
int value2= Integer.parseInt(data2); //에러 발생 !
int result = value1 + value2;
System.out.println("value1 + value2: " + result);
}
}
예를 들어, 실체 클래스는 Dog, Cat이고 추상 클래스는 Animal이라고 하자.
올바른 타입 변환은 다음과 같다.
Animal animal = new Dog();
Dog dog = (Dog) animal; //같은 클래스로 타입 변환 -> O
그러나 다음과 같이 타입변환을 대입된 객체가 아닌 다른 클래스 타입으로 변환하면 ClassCastException이 발생한다.
Animal animal = new Dog();
Cat cat = (Cat) animal; //다른 클래스로 타입 변환 -> X
instanceof
연산자 사용true
이면 → 좌항 객체를 우항 타입으로 변환이 가능하다 !Animal animal new Dog();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
}
프로그램에서 예외가 발생했을 경우 프로그램의 갑작스러운 종료를 막고, 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드라고 한다.
try-catch-finally
블록을 이용한다.이 블록은 생성자 내부와 메소드 내부에서 작성되어 일반 예외와 실행 예외가 발생할 경우 예외 처리를 할 수 있도록 해준다.
try
: 예외 발생 가능 코드가 위치한다.catch
: try블록에서 예외가 발생하면 즉시 멈추고 catch 블록이 실행된다.finally
: 생략 가능하다. 예외 발생 여부와 상관없이 항상 실행할 내용이 있을 경우에만 작성한다.public class TryCatchFinallyRuntimeExceptionExample {
public static void main(String... args) {
String data1 = null;
String data2 = null;
/* try-catch 구조 */
try{
data1 = args[0];
data2 = args[1];
} catch (ArrayIndexOutOfBoundsException e ){
System.out.println("실행 매개값의 수가 부족합니다.");
System.out.println("[실행 방법]");
System.out.println("java TryCatchFinallyRuntimeExceptionExample num1 num2");
return ;
}
/* try-catch-finally 구조*/
try {
int value1 = Integer.parseInt(data1);
int value2 = Integer.parseInt(data2);
} catch (NumberFormatException e) {
System.out.println("숫자로 변환할 수 없습니다.");
} finally {
System.out.println("다시 실행하세요.");
}
}
}
try 블록 내부에서 발생하는 예외별로 예외 처리 코드를 다르게 처리하는 방법 ?
☞ 다중 catch를 사용한다. 여기서 catch 순서가 중요하다 !!
⚠️ 다중 catch 블록을 작성할 때 주의할 점
= 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 한다.
상위 예외 클래스에서 하위 클래스 순으로 나열하면 다음과 같다.
Exception
> ClassNotFoundException
InterruptException
RuntimeException
> ...
하나의 catch 블록에서 여러 개이 예외를 처리하는 방법 ?
☞ 멀티 catch를 사용한다. 예외 처리는 | 로 연결한다.
try {
... // 예외 발생
} catch (예외1 | 예외2 예외처리변수) {
...
} finally{
...
}
try-with-resource를 사용하여 리소스 객체의 close() 메소드를 호출해서 안전하게 리소스를 닫아준다. (Java7 이후부터 가능)
리소스 객체란 각종 입출력 스트림, 서버 소켓, 소켓, 각종 채널 등을 말한다.
FileInputStream.java
public class FileInputStream implements AutoCloseable{
private String file;
public FileInputStream(String file) {
this.file = file;
}
public void read() {
System.out.println(file + "을 읽습니다.");
}
@Override //파일 닫기 재정의
public void close() throws Exception { //파일 닫기
System.out.println(file + "을 닫습니다.");
}
}
public class TryWithResourceExample {
public static void main(String... args) {
try (FileInputStream fis = new FileInputStream("file.txt")){
fis.read(); //파일 읽기
throw new Exception();
} catch (Exception e) {
System.out.println("예외 처리 코드가 실행되었습니다.");
}
}
}
메소드 내부에서 예외가 발생할 때 try-cath-finally 블록으로 예외를 처리하는 것이 기본이지만, 경우에 따라서 메소드를 호출한 곳으로 예외를 떠넘길 수도 있다.
(별로 좋지 않음)
throws
키워드 사용public void method1 () {
try {
method2();
} catch (ClassNotFoundException e) {
//예외 처리 코드
}
}
public void method2() throws ClassNotException {
Class clazz = Class.forName("java.lang.String2");
}
🚩 예외를 떠넘기는 것은 별로 좋지 못한 코드이다. 프로그램 사용자는 프로그램이 알 수 없는 예외 내용을 출력하고 종료되는 것을 좋아하지 않는다. 따라서 예외가 발생한다면 try-catch-finally 블록으로 최종 처리하는 것이 바람직하다 !!