ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바 예외(Exception) 및 예외 처리 개념
    Java 2020. 5. 26. 16:20
    반응형

    예외(Exception)의 종류

    컴파일 시점에 발생하는 예외를 Exception(일반예외)

    프로그램 실행시에 발생하는 예외를 RuntimeException(실행예외)

    둘다 java.lang.Exception 이라는 최상위 부모 클래스를 제공한다.

    예외처리 코드 및 실행 순서(Try-Catch-Finally)

    Try 블록 : 실제 코드가 들어가는 곳으로써 예외 Exeption이 발생할 가능성이 있는 코드

    Catch 블록 : Try 블록에서 Exeption이 발생하면 코드 실행 순서가 Catch 쪽으로 오게됨. 즉 예외에 대한 후 처리 코드

    Finally 블록 : Try 블록에서의 Exeption과 발생 유무와 상관 없이 무조건 수행되는 코드 (옵션이라 생략이 가능)

    예외처리 코드 실행 순서

    Exeption 발생 > Try 블록 수행 -> Catch 블록 수행 -> Finally 블록 수행 (생략가능)

    Exeption 미발생 > Try 블록 수행 -> Finally 블록 수행 (생략가능)

    컴파일러가 실행예외를 컴파일 시점에 판단하여 검사할 수 없기 때문에 개발자가 실행예외가 발생할 가능성이 있는 코드에 예외 처리 코드를 적용해줘야 한다.

    일반예외, 실행예외에 따른 예외처리 코드

    실행예외 : NullPointerException 

    (NullPointerException : 실제 참조할 대상이 null인데 참조하려고 할때 발생하는 예외)

    public class RuntimeExceptionExample {
    	public static void main(String[] args) {
    		//실행 예외
    		String[] array = null;
    		
    		for(int i=0; i<array.length; i++) {
    			System.out.println(array[i]);
    		}
    		System.out.println("Run...................");
    	}
    }
    

    결과

    java.lang.NullPointerExceptionjava.lang.NullPointerException RuntimeExceptionExample.main(RuntimeExceptionExample.java:8)

    위의 에러는 array가 null인데 참조를 시도하면서 발생하는 에러이다. 실행예외 발생.

    위의 소스를 예외 처리 하기.

    public class RuntimeExceptionExample {
    	public static void main(String[] args) {
    		
    		try {
    			//실행 예외
    			String[] array = null;
    			
    			for(int i=0; i<array.length; i++) {
    				System.out.println(array[i]);
    			}
    		} catch (NullPointerException e){
    			String message = e.getMessage();
    			System.out.println("예외 발생 : "+message);
    		} finally {
    			System.out.println("예외 상관 없이 실행");
    		}
    		System.out.println("예외처리 밖 ");
    	}
    }

    catch 블록에서 예외와 관련된 객체 e를 리턴하고 getMessage 메서드를 통해 예외 메세지를 확인 할 수 있다.

     

    일반예외 : ClassNotFoundException

    (ClassNotFoundException: 해당 클래스가 존재하지 않으면 발생하는 일반 예외)

    (forName 메서드 : 파라미터로 클래스 정보를 넘겨주고, 해당 클래스가 존재하면 갹체를 리턴해주는 메서드)

    컴파일러는 자동으로 일반예외를 감지하고 예외 처리 코드를 사용하도록 강제한다.

    public class ExceptionExample {
    	public static void main(String[] args) {
    		
    		try {
    			Class object = Class.forName("java.lang.limky");
    		} catch (ClassNotFoundException e){ 
    			e.printStackTrace(); //메서드를 통해, 어떤 과정을 거치다가 에러가 발생했는지 출력 한다.
    		}
    		System.out.println("예외처리 밖 ");
    	}
    }

    결과

    java.lang.ClassNotFoundException : java.lang.limky....

    예외처리 밖

    에러 유형은 java.lang.ClassNotFoundExcepotion : java.lang.limky를 상단에 찍어줌으로써 limky라는 클래스를 찾을 수 없어서 발생한 일반 예외임을 알린다.

     

    Exception 예제

    public class Test {
        public void defaultRun() {
            System.out.println("Run.");
        }
    
        public static void main(String[] args) {
            Test test = new Test();
            int c;
            try {
                c = 4 / 0;
            } catch (ArithmeticException e) {
                c = -1;
            } finally {
                test.defaultRun();
            }
        }
    }

    예외가 발생되도 defaultRun() 메소드는 실행 된다.

     

    예외 발생 시키기 (throw, throws)

    public class Test {
        public void getFruit(String fruit) {
            if("apple".equals(fruit)) {
                return;
            }
            System.out.println("고른 과일은 "+fruit+" 입니다.");
        }
    
        public static void main(String[] args) {
            Test test = new Test();
            test.getFruit("apple");
            test.getFruit("orange");
        }
    }

    getFruit 메소드는 apple이라는 문자열이 입력되면 return 으로 메소드를 종료 해 과일이 출력되지 못하도록 한다.

    RuntimeException

    단순히 과일을 출력하지 못하도록 하는 것이 아니라 적극적으로 예외를 발생시켜 보도록 하자.

    다음과 같은 AppleException 클래스를 작성한다.

    public class AppleException extends RuntimeException {
    	
    }
    

     

    위의 Test 클래스의 내용을 아래와 같이 수정한다.

    public class Test {
        public void getFruit(String fruit) {
            if("apple".equals(fruit)) {
                throw new AppleException();
            }
            System.out.println("고른 과일은 "+fruit+" 입니다.");
        }
    
        public static void main(String[] args) {
            Test test = new Test();
            test.getFruit("apple");
            test.getFruit("orange");
        }
    }

    return 부분을 throw new AppleException() 이라는 문장으로 변경하였다.

    이제 위 프로그램을 실행하면 "apple"이라는 입력값으로 getFruit 메소드 실행 시 다음과 같은 예외가 발생한다.

    Exception in thread "main" AppleException as Test.getFruit(Test.java:4) at Test.main(Test.java:11)

     

    Exception은 프로그램 작성 시 이미 예측가능한 예외를 작성할 때 사용하고 RuntimeException은 발생 할수도 발생 안 할수도 있는 경우에 작성한다.

    다른 말로 Exception을 Checked Exception, RuntimeException을 Unchecked Exception이라고도 한다.

    Exception

    AppleException을 다음과 같이 변경해 보자.

    public class AppleException extends Exception {
    	
    }
    

    RuntimeException을 상속하던 것을 Exception을 상속하도록 변경했다. 이렇게 하면 Test 클래스에서 컴파일 오류가 발생할 것이다. 예측 가능한 Checked Exception이기 때문에 예외처리를 컴파일러가 강제하기 때문이다.

    아래와 같이 변경해야 정상적으로 컴파일이 가능하다.

    public class Test {
        public void getFruit(String fruit) {
        	try {
        		if("apple".equals(fruit)) {
        			throw new AppleException();
        		}
        		System.out.println("고른 과일은 "+fruit+" 입니다.");
            }catch(AppleException e) {
                System.err.println("AppleException이 발생했습니다.");
            }    		
            
        }
    
        public static void main(String[] args) {
            Test test = new Test();
            test.getFruit("apple");
            test.getFruit("orange");
        }
        
    }

    AppleException이 발생했습니다.
    고른 과일은 orange 입니다.

    getFruit 메소드에서 try... catch 문으로 AppleException을 처리했다. 그래서 예외를 발생시키니깐 AppleException이 발생했습니다. 출력된다.

    예외 던지기(throws)

    위 예제를 보면 getFruit이라는 메서드에서 AppleException을 발생시키고 예외처리도 getFruit이라는 메서드에서 했는데 이렇게 하지 않고 getFruit을 호출한 곳에서 AppleException을 처리하도록 예외를 위로 던질 수 있는 방법이 있다.

    다음의 예제를 보자.

    public void getFruit(String fruit) throws  AppleException{
    	if("apple".equals(fruit)) {
    		throw new AppleException();
    	}
        System.out.println("고른 과일은 "+fruit+" 입니다.");
    }

    getFruit 메소드 뒷부분에 throws 라는 구문을 이용하여 AppleException을 위로 보낼 수가 있다. ("예외를 뒤로 미루기"라고도 한다.)

    위와 같이 getFruit 메소드를 변경하면 main 메소드에서 컴파일 에러가 발생할 것이다. throws 구문 때문에 AppleException의 예외를 처리해야 하는 대상이 getFruit 메소드에서 main 메소드(getFruit 메소드를 호출하는 메소드)로 변경되었기 때문이다.

    main메소드는 컴파일이 되기 위해서 다음과 같이 변경되어야 한다.

    public static void main(String[] args) {
    	Test test = new Test();
    	try {
    		test.getFruit("apple");
    		test.getFruit("orange");
    	}catch(AppleException e) {
    		System.err.println("AppleException이 발생했습니다.");        	
    	}
     }
    

    main 메소드에서 try... catch로 getFruit 메소드에 대한 AppleException 예외를 처리하였다.

    이제 한가지 문제가 남았다. AppleException 처리를 getFruit 메소드에서 하는것이 좋은지? main 메소드에서 하는것이 좋은지?

    getFruit 메소드에서 처리하는 것과 main 메소드에서 처리하는 것에는 아주 큰 차이가 있다.

    getFruit 메소드에서 예외를 처리하는 경우에는 다음의 두 문장이 모두 수행이된다.

    test.getFruit("apple");
    test.getFruit("orange");

    물론 test.getFruit("apple"); 문장 수행 시에는 AppleException이 발생하겠지만 그 다음 문장인 test.getFruit("orange"); 역시 수행이 된다.

    하지만 main 메소드에서 다음과 같이 예외 처리를 한 경우에는 두번 째 문장인 test.getFruit("orange");가 수행되지 않을 것이다. 이미 첫번 째 문장에서 예외가 발생하여 catch 문으로 빠져버리기 때문이다.

    try {
        test.getFruit("apple");
        test.getFruit("orange");
    }catch(AppleException e) {
        System.err.println("AppleException이 발생했습니다.");
    }

    프로그래밍 시 Exception을 처리하는 위치는 대단히 중요하다. 프로그램의 수행여부를 결정하기도 하고 트랜잭션 처리와도 밀접한 관계가 있기 때문이다.

     

    예시) 결제로직의 예외처리

    상품발송() {
        try {
            포장();
            영수증발행();
            발송();
        }catch(모두취소의 예외) {
           모두취소(); //로직 실행
        }
    }
    
    포장() throws 모두취소의 예외 {
       ...
    }
    
    영수증발행() throws 모두취소의 예외 {
       ...
    }
    
    발송() throws 모두취소의 예외 {
       ...
    }

     

     

    <예외처리 내용의 출처>

    wikidocs.net/229

    limkydev.tistory.com/198

    반응형

    댓글

Designed by Tistory.