[Java] 10-1 예외 클래스

예외(Exception)란 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류를 말한다.

예외가 발생하면 프로그램은 곧바로 종료된다는 점에서 에러와 비슷하지만 예외는 예외 처리(Exception handling)를 통해 프로그램을 종료하지 않고 정상 실행 상태가 유지되도록 할 수 있다.

자바는 예외가 발생할 가능성이 높은 코드를 컴파일할 때 예외 처리 유무를 확인한다.

만약 예외 처리 코드가 없다면 컴파일이 되지 않는다. 하지만 모든 예외에 대해서 예외 처리 유무를 확인하는 것은 아니고, 이것을 이해하기 위해선 예외의 종류를 알아야 한다.

철자와 문법 오류는 컴퓨터나 코딩 프로그램에서 알아서 걸러낼 수 있다. 특히 자바와 같이 실행되기 전에 기계에 더 친숙한 언어(0,1)로 번역(컴파일)되는 언어들은 문법 오류를 컴파일 과정에서부터 차단한다.(실행 과정에서 이런 오류가 나는 일은 없음) 이런 것을 바로 컴파일 오류라고 하고 컴퓨터가 실행 전에 걸러내지 못하는 실행 과정에서 발생한 오류를 런타임 오류라고 한다.

프로그래머의 논리적 결함에 의해 발생하는 오류를 논리 오류라고 한다. 이런 오류들은 전적으로 코드에 달려있기 때문에 프로그래밍하는 과정에서 방지할 수 있다. 하지만 프로그래머가 제어할 수 없는 돌발상황이 발생할 수 있기 때문에 프로그래밍 언어들은 일반적으로 이런 돌발상황을 대처하기 위해 예외 처리(Exception Handling)이라는 장치를 마련해놓았다.

예외와 예외 클래스

예외의 종류에는 일반 예외(Exception)실행 예외(Runtime exception)이 있다.

일반 예외는 컴파일러 체크 예외라고도 하는데, 프로그램 실행 시 예외가 발생할 가능성이 높기 때문에 자바 소스를 컴파일하는 과정에서 해당 예외 처리 코드가 있는지 검사한다.(예외 처리 코드가 없다면 컴파일 오류 발생)

실행 예외는 컴파일러 넌 체크 예외라고도 하는데, 실행 시 예측할 수 없이 갑자기 발생하기 때문에 컴파일하는 과정에서 예외 처리 코드가 있는지 검사하지 않는다.

자바에서는 예외를 클래스로 관리한다. JVM은 프로그램을 실행하는 도중에 예외가 발생하면 해당 예외 클래스로 객체를 생성한다. 그리고 나서 예외 처리 코드에서 예외 객체를 이용할 수 있도록 해준다. 모든 예외 클래스는 다음과 같이 java.lang.Exception 클래스를 상속받는다.

일반 예외와 실행 예외 클래스는 RuntimeException 클래스를 기준으로 구별된다.

RuntimeException의 하위 클래스가 아니면 일반 예외 클래스이고, 하위 클래스이면 실행 예외 클래스이다.(클래스 상속 관계에서 부모나 조상에 RuntimeException이 있다면 실행 예외 클래스)

실행 예외

실행 예외는 자바 컴파일러가 체크하지 않기 때문에 오로지 개발자의 경험에 의해서 예외 처리 코드를 작성해야 한다. 만약 개발자가 실행 예외에 대해 예외 처리 코드를 넣지 않았을 경우, 해당 예외가 발생하면 프로그램은 곧바로 종료된다.

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());

	}

}

Exception in thread "main" java.lang.NullPointerException
at chap01/sec03.exam02.NullPointerExceptionExample.main(NullPointerExceptionExample.java:9)

data변수는 null 값을 가지고 있기 때문에 String 객체를 참조하고 있지 않다. 그런데 9라인에서 String 객체의 toString()메소드를 호출하였기 때문에 NullPointerException이 발생한다.

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);
	}
}

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at chap01/sec03.exam02.ArrayIndexOutOfBoundsExceptionExample.main(ArrayIndexOutOfBoundsExceptionExample.java:7)

위 예제에서 예외가 발생한 이유는 2개의 실행 매개값을 주지 않았기 때문에 args[0], args[1]과 같이 인덱스를 사용할 수 없기 때문이다.

이클립스에서 Run - Run Configurations 메뉴를 선택한 후, Arguments 탭의 Program arguments 입력란에 2개의 매개값을 입력하고 실행하면 예외가 발생하지 않는다.

args[0]: 배열
args[1]: 인덱스

예제를 다음과 같이 배열값을 읽기 전에 배열의 길이를 먼저 조사하면 ArrayIndexOutOfBoundsException이 발생하지 않는 좋은 프로그램이 된다.(실행 매개값이 없거나 부족할 경우 조건문을 이용해서 사용자에게 알려줌)

package sec03.exam02;

public class ArrayIndexOutOfBoundsExceptionExample {

	public static void main(String[] args) {
		
		if(args.length==2) {
			String data1=args[0];
			String data2=args[1];
			System.out.println("args[0]: "+data1);
			System.out.println("args[1]: "+data2);
		}
		
		else System.out.println("두 개의 실행 매개값이 필요합니다.");
		
	}
}

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);
	}
}

Exception in thread "main" java.lang.NumberFormatException: For input string: "a100"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at chap01/sec03.exam02.NumberFormatExceptionExample.main(NumberFormatExceptionExample.java:10)

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{}

Exception in thread "main" java.lang.ClassCastException: class sec03.exam02.Cat cannot be cast to class sec03.exam02.Dog (sec03.exam02.Cat and sec03.exam02.Dog are in module chap01 of loader 'app')
at chap01/sec03.exam02.ClassCastExceptionExample.changeDog(ClassCastExceptionExample.java:16)
at chap01/sec03.exam02.ClassCastExceptionExample.main(ClassCastExceptionExample.java:10)

예제를 실행하면 ClassCastException이 발생하는데 그 이유는 Cat 객체를 매개값으로 주어서 Dog 타입으로 변환할 수 없기 때문이다.

따라서 잘못된 매개값이 들어오는 것을 방지하기 위해 instanceof 연산자로 타입 체크를 먼저 하는 것이 좋다.

좋은 웹페이지 즐겨찾기