소개
예외는 프로그램 실행 중 발생할 수 있는 오류 이벤트로, 정상적인 흐름을 방해합니다. 자바는 자바 예외 처리라고도 알려진 강력하고 객체지향적인 예외 상황 처리 방법을 제공합니다.
자바에서 예외는 사용자가 잘못된 데이터를 입력하거나 하드웨어 오류, 네트워크 연결 실패, 또는 데이터베이스 서버 다운과 같은 다양한 상황에서 발생할 수 있습니다. 특정 예외 상황에서 어떻게 처리할지 지정하는 코드를 예외 처리라고 합니다.
예외 던지기와 예외 잡기
자바는 문장 실행 중에 오류가 발생하면 예외 객체를 생성합니다. 예외 객체에는 메소드 계층 구조, 예외가 발생한 라인 번호, 예외의 유형과 같은 디버깅 정보가 포함되어 있습니다.
메소드에서 예외가 발생하면 예외 객체를 생성하여 런타임 환경에 전달하는 과정을 “예외를 던지는 것”이라고 합니다. 프로그램의 정상 흐름이 중단되고 Java 런타임 환경 (JRE)은 예외 처리기를 찾으려고 시도합니다. 예외 처리기는 예외 객체를 처리할 수 있는 코드 블록입니다.
- 예외 처리기를 찾는 논리는 오류가 발생한 메소드에서 검색을 시작합니다.
- 적절한 처리기가 발견되지 않으면 호출자 메소드로 이동합니다.
- 계속해서 진행됩니다.
따라서 메소드의 호출 스택이 A->B->C
이고 메소드 C
에서 예외가 발생하면 적절한 처리기를 찾는 검색은 C->B->A
로 이동합니다.
적절한 예외 처리기가 발견되면 예외 객체가 처리기로 전달됩니다. 처리기는 “예외를 잡는 것”이라고 합니다. 적절한 예외 처리기가 없으면 프로그램이 종료되고 예외에 대한 정보가 콘솔에 출력됩니다.
Java 예외 처리 프레임워크는 런타임 오류만 처리하기 위해 사용됩니다. 컴파일 타임 오류는 코드를 작성하는 개발자가 수정해야하며, 그렇지 않으면 프로그램이 실행되지 않습니다.
Java 예외 처리 키워드
자바는 예외 처리를 위한 특정 키워드를 제공합니다.
- throw – 오류가 발생하면 예외 객체가 생성되고 자바 런타임은 예외를 처리하기 위해 처리를 시작합니다. 때로는 코드에서 명시적으로 예외를 발생시키고자 할 수도 있습니다. 예를 들어, 사용자 인증 프로그램에서 비밀번호가
null
인 경우 클라이언트에게 예외를 발생시켜야 합니다.throw
키워드는 예외를 런타임에 던져서 처리하도록 합니다. - throws – 메소드에서 예외를 던지고 처리하지 않을 경우, 메소드 선언부에
throws
키워드를 사용하여 호출하는 프로그램에 해당 메소드에서 발생할 수 있는 예외를 알려야 합니다. 호출하는 메소드에서는 이러한 예외를 처리하거나throws
키워드를 사용하여 호출자 메소드로 전파할 수 있습니다.throws
절에는 여러 예외를 제공할 수 있으며,main()
메소드와 함께 사용할 수도 있습니다. - try-catch – 코드에서 예외 처리를 위해
try-catch
블록을 사용합니다.try
는 블록의 시작이고catch
는try
블록의 끝에 위치하여 예외를 처리합니다.try
블록에는 여러 개의catch
블록을 가질 수 있습니다.try-catch
블록은 중첩될 수도 있습니다.catch
블록은Exception
타입의 매개변수를 필요로 합니다. - 마침내 –
finally
블록은 선택 사항이며try-catch
블록과 함께 사용할 수 있습니다. 예외가 실행 프로세스를 중단하므로 닫히지 않을 수 있는 일부 리소스가 있을 수 있습니다. 이 때finally
블록을 사용할 수 있습니다.finally
블록은 예외가 발생했든 발생하지 않았든 항상 실행됩니다.
예외 처리 예제
testException()
메서드는throw
키워드를 사용하여 예외를 throw합니다. 메서드 시그니처는throws
키워드를 사용하여 호출자에게 throw할 수 있는 예외의 타입을 알려줍니다.main()
메서드에서는try-catch
블록을 사용하여 예외를 처리합니다. 처리하지 않을 경우에는main()
메서드의throws
절을 사용하여 런타임으로 전파합니다.testException(-10)
은 예외 때문에 실행되지 않고finally
블록이 실행됩니다.
printStackTrace()
는 디버깅 목적으로 Exception
클래스에서 유용한 메서드 중 하나입니다.
이 코드는 다음을 출력합니다:
Outputjava.io.FileNotFoundException: Negative Integer -5
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)
주의해야 할 몇 가지 중요한 점:
try
문 없이는catch
또는finally
절을 가질 수 없습니다.- A
try
statement should have eithercatch
block orfinally
block, it can have both blocks. try-catch-finally
블록 사이에는 어떤 코드도 작성할 수 없습니다.try
문 하나에 여러 개의catch
블록을 가질 수 있습니다.try-catch
블록은if-else
문과 유사하게 중첩될 수 있습니다.try-catch
문에는finally
블록 하나만 있을 수 있습니다.
Java 예외 계층
이전에 언급한 대로, 예외가 발생하면 예외 객체가 생성됩니다. Java 예외는 계층적이며 상속을 사용하여 다른 유형의 예외를 분류합니다. Throwable
은 Java 예외 계층의 부모 클래스이며 두 개의 하위 객체인 Error
와 Exception
이 있습니다. Exception
은 Checked Exception
과 Runtime Exception
으로 나뉩니다.
- 에러:
Error
는 응용 프로그램의 범위를 벗어난 예외적인 상황으로, 예측하고 복구할 수 없습니다. 예를 들어, 하드웨어 장애, Java 가상 머신 (JVM) 충돌 또는 메모리 부족 오류입니다. 따라서 별도의Error
계층이 있으며 이러한 상황을 처리하지 않아야 합니다. 일반적인Error
로는OutOfMemoryError
및StackOverflowError
가 있습니다. - 확인된 예외: 확인된
예외
는 프로그램에서 예상할 수 있는 예외적인 상황이며, 이를 복구하려고 시도합니다. 예를 들어,FileNotFoundException
입니다. 이 예외를 catch하여 사용자에게 유용한 메시지를 제공하고 디버깅 목적으로 적절하게 로그에 기록해야 합니다.Exception
은 확인된예외
들의 부모 클래스입니다. 확인된예외
를 던지는 경우, 동일한 메소드에서catch
해야 하거나throws
키워드를 사용하여 호출자에게 전파해야 합니다. - 런타임 예외: 런타임
예외
는 잘못된 프로그래밍으로 인해 발생합니다. 예를 들어, 배열에서 요소를 검색하려고 시도하는 경우 배열의 길이를 먼저 확인해야 합니다. 그렇지 않으면 런타임에서ArrayIndexOutOfBoundException
이 발생할 수 있습니다.RuntimeException
은 모든 런타임예외
들의 부모 클래스입니다. 메소드에서 런타임예외
를throw
하는 경우, 메소드 선언부의throws
절에 지정할 필요가 없습니다. 더 나은 프로그래밍으로 런타임 예외를 피할 수 있습니다.
예외 클래스의 유용한 메소드들
Java Exception
및 그 하위 클래스는 특정한 메소드를 제공하지 않으며, 모든 메소드는 기본 클래스인 Throwable
에 정의되어 있습니다. Exception
클래스는 다른 종류의 Exception
시나리오를 지정하기 위해 생성되었으므로, 우리는 쉽게 원인을 식별하고 해당 유형에 따라 Exception
을 처리할 수 있습니다. Throwable
클래스는 상호 운용성을 위해 Serializable
인터페이스를 구현합니다.
Throwable
클래스의 유용한 일부 메소드는 다음과 같습니다:
- public String getMessage() – 이 메소드는
Throwable
의 메시지String
을 반환하며, 예외를 생성할 때 생성자를 통해 메시지를 제공할 수 있습니다. - public String getLocalizedMessage() – 이 메소드는 하위 클래스가 호출하는 프로그램에 로케일별 메시지를 제공하기 위해 재정의할 수 있도록 제공됩니다. 이 메소드의
Throwable
클래스 구현은 예외 메시지를 반환하기 위해getMessage()
메소드를 사용합니다. - public synchronized Throwable getCause() – 이 메소드는 예외의 원인을 반환하거나 원인이 알려지지 않은 경우
null
을 반환합니다. - public String toString() – 이 메소드는
Throwable
에 대한 정보를String
형식으로 반환하며, 반환된String
은Throwable
클래스의 이름과 로케일별 메시지를 포함합니다. - public void printStackTrace() – 이 메서드는 스택 추적 정보를 표준 오류 스트림에 출력합니다. 이 메서드는 오버로드되어 있으며, 우리는
PrintStream
또는PrintWriter
를 인수로 전달하여 스택 추적 정보를 파일이나 스트림에 작성할 수 있습니다.
Java 7 자동 리소스 관리 및 Catch 블록 개선
한 번의 try
블록에서 많은 예외를 catch
하는 경우, catch
블록 코드는 대부분 오류를 기록하기 위한 중복 코드로 구성되는 것을 알 수 있습니다. Java 7에서 개선된 catch
블록이 도입된 기능 중 하나는 한 번의 catch
블록에서 여러 예외를 처리할 수 있다는 것입니다. 이 기능을 사용한 catch
블록의 예는 다음과 같습니다:
예외 객체가 final이며 catch
블록 내에서 수정할 수 없다는 제약 사항이 있으며, 자세한 분석은 Java 7 Catch 블록 개선에서 읽을 수 있습니다.
대부분의 경우, 리소스를 닫기 위해 finally
블록을 사용합니다. 때로는 이를 닫는 것을 잊고 리소스가 고갈될 때 런타임 예외가 발생합니다. 이러한 예외는 디버그하기 어렵고, 해당 리소스를 사용하는 각 위치를 조사하여 닫았는지 확인해야 할 수도 있습니다. 자바 7에서는 try-with-resources
라는 개선 사항이 도입되었습니다. 이를 사용하면 리소스를 try
문 자체에서 생성하고 try-catch
블록 내에서 사용할 수 있습니다. 실행이 try-catch
블록을 벗어나면 런타임 환경에서 이러한 리소스를 자동으로 닫습니다. 다음은 이 개선 사항을 사용한 try-catch
블록의 예입니다:
A Custom Exception Class Example
자바는 우리가 사용할 수 있는 많은 예외 클래스를 제공하지만, 때로는 사용자 정의 예외 클래스를 만들어야 할 수도 있습니다. 예를 들어, 적절한 메시지와 함께 특정 유형의 예외를 호출자에게 알리기 위해 사용자 정의 예외 클래스를 만들 수 있습니다. 에러 코드와 같은 추적을 위한 사용자 정의 필드를 가질 수도 있습니다. 예를 들어, 텍스트 파일만 처리하는 메서드를 작성한다고 가정해 봅시다. 그렇기 때문에 다른 유형의 파일이 입력으로 전송될 때 적절한 에러 코드를 호출자에게 제공할 수 있습니다.
먼저, MyException
을 생성하세요:
그런 다음, CustomExceptionExample
을 생성하세요:
우리는 다른 메서드에서 얻은 다른 유형의 에러 코드를 처리하기 위해 별도의 메서드를 가질 수 있습니다. 그 중 일부는 사용자에게 알리지 않기 위해 소비되고, 일부는 문제를 사용자에게 알리기 위해 다시 던질 것입니다.
여기서는 Exception
을 확장하여 이 예외가 발생할 때마다 해당 메소드에서 처리되거나 호출자 프로그램에 반환되어야 합니다. RuntimeException
을 확장하면 throws
절에 명시할 필요가 없습니다.
이것은 설계 결정이었습니다. 확인된 Exception
을 사용하는 것은 예외를 기대하고 적절한 조치를 취하여 처리하는 데 개발자를 지원하는 장점이 있습니다.
Java에서 예외 처리에 대한 모범 사례
- 구체적인 예외 사용하기 – 예외 계층의 기본 클래스는 유용한 정보를 제공하지 않으므로 Java에는
IOException
와 같은 예외 클래스와 그 하위 클래스인FileNotFoundException
,EOFException
등이 많이 있습니다. 항상 구체적인 예외 클래스를throw
하고catch
하여 호출자가 예외의 근본 원인을 쉽게 파악하고 처리할 수 있도록 해야 합니다. 이렇게 하면 디버깅이 쉬워지며 클라이언트 응용 프로그램이 예외를 적절하게 처리할 수 있게 도와줍니다. - 조기에 예외 발생하기 또는 Fail-Fast – 예외를 가능한 한 빨리
throw
하려고 해야 합니다. 위의processFile()
메소드를 고려해보면 이 메소드에null
인수를 전달하면 다음과 같은 예외가 발생합니다:
OutputException in thread "main" java.lang.NullPointerException
at java.io.FileInputStream.<init>(FileInputStream.java:134)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
디버깅 중에는 예외의 실제 발생 위치를 확인하기 위해 스택 추적을 주의 깊게 살펴봐야 합니다. 아래와 같이 구현 로직을 변경하여 이러한 예외를 빠르게 확인할 수 있습니다:
그러면 예외 스택 추적은 예외가 발생한 위치를 명확하게 나타내는 메시지를 표시합니다:
Outputcom.journaldev.exceptions.MyException: File name can't be null
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
- 지연된 catch – Java에서는 체크된 예외를 처리하거나 메소드 시그니처에 선언해야 합니다. 때로는 예외를
catch
하고 오류를 로그에 기록하는 경향이 있습니다. 그러나 이러한 방식은 호출자 프로그램이 예외에 대한 알림을 받지 못하기 때문에 해로울 수 있습니다. 예외를 적절히 처리할 수 있는 경우에만 예외를catch
해야 합니다. 예를 들어, 위의 메소드에서는 예외를 호출자 메소드로 다시throw
하여 처리하도록 합니다. 동일한 메소드는 예외를 다른 방식으로 처리하고자 하는 다른 애플리케이션에서 사용될 수 있습니다. 어떤 기능을 구현할 때는 항상 예외를 호출자에게 다시throw
하고 그들이 처리하는 방법을 결정하도록 해야 합니다. - 리소스 닫기 – 예외는 프로그램의 처리를 중단시키므로, 자원을 모두 finally 블록에서 닫거나 Java 7의
try-with-resources
기능을 사용하여 Java 런타임이 자동으로 닫도록 해야 합니다. - 예외 로깅 – 우리는 항상 예외 메시지를 로그에 기록해야하며, 예외를 throw 할 때 명확한 메시지를 제공하여 호출자가 왜 예외가 발생했는지 쉽게 알 수 있도록 해야합니다. 우리는 항상 예외를 소비하고 디버깅을 위한 예외에 대한 의미있는 세부 정보를 제공하지 않는 빈 catch 블록을 피해야합니다.
- 다중 예외에 대한 단일 catch 블록 – 대부분의 경우, 예외 세부 정보를 기록하고 사용자에게 메시지를 제공합니다. 이 경우 Java 7 기능을 사용하여 단일 catch 블록에서 여러 예외를 처리해야합니다. 이 접근 방식은 코드 크기를 줄이고 더 깔끔하게 보일 것입니다.
- 사용자 정의 예외 사용 – 설계 시에 예외 처리 전략을 정의하는 것이 항상 좋습니다. 예외를 throw하고 catch하는 대신, 오류 코드와 함께 사용자 정의 예외를 생성하고 호출 프로그램에서 이러한 오류 코드를 처리할 수 있습니다. 또한 다른 오류 코드를 처리하고 사용하는 유틸리티 메소드를 생성하는 것도 좋은 아이디어입니다.
- 명명 규칙과 패키징 – 사용자 정의 예외를 생성할 때, 예외 클래스임을 이름 자체에서 알 수 있도록 끝에
Exception
이라는 단어를 붙여야합니다. 또한 JDK (Java Development Kit)에서 수행되는 대로 패키징해야합니다. 예를 들어,IOException
은 모든 IO 작업에 대한 기본 예외입니다. - 예외를 신중하게 사용하십시오 – 예외는 비용이 많이 들며 때로는 예외를 던질 필요가 전혀 없을 수도 있으며, 호출 프로그램에 작업이 성공했는지 여부를 나타내는 부울 변수를 반환할 수 있습니다. 이는 작업이 선택적인 경우에 유용하며, 실패로 인해 프로그램이 멈추는 것을 원하지 않을 때 유용합니다. 예를 들어, 제3자 웹 서비스에서 데이터베이스의 주식 시세를 업데이트하는 동안 연결이 실패하면 예외를 던지지 않도록 피할 수 있습니다.
- 던져진 예외에 대해 문서화하기 – 메소드에서 던져지는 예외를 명확히 지정하기 위해 Javadoc
@throws
를 사용하십시오. 다른 애플리케이션이 사용할 수 있는 인터페이스를 제공하는 경우 매우 유용합니다.
결론
이 문서에서는 Java에서의 예외 처리에 대해 배웠습니다. throw
와 throws
에 대해 알게 되었습니다. 또한 try
(그리고 try-with-resources
), catch
, 그리고 finally
블록에 대해 알게 되었습니다.
Source:
https://www.digitalocean.com/community/tutorials/exception-handling-in-java