코딩 이야기

JAVA 예외 처리 본문

JAVA

JAVA 예외 처리

별메아리 2023. 2. 22. 10:30
728x90

예외 클래스

예외란 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류를 말합니다. 예외가 발생되면 프로그램은 곧바로 종료된다는 점에서는 에러와 비슷합니다. 그러나 예외는 예외 처리를 통해 프로그램을 종료하지 않고 정상 실행 상태가 유지되도록 할 수 있습니다.

 

자바는 예외가 발생할 가능성이 높은 코드를 컴파일 할 때 예외 처리 유무를 확인합니다. 만약 예외 처리 코드가 없다면 컴파일이 되지 않습니다. 하지만 모든 예외에 대해서 예외 처리 유무를 확인하는 것은 아닙니다. 이것을 이해하려면 예외의  종류부터 알아야 합니다.

 

예외와 예외 클래스

예외는 두 가지 종류가 있습니다. 하나는 일반 예외이고, 다른 하나는 실행 예외입니다.

 

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

 

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

 

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

일반 예외와  실행 예외 클래스는 RuntimeException 클래스를 기준으로 구별합니다. RuntimeException의 하위 클래스가 아니면 일반 예외 클래스이고, 하위 클래스이면 실행 예외 클래스입니다. 클래스 상속 관계에서 부모에 RuntimeException이 있다면 실행 예외 클래스입니다.

실행 예외

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

 

NullPointerException

자바 프로그램에서 가장 빈번하게 발생하는 실행 예외는 java.lang.NullPointerException입니다. 이것은 객체 참조가 없는 상태, 즉 null 값을 갖는 참조 변수로 객체 접근 연산자인 도트(,)를 사용했을 때 발생합니다. 객체가 없는 상태에서 객체를 사용하려 했으니 예외가 발생하는 것입니다.

 

NullPointerException

package ch10;

public class NullPointerExceptionExample {
	public static void main(String[] args) {
		String data = null;
		System.out.println(data.toString());
	}
}

5라인에서 data 변수는 null 값을 가지고 있기 때문에 String 객체를 참조하고 있지 않습니다. 하지만 6라인에서 String 객체의 to String() 메소드를 호출하고 있습니다. 여기서 NullPointerException이 발생합니다.

 

실행결과

Exception in thread "main" java.lang.NullPointerException
	at ch10.NullPointerExceptionExample.main(NullPointerExceptionExample.java:6)

프로그램에서 예외가 발생하면 예외 메시지가 Console 뷰에 출력되면서 프로그램이 종료됩니다.

Console 뷰에 출력되는 내용에는 어떤 예외가 어떤소스의 몇 번째 코드에서 발생했는지에 대한 정보가 들어있습니다.

 

ArrayIndexOutOfBoundsException

배열에서 인덱스 범위를 초과할 경우 실행 예외인 java.lang.ArrayIndexOutOfBoundsException이 발생합니다. 예를 들어 길이가 3인 int[]arr = new int[3] 배열을 선언했다면, 배열 항목을 지정하기위해 arr[0]~arr[2]를 사용할 수 있습니다. 하지만 arr[3]을 사용하면 인덱스 범위를 초과했기 때문에 arrayindexOutOfBoundException이 발생합니다.

 

arrayindexOutOfBoundException

package ch10;

public class arrayindexOutOfBoundException {
	public static void main(String[] args) {
		String data1 = args[0];
		String data2 = args[1];
		
		System.out.println("args[0]: " + data1);
		System.out.println("args[1]: " + data2);
	}
}

위 예제를 실행 하면 5라인에서 ArrayIndexOutOfBoundsException이 발생합니다. 2개의 실행 매개값을 주지 않았기 때문에 arg[0], arg[1]과 같이 인덱스를 사용할 수 있습니다.

 

실행결과

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
	at ch10.arrayindexOutOfBoundException.main(arrayindexOutOfBoundException.java:5)

 그외 다른 실행예외

 

NumberFormatException

package ch10;

public class NumberFormatException {
	public static void main(String[] args) {
		String data1 = "100";
		String data2 = "a100";
		
		int value1 = Integer.parseInt(data1);
		int value2 = Integer.parseInt(data2);
		
		int result = value1 + value2;
		
		System.out.println(result);
	}
}

