Exception Handling

Updated:

Exception Handling

에러와 예외

  • 어떤 원인에 의해 오동작 하거나 비정상적으로 종료되는 경우
  • 심각도에 따른 분류
    • Error : 메모리 부족, stack overflow와 같이 일단 발생하면 복구 할 수 없는 상황.
    • 프로그램의 비 정상적 종료를 막을 수 없음 => 디버깅 필요
    • Exception : 읽으려는 파일이 없거나 네트워크 연결이 안되는 등 수습 될 수 있는 비교적 상태가 약한 것들
    • 프로그램 코드에 의해 수습될 수 있는 상황
  • Exception handlling(예외처리)란
    • 예외 발생 시 프로그램의 비 정상 종료를 막고 정상적인 실행 상태를 유지하는 것.
    • 예외의 감지 및 예외 발생 시 동작할 코드 작성 필요

예외 클래스의 계층

  1. Error 계열 ( OutOfMemoryError ..)
  2. Checked Exception 계열 (SQLEception, IOException, FileNorFoundException)
  3. Unchecked Exception 계열(RuntimeException, ArithmeticException)
  • checked Exception : 예외에 대한 대처 코드가 없으면 컴파일이 진행되지 않음
  • unchecked exception(RuntimeException의 하위클래스) : 예외에 대한 대처 코드가 없더라도 컴파일은 진행됨.

예외의 발생

public static void main(String[] args){
	int[] intArray = {10};
	System.out.println(intArray[2]);
	System.out.println("프로그램 종료합니다.")
}

=> Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException발생

  • 배열의 범위를 벗어나므로 발생했고, 프로그램이 갑자기 종료
  • 컴파일시에는 지장이 없기때문에 RuntimeException 계열이다.

Exception handling 기법

try~catch 구문

try{
	//예외가 발생할 수 있느 코드
}catch(Exception e){
	//던진 예외를 받음
	//예외가 발생했을 때 처리할 코드
}
public static void main(String[] args){
	int[] intArray = {10};
	try{
		System.out.println(intArray[2]);
	} catch(ArrayIndexOutOfBoundsException e){
		System.out.println("예외가 발생했찌만 처리함 : 배열 크기 확인 필요");
	}
	System.out.println("프로그램 종료합니다.");
}

Exception 객체의 정보활용

  • Throwable의 주요 메서드
    • public String getMessage() : 발생된 예외에 대한 구체적인 메시지를 반환한다.
    • public Throwable getCause() : 예외의 원인이 되는 Throwable 객체 또는 null을 반환한다.
    • public void printStackTrace() : 예외가 발생된 메서드가 호출되기까지의 메서드 호출 스택을 출력한다. 디버깅의 수단으로 주로 사용된다.
int[] intArray = {10};
try{
	System.out.println(intArray[2]);
} catch (ArrayIndexOutOffBoundsException e){
	System.out.printf("exception handling: 배열 크기 확인 필요 : %s%n", e.getMessage());
	e.printStacTrace();
}
System.out.println("프로그램 종료합니다.");
  • 출력결과 exception handling : 배열 크기 확인 필요 : 2

java.lang.ArrayIndexOutOfBoundsException : 2

at ch09.exception.SimpleTryCtch.main(SimpleTryCatch.java : 8) 프로그램 종료합니다.    

try-catch문에서의 흐름

  • try 블록에서 예외가 발생하면
    • JVM이 해당 Exception 클래스의 객체 생성 후 던짐(throw) => throw new XXException()
    • 던져진 exception을 처리할 수 있는 catch 블록에서 받은 후 처리 => 적당한 catch 블록을 만나지 못하면 예외처리는 실패
    • 정상적으로 처리되면 try - catch 블록을 벗어나 다음 문장 진행
  • try 블록에서 어떠한 예외도 발생하지 않은 경우
    • catch문을 거치지 않고 try-catch 블록의 다음 흐름 문장을 실행

다중 exception handling

  • try 블록에서 여러 종류의 예외가 발생할 경우
  • 하나의 try 블록에 여러개의 catch 블록 추가 가능
  • 다중 catch 문장 작성 순서 유의 사항
    • JVM이 던진 예외는 catch문장을 찾을 때는 다형성이 적용됨
    • 상위 타입의 예외가 먼저 선언되는 경우 뒤에 등장하는 catch블록은 동작할 기회가 없음
    • Unreachable catch block for Exception
    • 상속 관계가 없는 경우는 무관
    • 상속 관계에서는 작은 범위(자식)에서 큰 범위(조상)순으로 정의
