케네스로그

[Java] 예외처리 본문

Dev/Java

[Java] 예외처리

kenasdev 2022. 3. 6. 15:32
반응형

 

에러(Error)는 프로그램의 비정상적인 종료를 뜻한다. 에러는 두가지로 나뉘어질 수 있는데, 컴파일에러와 런타임 에러가 존재한다. 컴파일에러는 보통의 IDE 차원에서 문법 검사 등으로 확인해주기때문에 프로그램 시작 전에 알 수 있다. 하지만, 런타임 에러는 프로그램이 동작하는 과정에서 발생한다.

 

런타임 에러는 JVM이 정상적으로 프로그램 동작수행을 할 수 없기에 종료되는 것이며, 이것을 개발자가 핸들링 할 수 있는 방법은 없다. 애초에 코드를 짤 때부터 발생하지 않도록 해야하며, 에러는 로그를 남기거나 종료되기전 적절한 메시지를 보여주는 방법 등을 쓸 수는 있다. 메모리 부족으로 인한 프로그램의 강제 종료를 예로 들 수 있다.

 

예외(Exception)는 에러와 달리 복구할 수 있으며 프로그램의 종료를 피해서 개발자가 핸들링할 수 있다. 예를 들어, 이메일 입력창에 이메일 형식이 아닌 전화번호를 기입하는 경우, 예외 발생(Illegal Argument Exception)을 처리하는 로직을 짤 수 있다.

 

Error Exception

  Error Exception
발생 원인 컴퓨터 하드웨어의 오동작 또는 고장으로 JVM 실행에 문제가 발생 사용자의 잘못된 조작 또는 개발자의 잘못된 설계로 인해 발생
영향을 받는 대상 프로세스 쓰레드
복구 불가 가능
패키지 java.lang.error java.lang.Exception
예시 IOError, OutOfMemoryError etc NullPointerException, SqlException etc

자바 예외 계층 구조

출처: https://madplay.github.io/post/java-checked-unchecked-exceptions

자바에서는 예외를 클래스로 관리한다. JVM은 실행 중 예외(Exception)나 에러(Error)가 발생하면 이를 객체로 만든다. 그 후, 예외 처리 코드에서 생성된 예외 객체를 이용할 수 있도록 한다.

모든 예외(Exception)와 에러(Error)는 Throwable 클래스의 하위 클래스이다.

Error는 앞서 언급한것처럼 사전에 감지할 수 없는 상황이기때문에 Unchcked Exception에 해당된다.

Exception은 Unchecked Exception과 Checked Exception으로 나뉘어진다.

Checked Exception

컴파일러 수준에서 확인이 가능한 에러를 checked exception이라고 한다. 즉, 예상이 가능한 부분이기때문에 try-catch구문을 통해 핸들링할 수 있다.

 

 

Unchecked Exception

Unchecked exception은 컴파일러가 감지하지 못하는 예외를 말한다. 자바에서는 Runtime Exception, Error, 그 하위 클래스들이 이에 속한다. Error는 앞서 언급했듯이 개발자가 핸들링할 수 있는 것이 아니기때문에 예외처리가 불가능하다. 반면, Exception 중 Runtime Exception의 하위 클래스에 해당하는 예외들은 개발자가 직접 예외처리를 해야만 한다.

왜 exception을 두가지로 나누었을까? 출처: https://wisdom-and-record.tistory.com/46

Runtime Exception은 프로그램 코드의 문제로 발생하는 예외로써, 메소드를 호출하는 쪽(클라이언트)에서 이를 해결하거나 대처할것으로 예상하기 힘들다. Runtime Exception은 프로그램 동작 수행 중 빈번하게 발생할 수 있기 때문에 모든 Runtime Exception를 체크한다면 프로그램의 명확성을 떨어뜨릴 수 있다. 따라서, 메소드를 호출하는 쪽(클라이언트)가 에외상황을 적절히 대처할 수 있다면 checked exception으로 만들고, 그렇지 않은 경우엔 unchecked exception으로 만든다.

 

 

Runtime Exception

  • NullPointerException

가장 빈번하게 발생하는 예외로써, null값을 가진 참조 변수로 객체 접근 연산자(.)을 사용했을때 발생한다.

String s = null;
System.out.println(s.toString()); // NullPointerException
  • ArrayIndexOutOfBoundsException

배열에서 인덱스의 범위를 초과하여 참조하는 경우에 발생한다.

int[] nums = new int[3];
System.out.println(nums[5]); // ArrayIndexOutOfBoundsException
  • NumberFormatException

문자열을 파싱하는 과정에서 적절치 못한 데이터가 주어질때 발생한다.

String data = "abc"
int value = Integer.parseInt(data); // NumberFormatException
  • ClassCastException

ClassCastException은 클래스, 인터페이스 간 타입변환에서 발생하는 예외를 말한다.

Animal animal = new Dog();
Cat cat = (Cat) animal;

 

자바에서 예외처리 하는 방법

try-catch

try {
	...
} catch (ExceptionClass variableName1) {
	...
} catch (ExceptionClass variableName2) {
	...
}

try블록은 수행할 코드로써, 예외 발생 가능성이 있는 블록

catch블록에 예외클래스에 해당하는 예외가 발생하면 실행할 블록. 1개 이상의 다중 catch블록도 가능하다.

  • printStackTrace(): 예외 발생 시 호출스택에 있는 메소드의 정보와 예외 메시지를 출력
  • getMessage(): 발생한 예외 클래스의 인스턴스에 저장된 메시지 반환

자바 7 이후부터는 catch 블록에 1개 이상의 Exception 객체를 받도록 할 수 있다. 하지만, Exception객체들이 조상과 자손 관계이면 에러가 발생한다.