실행결과

Exception in thread "main" java.lang.NumberFormatException: For input string: "a100"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at ch10.NumberFormatException.main(NumberFormatException.java:9)

 

ClassCastException

package ch10;

public class ClassCastException {
 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: ch10.Cat cannot be cast to ch10.Dog
	at ch10.ClassCastException.changeDog(ClassCastException.java:14)
	at ch10.ClassCastException.main(ClassCastException.java:9)

예외 처리 코드

자바 컴파일러는 소스 파일을 컴파일할 때 일반 예외가 발생할 가능성이 있는 코드를 발견하면 컴파일 에러를 발생시켜 개발자가 강제적으로 예외 처리 코드를 작성하도록 요구합니다. 그러나 실행 예외는 컴파일러가 체크해주지 않기 때문에 개발자의 경험을 바탕으로 예외 처리 코드를 작성해야 합니다.

 

try-catch-finally 블록은 생성자 내부와 메소드 내부에서 작성되어 일반 예외와 실행 예외가 발생할 경우 예외 처리를 할 수 있도록 해줍니다. try-catch-finally블록은 다음과 같이 작성합니다.

정상 실행되었을 경우
try {

//예외 발생


} catch(예외클래스 e) {


예외 처리

} finally {

항상 실행;

}
  1. try 블록에는 예외 발생 코드가 위치합니다.
  2. try 블록의 코드가 예외 발생 없이 정상 실행되면 catch 블록의 코드는 실행되지 않고 finally블록의 코드를 실행 합니다. 만약 try 블록의 코드에서 예외가 발생하면 즉시 실행을 멈추고 catch 블록으로 이동하여 예외 처리 코드를 실행합니다. 그리고 finally블록의 코드를 실행합니다.
  3. finally 블록은 생략 가능합니다. 예외 발생 여부와 상관없이 항상 실행할 내용이 있을 경우에만 finally 블록을 작성해주면 됩니다. 심지어 try 블록과 catch 블록에서 return문을 사용하더라고 finally 블록은 항상 실행됩니다.

일반 예외 처리

package ch10;

public class TryCatchFinallyExample {
	public static void main(String[] args) {
		try {
			Class clazz = Class.forName("java.lang.String2");
		} catch(ClassNotFoundException e) {
			System.out.println("클래스가 존재하지 않습니다.");
		}
	}
}

실행 예외 처리

package ch10.exam01;

public class TryCatchFinallyRunTimeExceptionExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String data1 = null;
		String data2 = null;
		try {
			data1 = args[0];
			data2 = args[1];
		} catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("실행 매개값의 수가 부족합니다.");
			return;
		}
		
		try {
			int value1 = Integer.parseInt(data1);
			int value2 = Integer.parseInt(data2);
			int result = value1 + value2;
			System.out.println(data1 + "+" + data2 + "=" + result);
		} catch(NumberFormatException e) {
			System.out.println("숫자로 변환할 수 없습니다.");
		} finally {
			System.out.println("다시 실행하세요");
		}
	}
}

다중 catch

예외별로 예외 처리 코드를 다르게 하러면 다중 catch블록을 작성하면 됩니다. cat 블록의 예외 클래스 타입은 try 블록에서 발생된 예외의 종류를 말하는데,try 블록에서 해당 타입의 예외가 발생하면 catch블록을 실행하도록 되어 있습니다.

catch 블록이 여러 개라 할지라도 단 하나의 catch 블록만 실행됩니다. 그 이유는 try 블록에서  동시 다발적으로 예외가 발생하지 않고, 하나의 예외가 발생하면 즉시 실행을 멈추고 해당 catch 블록으로 이동하기 때문입니다.

 

package ch10.exam01;

public class CatchByExceptionKindExample {
	public static void main(String[] args) {
		try {
			String data1 = args[0];
			String data2 = args[1];
			int value1 = Integer.parseInt(data1);
			int value2 = Integer.parseInt(data2);
			int result = value1 + value2;
			System.out.println(data1 + "+" + data2 + "=" + result);
		}	catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("실행 매개값의 수가 부족합니다.");
		} 	catch(NumberFormatException e) {
			System.out.println("숫자로 변환할 수 없습니다.");
		}	finally {
			System.out.println("다시 실행하세요");
		}
	}
}

