예외(Exception)
사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류
예외가 발생하면 프로그램은 곧바로 종료된다는 점에서 에러와 비슷하지만 예외는
예외 처리(Exception handling)를 통해 프로그램을 종료하지 않고 정상 실행 상태를 유지
예외의 종류에는 일반 예외(Exception)와 실행 예외(Runtime exception)가 있다.
일반 예외는 컴파일러 체크 예외라고도 하는데, 프로그램 실행 시 예외가 발생할 가능성이 높기 때문에
자바 소스를 컴파일하는 과정에서 해당 예외 처리 코드가 있는지 검사(예외 처리 코드가 없다면 컴파일 오류 발생)
실행 예외는 컴파일러 넌 체크 예외라고도 하는데, 실행 시 예측할 수 없이 갑자기 발생하기 때문에 컴파일하는 과정에서 예외 처리 코드가 있는지 검사하지 않는다.
- Throwable : JAVA언어의 모든 오류 및 예외의 슈퍼클래스
- Error : Throwable의 하위 클래스, 응용프로그램의 심각한 문제를 나타냄
- Exception : 응용 프로그램이 catch 할 수 있는 조건을 나타내는 Throwable 형식
- Exception은 처리 방식에 따라서 Checked Exception과 UncheckedException(Runtime Exception)으로 분류
Checked Exception과 UncheckedException(Runtime Exception)
Checked Exception | Unchecked Exception | |
처리여부 | 반드시 예외 처리 | 처리를 강제하지 않음 |
확인시점 | 컴파일 단계 | 실행단계 |
예외발생 시 트랜잭션 처리 | roll-back 하지 않음 | roll-back 함 |
일반적으로 컴파일 단계에서 명확하게 Exception 체크가 가능하면 Checked Exception,
실행과정 중 발견되는 Unchecked Exception
예외처리시 참고 및 고려사항
- Exception 무시하지 않기 : catch절에서 아무것도 하지 않는 코드 X
- java.lang.Exception 남용 금지 : 단순하게 Exception을 통째로 선언하면 자세하기 파악 힘듬
- Runtime Exception 활용 검토 : Runtime Exception을 Default 로 사용, 특별한 이유가 있으면 Checked Exception 처리
- 추상화 Layer에 맞는 Exception 던지기 : 적절한 수준으로 추상화된 Exception 정의, Service Layer에서는 비즈니스로직에 맞는 Custom Exception을 정의하여 사용
여러가지 예외
예외 구문 | 이유 |
ArithmeticException | 정수를 0으로 나눌경우 발생 |
ArrayIndexOutOfBoundsExcetion | 배열의 범위를 벗어난 index를 접근할 시 발생 |
ClassCastExcetion | 변환할 수 없는 타입으로 객체를 반환 시 발생 |
NullPointException | 존재하지 않는 레퍼런스를 참조할때 발생 |
IllegalArgumentException | 잘못된 인자를 전달 할 때 발생 |
IOException | 입출력 동작 실패 또는 인터럽트 시 발생 |
OutOfMemoryException | 메모리가 부족한 경우 발생 |
NumberFormatException | 문자열이 나타내는 숫자와 일치하지 않는 타입의 숫자로 변환시 발생 |
NullPointerException
자바 프로그램에서 가장 빈번하게 발생하는 실행 예외는 java.lang.NullPointerException 이다.
이것은 객체 참조가 없는 상태, 즉 null 값을 갖는 참조 변수로 객체 접근 연산자인 도트(.)를 사용했을 때 발생하는 실행 예외이다. 즉, 객체가 없는 상태에서 객체를 사용하려 했으니 예외가 발생하는 것이다.
package sec03.exam02;
public class NullPointerExceptionExample {
public static void main(String[] args) {
// TODO 자동 생성된 메소드 스텁
String data=null;
System.out.println(data.toString());
}
ArrayIndexOutOfBoundsException
배열에서 인덱스 범위를 초과할 경우 실행 예외인 java.lang.ArrayIndexOutOfBoundsException이 발생한다.
package sec03.exam02;
public class ArrayIndexOutOfBoundsExceptionExample {
public static void main(String[] args) {
String data1=args[0];
String date2=args[1];
System.out.println("args[0]: "+data1);
System.out.println("args[1]: "+data2);
}
}
NumberFormatException
프로그램을 개발하다 보면 문자열로 되어 있는 데이터를 숫자로 변경하는 경우가 자주 발생한다.
문자열을 숫자로 변환하는 방법은 여러 가지가 있지만 주로 Integer.parseInt(String s)와 Double.parseDouble(String s) 메소드를 가장 많이 사용한다.
Integer와 Double은 포장(Wrapper) 클래스라고 하는데, 이 클래스의 정적 메소드인 parseXXX() 메소드를 이용하면 문자열을 숫자로 변환할 수 있다.
이 메소드들은 매개값인 문자열이 숫자로 변환될 수 있다면 숫자를 리턴하지만 숫자가 변환될 수 없는 문자가 포함되어 있다면 java.lang.NumberFormatException을 발생시킨다.
package sec03.exam02;
public class NumberFormatExceptionExample {
public static void main(String[] args) {
String data1="100";
String data2="a100";
int value1 = Integer.parseInt(data1);
int value2 = Integer.parseInt(data2); //NumberFormatException 발생
int result = value1+value2;
System.out.println(data1+"+"+data2+"="+result);
}
}
ClassCastException
타입 변환(Casting)은 상위 클래스와 하위 클래스 간에 발생하고 구현 클래스와 인터페이스 간에도 발생한다. 이러한 관계가 아니라면 클래스는 다른 타입으로 변환할 수 없기 때문에 ClassCastException이 발생한다.
예를 들면 Animal이라는 추상 클래스를 상속하는 Dog와 Cat이라는 클래스가 있고, RemoteControl이라는 인터페이스를 구현한 Television와 Audio라는 객체가 있다고 가정해보자.
Animal animal = new Dog(); //자동 타입 변환
Dog dog = (Dog)animal; //강제 타입 변환
RemoteControl rc = new Television();
Television tv = (Television)rc;
위 코드들은 올바른 타입 변환을 보여주지만 다음과 같이 타입 변환을 하면 ClassCastException이 발생한다.(대입된 객체가 아닌 다른 클래스 타입으로 타입 변환했기 때문)
Animal animal = new Dog();
Cat cat = (Cat)animal;
RemoteControl rc = new Television();
Audio audio = (Audio)rc;
ClassCastException을 발생시키지 않으려면 타입 변환 전에 변환이 가능한지 instanceof 연산자로 확인하는 것이 좋다. instanceof 연산의 결과가 true이면 좌항 객체를 우항 타입으로 변환이 가능하다는 의미이다.
Animal animal = new Dog();
if(animal instanceof Dog){
Dog dog = (Dog) animal;
}
else if(animal instanceof Cat){
Cat cat = (Cat) animal;
}
RemoteControl rc = new Audio();
if(rc instanceof Television){
Television tv = (Television)rc;
}
else if(rc instanceof Audio(){
Audio audio = (Audio)rc;
}
package sec03.exam02;
public class ClassCastExceptionExample {
public static void main(String[] args) {
Dog dog = new Dog();
changeDog(dog);
Cat cat = new Cat();
changeDog(cat);
}
public static void changeDog(Animal animal) {
//if(animal instanceof Dog){
Dog dog = (Dog)animal; //ClassCastException 발생 가능
//}
}
}
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}
try, catch
예외가 발생했을때 우리는 try ... catch ... finally 라는 키워드로 예외를 처리할 수 있거나 메소드를 호출한 곳으로 던질 수 있습니다. 한 가지 중요한 점은 자바에서 모든 예외는 Exception이라는 클래스를 상속
try{
//예외가 발생될만한 코드
}catch(FileNotFoundException e){ //FileNotFoundException이 발생했다면
}catch(IOException e){ //IOException이 발생했다면
}catch(Exception e){ //Exception이 발생했다면
}finally{
///어떤 예외가 발생하던 말던 무조건 실행
}
try 블록 : 이 블록에서 예외가 발생할만한 코드가 쓰여집니다.
catch (예외 종류) 블록 : 이 부분에서 예외가 발생되었을때 처리하는 동작을 명시합니다. catch블록은 여러 개가 있을 수 있습니다. 맨 처음 catch 블록에서 잡히지 않는 예외라면 다음 catch의 예외를 검사합니다. 이때 상속관계에 있는 예외 중 부모가 위의 catch, 그리고 그 자식 예외 클래스가 아래의 catch로 놓일 순 없습니다. 예를 들어 아래와 같이말이죠.
try{
//.. 중략 ..//
} catch (Exception e){
//컴파일 오류 발생
} catch (IOException e){
}
Exception 클래스는 모든 예외의 부모이기 때문에 Exception을 IOException보다 위에서 처리할 수는 없다는 뜻입니다. 왜냐면 IOException의 catch블록은 도달할 수 없는 코드이기 때문이죠.
finally 블록 : 여기서는 예외가 발생하건 발생하지 않건 공통으로 수행되어야할 코드가 쓰여집니다. 임시 파일의 삭제 등 뒷정리 코드가 쓰입니다.
public static void main(String[] ar){
int a,b;
a=10;
b=0;
try {
int c=a/b;
System.out.println(c); //예외발생으로 실행 불가한 코드
}catch(ArithmeticException e) {
System.out.println("ArithmeticException 발생");
System.out.println("0으로 나눌 수는 없습니다");
e.printStackTrace();
}finally {
System.out.println("finally 실행");
}
}
printStackTrace()라는 메소드는 어느 부분에서 예외가 발생했는지 알려주는 추적로그를 보여줍니다. Exception이 발생했을때 기본 동작이죠. 결과는 아래와 같은 것을 알 수 있습니다.
ArithmeticException 발생
java.lang.ArithmeticException: / by zero
at aa.Main.main(Main.java:11) //Main.java에서 11번째 줄에서 발생했다는 printStackTrace
finally 실행
throws
아까전에 예외를 그냥 던질 수 있다고 했죠? 그 의미가 어떤 의미냐면 예외를 여기서 처리하지 않을테니 나를 불러다가 쓰는 녀석에게 에러 처리를 전가하겠다는 의미이며 코드를 짜는 사람이 이 선언부를 보고 어떤 예외가 발생할 수 있는지도 알게 해줍니다. 어떤 뜻인지 모르겠다구요? 아래의 코드를 통해서 알아보도록 합니다.
public static void divide(int a,int b) throws ArithmeticException {
if(b==0) throw new ArithmeticException("0으로 나눌 수는 없다니까?");
int c=a/b;
System.out.println(c);
}
public static void main(String[] ar){
int a=10;
int b=0;
divide(a,b);
}
divide()메소드는 a와 b를 나눈 후에 출력하는 역할을 하는데, 이 나누기 부분에서 우리는 예외가 발생할 수 있음을 알았습니다. 그래서 try, catch로 예외 처리를 해야하지만, divide()를 호출하는 부분에서 처리하기를 원합니다. 왜냐면 divide()를 호출한 곳에서 예외가 발생한 다음의 처리를 divide() 메소드가 정하지 않기 때문입니다. 예를 들어 main메소드에서는 예외가 발생하면 다시 divide()를 호출하거나, 프로그램을 끝내거나, b의 값을 다시 입력받거나 해야하기 때문이고, divide()메소드가 그 결정을 할 수 없다는 의미입니다. 그래서 throws ArithmeticException을 divide를 호출한 main에다가 던지는 것(throw)입니다. 여기서 예외를 던지는 방법은 아래와 같습니다.
(아, 참고로 Exception 생성자 중에서 메시지를 받는 생성자가 있는데, 메시지를 보려면 getMessage()메소드를 이용할 수 있습니다. 아래에서 그 메소드를 사용합니다.)
throw 예외객체
ex) throw new Exception("예외 발생!")
예외를 발생시키는 키워드는 throw입니다. 이때 main은 그 예외를 처리하기 위해 try, catch블록을 쓰면 됩니다. 아래처럼 말이죠.
try {
divide(a,b);
}catch(ArithmeticException e) {
e.getMessage();
e.printStackTrace();
}
throws 키워드로 처리되어야할 예외가 여러개가 존재한다면 쉼표로 끊어서 예외를 넘겨줄 수 있습니다. 그 결과는 아래와 같다.
java.lang.ArithmeticException: 0으로 나눌 수는 없다니까?
at aa.Main.divide(Main.java:8)
at aa.Main.main(Main.java:17)
'Java > Java' 카테고리의 다른 글
[Java] 스레드(thread) (0) | 2022.12.08 |
---|---|
[Java] 타입 변환과 다형성 (0) | 2022.12.06 |
[Java] 인터페이스(Interface) (0) | 2022.12.05 |
[Java] 추상 클래스, 메소드 (0) | 2022.12.02 |
[Java]타입 변환과 다형성 (0) | 2022.11.30 |
댓글