새소식

TIL

[TIL] 240428 <자바> 예외처리

  • -

 

 

 

 

오류(Error) vs 예외(Exception)

 

오류 : 회복 불가

  • 이는 시스템 레벨에서, 또는 주로 환경적인 이유로 발생
  • 코드의 문제로 발생하는 경우도 있지만, 일단 발생하는 경우 일반적으로 회복이 불가능
  • 에러가 발생한 경우 우리는 어떠한 에러로 프로그램이 종료되었는지를 확인하고 대응

예외 : 회복 가능

  • 회복이 가능하다는 전제는 우리가 “그 예외가 발생할 수 있다는 것을 인지하고, 대응했을 것입니다”.
  • 현실적으로 코드 레벨에서 할 수 있는 문제 상황에 대한 대응은 “예외 처리”에 속함

 

 

 

예외(Exception)의 종류

'코드 실행' 관점에서 예외의 종류

  • 컴파일 에러(예외)
    • .java 파일을 .class 파일로 컴파일할 때 발생하는 에러
    • 대부분 자바 프로그래밍 언어의 규칙을 지키지 않았기 때문에 발생
    • 해결 방법은 문법에 맞게 다시 작성하는 것

  • 런타임 에러(예외)
    • 문법적인 오류는 아니라서, 컴파일은 잘 되었지만 “프로그램”이 실행 도중 맞닥뜨리게 되는 예외

 

'예외 처리' 관점에서 예외의 종류

  • 확인된 예외 (Checked Exception)
    • 컴파일 시점에 확인하는 예외
    • 반드시 예외 처리를 해줘야 하는 예외
    • 이미 특정한 문제를 인지하고 있어서, 해당 예외를 정의해두었고, 정의해두었기 때문에 컴파일하는동안
      이 예외에 대한 예외 처리를 했는지 확인(Check)할 수 있는 예외
    • "Checked Exception에 대한 예외 처리를 하지 않으면 컴파일 에러가 발생" (컴파일 에러와 다르다!)
  • 미확인된 예외 (Unchecked Exception)
    • 런타임 시점에 확인되는 예외
    • 예외 처리가 반드시 필요하지 않은 예외

 

 


 

예외 처리의 흐름

 

1. 예외 정의하기 - 예외 클래스를 생성하여 직접 예외 정의 가능

class OurBadException extends Exception {  //Exception 상속
	public OurBadException() {
		super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
	}
}

 

 

2. 예외가 발생할 수 있음을 알림 - 클래스를 만들고, 메서드를 만들며 메서드가 위험하다는 것을 미리 예측하여 위험하다고 알리기
    (throw, throws)

class OurClass {
    private final Boolean just = true;
		
	//throws : 메서드 이름 뒤에 붙어 이 메서드가 어떠한 예외사항을 던질 수 있는지 알려줌
    public void thisMethodIsDangerous() throws OurBadException {
        if (just) {
			//throw : 메서드 안에서, 실제로 예외 객체를 던질 때 사용
            throw new OurBadException(); //실제로 던지는 예외 객체 하나와 같이 써야함
            //throw문과 함께 메서드가 종료됨
        }
    }
}
  • throws
    • 메서드 이름 뒤에 붙어 이 메서드가 어떠한 예외사항을 던질 수 있는지 알려주는 예약어
    • 여러 종류의 예외사항을 적을 수 있음
  • throw
    • 메서드 안에서, 실제로 예외 객체를 던질 때 사용하는 예약어
    • 실제로 던지는 예외 객체 하나와 같이 써야함
    • 일반 메서드의 return키워드처럼 throw 아래의 구문들은 실행되지 않고, throw문과 함께 메서드 종료됨4

 

 

3. 예외가 발생할 수 있음을 알고 예외를 핸들링하기 - 위험 감지했다면 try-catch(finally) 키워드 이용

public class StudyException {
    public static void main(String[] args) {  //메인 메서드
        OurClass ourClass = new OurClass();
        
        
	//try-catch, finally
        try {  // "시도" 해보는 코드
            // 1. 위험한 메소드의 실행을 "시도" 해 봅니다.
            ourClass.thisMethodIsDangerous();  //위험메서드
        } catch (OurBadException e) {  //예외가 발생하는경우 "handling" 하는 코드
            // 2. 예외가 발생하면, "잡아서" handling 합니다.
						// 즉 try 블럭 내의 구문을 실행하다가 예외가 발생하면
						// 예외가 발생한 줄에서 바로 코드 실행을 멈추고
						// 여기 있는 catch 블럭 내의 코드가 실행됨
            System.out.println(e.getMessage());
        } finally {  // 무조건 실행되는 코드
            // 3. 예외의 발생 여부와 상관없이, 실행시켜야 하는 코드가 들어감
            System.out.println("우리는 방금 예외를 handling 했습니다!");
        }

    }
}

 

1. 위험 감지하기 : 특정 클래스의 메서드 사용 시, 이 메서드가 위험한지 아닌지 확인해야함

2. 위험 감지했다면, try-catch, finally 키워드 이용하기

  • try : 위험한 메서드의 실행을 "시도"할 코드 담음
  • catch : try 안의 코드 실행하다가 예외 발생 시, 거기서 바로 코드실행 멈추고 catch 안의 코드 실행
    • catch 는 소괄호()를 통해 어떤 예외 클래스를 받아서 처리할지 정의해 주어야함
    • catch로 모든 예외를 다 받고 싶으면 Exception 을 넣어주면됨
    • 1개의 try 문에 catch 문은 여러 개 사용가능
  • finally : 예외 발생 여부와 상관없이 무조건 실행시켜야 하는 코드 담기

 

 