public class CheckedExceptionHandling {
        public static void main(String[] args) {
                try {
                        Class.forName("abc.Def"); // ClassNotFoundException
                        new FileInputStream("Hello.java"); // FileNotFoundException
                        DriverManager.getConnection("Hello"); // SQLException

                } catch (ClassNotFoundException e) {
                        System.out.println("클래스가없다" + e.getMessage());
                } catch (FileNotFoundException e) {
                        System.out.println("뭔가.. 리소스가 없을까요?" + e.getMessage());
                } catch (SQLException e) {
                        System.out.println("드라이버가 없나??? " + e.getMessage());
                } catch (Exception e) {
                        System.out.println("뭐든지 처리완료!!!");
                }

                System.out.println("프로그램 정상 종료");

        }
}

다중 예외 처리fmf 이용한Checked Exception 처리

  • 처리하지 않으면 컴파일 불가 : Checked Exception
  • 예외처리는 가능한 구체적으로 진행
  • 발생하는 예외들을 하나로 처리하기
    • 예외 상황 별 처리가 쉽지 않다.
    • 가급적 예외 상황별로 처리하는 것을 권장
  • 심각하지 않은 예외를 굳이 세분화 해서 처리하는 것도 낭비
    • ’` ‘를 이용해 하나의 catch구문에서 상속관계강 없는 여러 개의 exception 처리
  • 계층을 이루는 예외의 처리
public class HierachyException {
    public static void main(String[] args) {
        String src = "./.project";
         try {
            FileInputStream input = new FileInputStream(src);
            int readData = -1;

            while ((readData = input.read()) != -1) {
                System.out.print((char) readData);
            }
         }catch(FileNotFoundException e) {
        	 System.out.println("파일이 없나봐...");
         }catch(IOException e) {
        	 System.out.println("읽다가 무가 잘못되었나봐...");
         }


        System.out.println("파일 읽음 완료!");
    }

}

try~catch~finally 구문을 이용한 예외처리

  • finally는 예외 발생 여부와 상관 없이 언제나 실행
  • 중간에 return을만나는 경우도 finally블록을 먼저 수행 후 return 실행
try{
	//exception이 발생할 만한 코드 - System 자원 사용
}catch(Exception e){
	//XXException 발생 시 처리코드
}finally{
	//try block에 접근했던 System자원의 안전한 원상복구
}
  • try~catch문 밖에서 코드를 처리하지 않고 finally를 하는 이유
    1. try에 return이 적혀 있으면 밖에 코드는 진행하지 않는다.
    2. 클린코드
    3. ex) 생성한 시스템 자원을 반납하지 않으면 장래 resource leak발생 가능 => close처리
    4. 지전분할 수 밖에 없는 finally블록
    5. close 메서드 자체가 IOException 유발가능
    6. FileInputStream 생성자에서 IOException 발생시 fileInput은 null인 상황 => try-with-resource로 처리

try-with-resource

  • JDK 1,7이상에서 리소스의 자동 close 처리
try(리소스타입1 res1 = 초기화; 리소스타입2 res2 = 초기화; ...){
	//예외 발생 코드
}catch(Exception e){
	//Exception handling코드
}
  • try선언문에 선언된 객체들에 대해 자동 close호출(finally역할)
  • 단 해당 객체들이 AutoCloseable interface를 구현할 것. (각종 I/O Stream, socket, connection …)
  • 해당 객체는 try 블록에서 다시 할당될 수 없음
public void useStreamNewStye(){
	try (FileInputStream fileInput = new FileInputStream("abc.txt")){
		System.out.println("FileInputStream이 생성되었습니다.");
		fileInput.read();
	}catch (IOException e){
		System.out.println("파일 처리 실패");
	}
}

throws 키워드를 통한 처리 위임

  • method에서 처리해야 할 하나 이상의 예외를 호출한 곳으로 전달(처리 위임)
  • 예외가 없어지는 것이 아니라 단순히 전달됨
  • 예외를 전달받은 메서드는 다시 예외 처리의 책임 발생
    • 예외가 없어지는 것이 아니라 단순히 전닮됨
    • 예외를 전달받은 메서드는 다시 예외 처리의 책임 발생
    • 처리하려는 예외의 조상 타입으로 throws 처리 가능

checked exception과 throws

  • checked exception은 반드시 try~catch 또는 throws필요
  • 필요한 곳에서 try~catch처리

runtime exception과 throws

  • runtime exception은 throws 하지 않아도 전달되지만
  • 하지만 결국은 try~catch로 처리해야함
public class ThrowsTest {
	public static void main(String[] args) {
		try {
			methodCall1();
		} catch (ClassNotFoundException e) {
			System.out.println("classnotfound exception 발생");
			e.printStackTrace();
		} catch (ArithmeticException e) {
			System.out.println("Arithmetic exception 발생");
			e.printStackTrace();
		}
		System.out.println("done");
	}

	private static void methodCall1() throws ClassNotFoundException {
		methodCall2();
	}

	private static void methodCall2() throws ClassNotFoundException {
		// checkedExceptionMethod();
		uncheckedExceptionMethod();
	}

	private static void checkedExceptionMethod() throws ClassNotFoundException {
		Class.forName("Hello");
	}

	private static void uncheckedExceptionMethod() {
		int i = 1 / 0; // 예외 발생 - unchecked exception
	}

}

로그 분석과 예외의 추적

  • Throwable의 printStackTrace는 메서드 호출 스택 정보 조회 가능
    • 최초 호출 메서드에서부터 예외 발생 메서드까지의 스택 정보 출력
  • 꼭 확인해야할 정보
    • 어떤 예외인가? - 예외종류
    • 예외 객체의 메시지는 무엇인가? - 예외 원인
    • 어디서 발생했는가? - 디버깅 출발점
    • 직접 작성한 코드를 디버깅 대상으로 삼을 것
    • 참조하는 라이브러리(java.xx 등)은 과감히 건너뛰기

throws의 목적과 API 활용

  • API가 제공되는 많은 메서드들ㅇ른 사전에 예외갑 ㅏㄹ생할 수 있음을 선언부에 명시하고 프로그래머가 그 예외에 대처하도록 강요함
  • JAVA API에 예외발생과 처리까지 잇으면 개발자는 상황을 인지하지 못한다. 그래서, 예외가 발생하면 예외를 전파시켜주고 개발자는 상황을 인지하고 적절한 예외처리가 필요하다.

메서드 재정의와 throws

  • 메서드 재정의 시 조상클래스 메서드가 던지는 예외보다 부모예외를 던질 수 없다.
  • 부모가 치지 않은 사고를 자식이 칠 수 없다.

사용자 정의 예외

  • API에 정의된 Exception이외에 필요에 따라 사용자 정의 예외 클래스 작성
  • 대부분 Exception 또는 RuntimeException 클래스를 상속받아 작성.
    • checked exception활용 : 명시적 예외 처리 또는 throws 필요
    • 코드는 복잡해지지만 처리 누락 등 오류 발생 가능성은 줄ㄹ어듦
    • runtime Exception 활용 : 묵시적 예외 가능
    • 코드가 간결해지지만 예외 처리 누락 가능성 발생
  • 사용자 정의 예외를 만들어 처리하는 장점
    • 객체의 활용 - 필요한 추가정보, 기능 활용 가능
    • 코드의 재사용 - 동일한 상황에서 예외 객체 재사용 가능
    • throws 메커니즘의 이용 - 중간 호출 단계에서 return 불필요
  • FruitNotFoundException(사용자예외class)
public class FruitNotFoundException extends Exception {//checked Exception 처리
    public FruitNotFoundException(String name) {
        super(name + "에 해당하는 과일은 없습니다.");
    }
}

  • StoreFullException(사용자 예외 class)
public class StoreFullException extends RuntimeException{//unchecked Exception 처리
	public StoreFullException() {
		super("창고가 꽉 찼어요");
	}

}
package com.ssafy.day5.exception.custom;

/**
 * @since 2021. 7. 7.
 */
public class UserExceptionTest {
    private static String[] fruits = {"사과", "오렌지", "토마토"};

    public static void main(String[] args) {
        boolean result = getFruit1("사과");
        if (!result) {
            System.out.println("사과는 없습니다.");
        }
        result = getFruit1("사과");
        if (!result) {
            System.out.println("사과는 없습니다.");
        }
        // TODO: 2. getFruit2를 이용해서 오렌지 2개를 소비하시오.

        try {
			getFruit2("오렌지");
			getFruit2("오렌지");
		} catch (FruitNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

        // END:

        // TODO: 4. 수박, 멜론, 복숭아를 저장하시오.
        try {
        	storeFruit("수박");
        	storeFruit("멜론");
        	storeFruit("복숭아");
        }catch (StoreFullException e) {
        	e.printStackTrace();
        }
        // END:

        System.out.println("창고 관리 끝~");
    }

    private static boolean getFruit1(String name) {
        for (int i = 0; i < fruits.length; i++) {
            if (fruits[i] != null && fruits[i].equals(name)) {
                fruits[i] = null;
                return true;
            }
        }
        return false;
    }

    // TODO: 1. getFruit1을 참조하여 예외를 활용하는 형태로 getFruit2를 작성하시오.
    private static void getFruit2(String name) throws FruitNotFoundException {
    	for (int i = 0; i < fruits.length; i++) {
            if (fruits[i] != null && fruits[i].equals(name)) {
                fruits[i] = null;
                return;
            }
        }
    	//여기까지 왔다는 것은? 해당하는 과일이 없었다는 것!!
    	throw new FruitNotFoundException(name);
    }

    // END:

    // TODO: 3. 배열의 null인 지점에 과일을 저장하도록 작성하시오.
    private static void storeFruit(String name) {
    	for(int i=0; i<fruits.length; i++) {
    		if(fruits[i]==null) {
    			fruits[i] = name;
    			return;
    		}
    	}
    	//여기까지 왔다는 것은? 넣을 칸이 없어서 못넣었다.
    	throw new StoreFullException();

    }


    // END:
}

Leave a comment