catch 순서

다중 catch 블록을 작성할 때 주의할점은 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 한다는 점입니다.

try 블록에서 예외가 발생했을 때, 예외를 처리해줄 catch 블록은 위에서 부터 차례대로 검색됩니다. 만약 상위 예외 클래스의 catch 블록이 위에 있다면, 하위 예외 클래스의 catch블록은 실행되지 않습니다. 왜냐하면 하위 예외는 상위 예외를 상속했기 때문에 상위 예외 타입도 되기 때문입니다.

 

package ch10.exam01;

public class CatchOrderExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			String data1 = args[0];
			String data2 = args[1];
			int value1 = Integer.parseInt(data1);
			int value2 = Integer.parseInt(data2);
			int result = value1 + value2;
			System.out.println(data1+ "+" + data2 + "=" + result);
		}   catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("실행 매개값의 수가 부족합니다.");
		}	catch(Exception e) {
			System.out.println("실행에 문제가 있습니다.");
		}	finally {
			System.out.println("다시 실행하세요");
		}
	}

}

예외 떠넘기기

메소드 내부에서 예외가 발생할 수 있는 코드를 작성할 때 try-catch 블록으로 예외를 처리하는 것이 기본이지만, 경우에 따라서는 메소드를 호출하는 곳으로 예외를 떠넘길 수도 있습니다. 이때 사용하는 키워드가 throws입니다. throws 키워드는 메소드 선언부 끝에 작성되어 메소드에서 처리하지 않은 예외를 호출한 곳으로 떠넘기는 역활을 합니다. throws 키워드 뒤에는 떠넘길 예외 클래스를 쉼표로 구분해서 나열해주면 됩니다.

리턴타입 메소드이름(매개변수,...) throws 예외클래스1, 예외클래스2, ... {
}

발생할 수 있는 예외의 종류별로 throws 뒤에 나열하는 것이 일반적이지만, 다음과 같이 thorows Exception만으로 모든 예외를 간단히 떠넘길 수도 있습니다.

리턴타입 메소드이름(매개변수,...) throws Excetion {
}

throws 키워드가 붙어 있는 메소드는 반드시 try 블록 내에서 호출되어야 합니다. 그리고 catch 블록에서 떠넘겨 받은 예외를 처리해야 합니다. 다음 코드는 throws키워드가 있는 method2()를 method1()dp에서 호출하는 방법을 보여줍니다.

public void method1(){
	try {
     method2();
   } catch(ClassNotFoundException e) {
   // 예외 처리 코드
   System.out.println("클래스가 존재하지 않습니다.");
   }
  }
  
  public void method2() throws ClassNotFoundException {
   Class clazz = Class.forName("java.lang.String2");
  }

method1()에서도 try-catch 블록으로 예외를 처리하지 않고 다음과 같이 throws 키워드로 다시 예외를 떠넘길 수 있습니다. 그러면 method1() 호출하는 곳에서 try-catch 블록을 사용해서 예외를 처리해야 합니다.

public void method1() throws ClassnotFoundException {
 method2();
}

예외 처리 떠넘기기

package ch10.exam01;

public class ThrowsExample {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			findClass();
		} catch(ClassNotFoundException e) {
			System.out.println("클래스가 존재하지 않습니다.");
		}
	}
	
	public static void findClass() throws ClassNotFoundException {
		Class clazz = Class.forName("java.lang.String2");
	}

}

main() 메소드에서도 throws 키워드를 사용해서 예외를 떠넘길 수 있는데, 결국 JVM이 최종적으로 예외 처리를 하게 됩니다. JVM은 예외의 내용을 콘솔에 출력하는 것으로 예외 처리를 합니다.

public static void main (Sting[] args) throws ClassNotfoundException {
   findClass();
}
728x90

'JAVA' 카테고리의 다른 글

JAVA 익명 객체  (0) 2023.02.20
JAVA 중첩 클래스와 중첩 인터페이스  (0) 2023.02.17
JAVA 타입변환과 다형성(인터페이스)  (0) 2023.02.16
JAVA 인터페이스  (0) 2023.02.15
JAVA 추상 클래스  (0) 2023.02.15
Comments