catch (ArithmeticException | IOException e) {
	e.printStackTrace();
	throw e;
}

 

throw

public static void main(String[] args) {
	int i = 10;
	int j = 0;
	divide(i, j);
}

public static int divide(int i, int j) {
	if(j == 0) {
		System.out.println("j는 0이 되면 안됩니다");
		return 0;
	}
	return i / j;
}

위의 방법으로 ArithmeticException을 해결할 수 있지만, 잘못된 값(0을 리턴)을 전달하는 것 또한 문제가 된다.

public static void main(String[] args) {
	int i = 10;
	int j = 0;
	divide(i, j);
}

public static int divide(int i, int j) {
	if(j == 0) {
		throw new IllegalArgumentException("0으로 나눌수없습니다.");
	}
	return i / j;
}

특정 경우 새로운 Exception을 생성하여 직접 예외를 발생시킬 수 있다.

 

 

throws

메소드 선언부에 throws키워드를 사용하여 예외를 발생시킨 메소드에서 예외를 처리하도록 한다. throws 키워드를 지닌 메소드를 호출하는 메소드(클라이언트)에서 try-catch를 통해 예외처리를 반드시 해줘야 한다. 즉, 예외를 처리하는 책임의 주체를 메소드를 호출하는 쪽으로 전가시킨다.

선언부에 예외를 선언해둠으로써 명시적으로 어떤 예외가 일어날 수 있는지 직관적으로 알 수 있다는 장점이 있다.

public static void main(String[] args) {
	int i = 10;
	int j = 0;
	divide(i, j);
} catch (ArithmeticException e) {
	System.out.println(e);
}

public static int divide(int i, int j) throws ArithmeticExceptin {
	return i / j;
}

<aside> 💡 thorws vs thorw

</aside>

 

 

finally

출처: https://velog.io/@youngerjesus/자바-예외-처리

finally 블록은 try 블록이 끝나거나 예상치 못한 에러가 발생하더라도 실행되는 블록이다. 하지만, JVM이 try catch 블록을 실행 중 종료되었다면 finally블록은 실행되지 않는다.

Thread가 try catch 블록을 실행 중 interrupt 당하거나 kill 된다면 finally 블록은 실행되지 않는다.

 

 

try-catch-resource

참조: https://catch-me-java.tistory.com/46 https://wisdom-and-record.tistory.com/46

자바 7 이후부터는 try-catch-resource를 사용할 수 있으며, 이것은 InputStream, ServerSocket, DB커넥션 등을 안전하게 사용할수 있도록 지원한다.

FileInputStream fis = null;
try {
	fis = new FileInputStream("not_exist.txt");
} catch (IOException e) {
	e.printStackTrace();
} fianlly {
	if(fis!=null) {
		try {
			fis.close(); // FileInputStream을 close해줘야한다.
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

try구문에서 close가 필요한 코드가 있다면 바이트코드를 조작하여 자원을 반환한다. 모든 객체가 자동으로 반환되는것은 아니며, AutoCloseable 인터페이스를 구현한 클래스만 자동으로 반환된다.

try(
	FileInputStream fis = new FileInputStream("not_exist.txt")
	) {
		System.out.println(fis.read());
	} catch (IOException e) {
		e.printStackTrace();	
	}

 

 

커스텀한 예외를 만드는 방법

public class {Exception_Name} extends Exception {
	...
}

Exception이나 Exception의 후손을 상속받아 만들어진 클래스

클래스의 이름만으로 어떤 오류가 발생했는지 알려주어 코드의 직관성을 높인다.

public class BizException extends RuntimeException {
	public BizException(String msg) {
		super(msg);
	}

	public BizException(Exception ex) {
		super(ex);
	}
}

 

 

예외 되던지기 (exception re-throwing)

한개의 메소드에서 발생할 수 있는 예외가 여러개라면, 일부는 메소드 내부에서 throw를 통해 해결하고, 일부는 선언부에서 throws를 통해 메소드를 호출한 쪽(클라이언트)에서 해결하도록 할 수 있다. 이를 예외 되던지기라고 한다.

public class ExceptionDemo {

    public static void main(String[] args) {
        try {
            methodA();
        } catch (Exception e) {
            System.out.println(“main에서 예외 처리”);
        }
    }

    static void methodA() throws Exception {
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.println(“methodA에서 예외 처리”);
            throw e;
        } 
    }
}
// get this example code from wisdom-and-record.tistory.com

 

 

Chained Exception

한 예외가 다른 예외를 발생시키는 경우가 있다. 예를 들어, 예외A가 예외 B를 발생시켰다면, A는 B의 원인 예외(cause exception)이라고 한다. 원인 예외는 initCause()로 지정할 수 있으며, Throwable클래스에 정의되어 있기 때문에 모든 예외 클래스에서 사용할 수 있다.

public class ExceptionDemo {

    public static void main(String[] args) {
        try {
            methodA(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void methodA(int num) throws IOException{
        try {
            if (num == 0) {
                throw new IllegalArgumentException();
            }
        } catch (IllegalArgumentException e) {
            IOException ioException = new IOException();
            ioException.initCause(e);  /// IOException의 예외를 IllegalArgumentException으로 지정/
            throw ioException;
        }
    }
}
// get this example code from wisdom-and-record.tistory.com
반응형

'Dev > Java' 카테고리의 다른 글

[Java] 인터페이스  (0) 2022.02.20
[Java] 패키지  (0) 2022.02.18
[Java] 메소드 디스패치  (0) 2022.02.16
[Java] 상속, super()  (1) 2022.02.15
[Java] 객체의 생성, 인스턴스화, 생성자, this 키워드  (0) 2022.02.09