위의 예시에서 Checked Exception을 다뤘음!
(예외가 발생하는 상황을 “인지” 했고, 어떠한 에러인지 “정의” 하였음)

메서드를 선언할 때 예외가 발생하는 위험한 메서드라는 것을 “알렸음 (throws/throw)”
checked exception을 정의하고 알렸으니 이 메서드를 사용할 때 예외 처리를 하지 않으면 컴파일 에러가 발생!!!

 

 

 


 

예외 클래스 구조

 

Throwable 클래스의 자식으로 에러( Error )와 예외( Exception ) 클래스

 

  • RuntimeException을 상속한 예외들은 UncheckedException
  • 반대로 상속하지 않은 예외들은 CheckedException으로 구현

 

Java 예외 리스트

더보기

// Java 예외 리스트

// 여기에 없다면 특정 에러를 더 구체화해서 직접 정의&구현하면됨

 

// 출처 : https://programming.guide/java/list-of-java-exceptions.html

// java.io
IOException
CharConversionException
EOFException
FileNotFoundException
InterruptedIOException
ObjectStreamException
InvalidClassException
InvalidObjectException
NotActiveException
NotSerializableException
OptionalDataException
StreamCorruptedException
WriteAbortedException
SyncFailedException
UnsupportedEncodingException
UTFDataFormatException
UncheckedIOException


// java.lang
ReflectiveOperationException
ClassNotFoundException
InstantiationException
IllegalAccessException
InvocationTargetException
NoSuchFieldException
NoSuchMethodException
CloneNotSupportedException
InterruptedException


// 산술 예외
IndexOutOfBoundsException
ArrayIndexOutOfBoundsException
StringIndexOutOfBoundsException
ArrayStoreException
ClassCastException
EnumConstantNotPresentException
IllegalArgumentException
IllegalThreadStateException
NumberFormatException
IllegalMonitorStateException
IllegalStateException
NegativeArraySizeException
NullPointerException
SecurityException
TypeNotPresentException
UnsupportedOperationException


// java.net
HttpRetryException
SocketTimeoutException
MalformedURLException
ProtocolException
SocketException
BindException
ConnectException
NoRouteToHostException
PortUnreachableException
UnknownHostException
UnknownServiceException
URISyntaxException


// java.text
ParseException
 

// java.time
DateTimeException
 

// java.time.zone
ZoneRulesException

 

 


 

연결된 예외 (Chained Exception)

  • 예외는 다른 예외를 유발할 수 있음
  • 예외 A가 예외 B를 발생시켰다면, 예외 A는 B의 원인 예외
  • 원인 예외를 새로운 예외에 등록한 후 다시 새로운 예외를 발생시킴 → 예외 연결

 

// 연결된 예외 
public class main {

    public static void main(String[] args) {
        try {
            // 예외 생성
            NumberFormatException ex = new NumberFormatException("가짜 예외이유");

            // initCause() : 원인 예외 설정(지정한 예외를 원인 예외로 등록)
            ex.initCause(new NullPointerException("진짜 예외이유"));

            // 예외를 직접 던집니다.
            throw ex;
            
        } catch (NumberFormatException ex) {
            // 예외 로그 출력
            ex.printStackTrace();

            // getCause() : 예외 원인 조회, 후 출력
            ex.getCause().printStackTrace();
        }
        
        
        

        // checked exception 을 감싸서 unchecked exception 안에 넣음
        throw new RuntimeException(new Exception("이것이 진짜 예외 이유 입니다."));
    }
}

// 출력
Caused by: java.lang.NullPointerException: 진짜 예외이유

 

  • 원인 예외를 다루기 위한 메소드
    • initCause() : 지정한 예외를 원인 예외로 등록하는 메소드
    • getCause() : 원인 예외를 반환하는 메소드

  • 예외를 연결하는 이유
    • 여러 가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위함
    • checked exception을 unchecked exception으로 포장(wrapping) 하는데 유용하게 사용됨

 

 

 

 

실제 예외 처리하는 방법

 

1. 예외 복구하기

public String getDataFromAnotherServer(String dataPath) {
		try {
				return anotherServerClient.getData(dataPath).toString();
		} catch (GetDataException e) {
				return defaultData;
		}
}
  • 실제로 try-catch로 예외를 처리하고 프로그램을 정상 상태로 복구하는 방법
  • 가장 기본적인 방식이지만, 현실적으로 복구가 가능한 상황이 아닌 경우가 많거나 최소한의 대응만 가능한 경우가 많기 때문에 자주 사용되지는 않음

 

2. 예외 처리 회피하기

public void someMethod() throws Exception { ... }

public void someIrresponsibleMethod() throws Exception {
		this.someMethod();
}
  • someMethod()에서 발생한 에러가 someIrresponsibleMethod()의 throws를 통해서 그대로 다시 흘러나가게됨
    (물론 같은 객체 내에서 X!, 예외 처리 회피를 보여주기위한 단순한 예시 코드)
  • 관심사를 분리해서 한 레이어에서 처리하기 위해서 이렇게 에러를 회피해서 그대로 흘러 보내는 경우도 있음

 

3. 예외 전환하기

public void someMethod() throws IOException { ... }

public void someResponsibleMethod() throws MoreSpecificException {
		try {
			this.someMethod();
		} catch (IOException e) {
			throw new MoreSpecificException(e.getMessage());
		}
}
  • 예외 처리 회피하기의 방법과 비슷하지만, 조금 더 적절한 예외를 던져주는 경우
  • 보통은 예외 처리에 더 신경 쓰고 싶은 경우나, 오히려 RuntimeException처럼 일괄적으로 처리하기 편한 예외로 바꿔서 던지고 싶은 경우 사용
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.