Java의 예외, Part 1 : 예외 처리 기본 사항

Java 예외는 프로그램 실패를 나타내고 처리하는 데 사용되는 라이브러리 유형 및 언어 기능입니다. 소스 코드에서 실패가 어떻게 표현되는지 이해하고 싶었다면 제대로 찾아 오셨습니다. Java 예외에 대한 개요 외에도 객체를 던지고, 실패 할 수있는 코드를 시도하고, 던진 객체를 포착하고, 예외가 발생한 후 Java 코드를 정리하는 Java의 언어 기능을 시작합니다.

이 튜토리얼의 전반부에서는 Java 1.0 이후에 있었던 기본 언어 기능 및 라이브러리 유형에 대해 학습합니다. 후반부에서는 최신 Java 버전에 도입 된 고급 기능에 대해 알아 봅니다.

이 자습서의 코드 예제는 JDK 12와 호환됩니다.

다운로드 코드 받기이 자습서에서 예제 응용 프로그램의 소스 코드를 다운로드합니다. JavaWorld를 위해 Jeff Friesen이 만들었습니다.

Java 예외 란 무엇입니까?

Java 프로그램의 정상적인 동작이 예기치 않은 동작으로 인해 중단되면 실패가 발생합니다. 이 차이를 예외라고 합니다. 예를 들어, 프로그램이 내용을 읽기 위해 파일을 열려고 시도하지만 파일이 존재하지 않습니다. Java는 예외를 몇 가지 유형으로 분류하므로 각각을 고려해 보겠습니다.

확인 된 예외

Java는 외부 요인 (예 : 누락 된 파일)에서 발생하는 예외확인 된 예외로 분류 합니다. Java 컴파일러는 이러한 예외가 발생하는 곳에서 처리 (수정)되거나 다른 곳에서 처리되도록 문서화 되었는지 확인합니다 .

예외 처리기

예외 핸들러는 예외를 처리하는 코드의 시퀀스이다. 컨텍스트 (예외가 발생했을 때 범위에 있던 변수에서 저장된 값을 읽는다는 의미)를 조사한 다음 학습 한 내용을 사용하여 Java 프로그램을 정상적인 동작의 흐름으로 복원합니다. 예를 들어 예외 처리기는 저장된 파일 이름을 읽고 사용자에게 누락 된 파일을 교체하라는 메시지를 표시 할 수 있습니다.

런타임 (확인되지 ​​않은) 예외

프로그램이 정수를 정수 0으로 나누려고한다고 가정합니다.이 불가능 성은 다른 종류의 예외, 즉 런타임 예외를 보여 줍니다. 확인 된 예외와 달리 런타임 예외는 일반적으로 잘못 작성된 소스 코드에서 발생하므로 프로그래머가 수정해야합니다. 컴파일러는 런타임 예외가 처리되거나 다른 곳에서 처리되도록 문서화되었는지 확인하지 않기 때문에 런타임 예외를 확인되지 않은 예외 로 생각할 수 있습니다 .

런타임 예외 정보

런타임 예외를 처리하도록 프로그램을 수정할 수 있지만 소스 코드를 수정하는 것이 좋습니다. 런타임 예외는 종종 유효하지 않은 인수를 라이브러리의 메소드에 전달하는 데서 발생합니다. 버그가있는 호출 코드를 수정해야합니다.

오류

일부 예외는 프로그램의 실행을 계속할 수있는 능력을 위태롭게하기 때문에 매우 심각합니다. 예를 들어, 프로그램이 JVM에서 메모리를 할당하려고하지만 요청을 충족 할 충분한 여유 메모리가 없습니다. 프로그램이 Class.forName()메소드 호출을 통해 클래스 파일을로드하려고 시도 하지만 클래스 파일이 손상 되었을 때 또 다른 심각한 상황이 발생합니다 . 이러한 종류의 예외를 오류 라고 합니다 . JVM이 복구 할 수 없을 수도 있으므로 오류를 직접 처리하지 마십시오.

소스 코드의 예외

예외는 소스 코드에서 오류 코드 또는 객체 로 표시 될 수 있습니다 . 두 가지를 모두 소개하고 왜 사물이 우수한지 보여 드리겠습니다.

오류 코드 대 개체

C와 같은 프로그래밍 언어는 정수 기반 오류 코드 를 사용하여 실패 및 실패 이유 (예 : 예외)를 나타냅니다. 다음은 몇 가지 예입니다.

if (chdir("C:\\temp")) printf("Unable to change to temp directory: %d\n", errno); FILE *fp = fopen("C:\\temp\\foo"); if (fp == NULL) printf("Unable to open foo: %d\n", errno);

C의 chdir()(디렉토리 변경) 함수는 성공하면 0, 실패하면 -1의 정수를 반환합니다. 유사하게, C의 fopen()(파일 열기) 함수는 성공시 구조에 대한 널이 아닌 포인터 (정수 주소)를 FILE리턴 NULL하고 실패시 널 (0) 포인터 (상수로 표시됨 )를 리턴 합니다. 두 경우 모두 실패를 유발 한 예외를 식별하려면 전역 errno변수의 정수 기반 오류 코드를 읽어야합니다 .

오류 코드에는 몇 가지 문제가 있습니다.

  • 정수는 의미가 없습니다. 그들은 그들이 나타내는 예외를 설명하지 않습니다. 예를 들어 6은 무엇을 의미합니까?
  • 컨텍스트를 오류 코드와 연결하는 것은 어색합니다. 예를 들어 열 수없는 파일의 이름을 출력하려고 할 수 있지만 파일 이름을 어디에 저장할 것인가?
  • 정수는 임의적이므로 소스 코드를 읽을 때 혼동을 일으킬 수 있습니다. 예를 들어, 실패를 테스트하는 대신 if (!chdir("C:\\temp"))( !NOT을 의미 함)을 지정하는 if (chdir("C:\\temp"))것이 더 명확합니다. 그러나 성공을 나타 내기 위해 0이 선택되었으므로 if (chdir("C:\\temp"))실패를 테스트하려면 지정해야합니다.
  • 오류 코드는 무시하기가 너무 쉽기 때문에 버그가 발생할 수 있습니다. 예를 들어, 프로그래머 chdir("C:\\temp");if (fp == NULL)검사를 지정 하고 무시할 수 있습니다. 또한 프로그래머는 errno. 오류를 테스트하지 않으면 두 함수 중 하나가 오류 표시기를 반환 할 때 프로그램이 비정상적으로 작동합니다.

이러한 문제를 해결하기 위해 Java는 예외 처리에 대한 새로운 접근 방식을 채택했습니다. Java에서 우리는 예외를 설명하는 객체를 이러한 객체를 던지고 포착하는 메커니즘과 결합합니다. 예외를 표시하기 위해 객체와 오류 코드를 사용하면 다음과 같은 몇 가지 이점이 있습니다.

  • 의미있는 이름을 가진 클래스에서 개체를 만들 수 있습니다. 예를 들어, FileNotFoundException( java.io패키지에서) 6보다 더 의미가 있습니다.
  • 객체는 다양한 필드에 컨텍스트를 저장할 수 있습니다. 예를 들어 메시지, 열 수없는 파일 이름, 구문 분석 작업이 실패한 가장 최근 위치 및 / 또는 개체의 필드에있는 기타 항목을 저장할 수 있습니다.
  • if실패를 테스트하기 위해 문을 사용하지 않습니다 . 대신 예외 개체는 프로그램 코드와는 별도의 처리기로 throw됩니다. 결과적으로 소스 코드는 읽기 쉽고 버그가 적습니다.

Throwable 및 그 하위 클래스

Java는 다양한 종류의 예외를 나타내는 클래스 계층 구조를 제공합니다. 이러한 클래스는에 뿌리를두고있다 java.lang패키지의 Throwable그것과 함께, 클래스 Exception, RuntimeExceptionError서브 클래스.

Throwable예외가 관련된 궁극의 슈퍼 클래스입니다. 생성 된 객체 Throwable와 그 하위 클래스 만 던져 질 수 있습니다 (그리고 나중에 잡힐 수 있습니다). 이러한 개체를 throwables 라고 합니다 .

A Throwable object is associated with a detail message that describes an exception. Several constructors, including the pair described below, are provided to create a Throwable object with or without a detail message:

  • Throwable() creates a Throwable with no detail message. This constructor is appropriate for situations where there is no context. For example, you only want to know that a stack is empty or full.
  • Throwable(String message) creates a Throwable with message as the detail message. This message can be output to the user and/or logged.

Throwable provides the String getMessage() method to return the detail message. It also provides additional useful methods, which I'll introduce later.

The Exception class

Throwable has two direct subclasses. One of these subclasses is Exception, which describes an exception arising from an external factor (such as attempting to read from a nonexistent file). Exception declares the same constructors (with identical parameter lists) as Throwable, and each constructor invokes its Throwable counterpart. Exception inherits Throwable's methods; it declares no new methods.

Java provides many exception classes that directly subclass Exception. Here are three examples:

  • CloneNotSupportedException signals an attempt to clone an object whose class doesn't implement the Cloneable interface. Both types are in the java.lang package.
  • IOException signals that some kind of I/O failure has occurred. This type is located in the java.io package.
  • ParseException signals that a failure has occurred while parsing text. This type can be found in the java.text package.

Notice that each Exception subclass name ends with the word Exception. This convention makes it easy to identify the class's purpose.

You'll typically subclass Exception (or one of its subclasses) with your own exception classes (whose names should end with Exception). Here are a couple of custom subclass examples:

public class StackFullException extends Exception { } public class EmptyDirectoryException extends Exception { private String directoryName; public EmptyDirectoryException(String message, String directoryName) { super(message); this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } }

The first example describes an exception class that doesn't require a detail message. It's default noargument constructor invokes Exception(), which invokes Throwable().

The second example describes an exception class whose constructor requires a detail message and the name of the empty directory. The constructor invokes Exception(String message), which invokes Throwable(String message).

Objects instantiated from Exception or one of its subclasses (except for RuntimeException or one of its subclasses) are checked exceptions.

The RuntimeException class

Exception is directly subclassed by RuntimeException, which describes an exception most likely arising from poorly written code. RuntimeException declares the same constructors (with identical parameter lists) as Exception, and each constructor invokes its Exception counterpart. RuntimeException inherits Throwable's methods. It declares no new methods.

Java provides many exception classes that directly subclass RuntimeException. The following examples are all members of the java.lang package:

  • ArithmeticException signals an illegal arithmetic operation, such as attempting to divide an integer by 0.
  • IllegalArgumentException signals that an illegal or inappropriate argument has been passed to a method.
  • NullPointerException signals an attempt to invoke a method or access an instance field via the null reference.

Objects instantiated from RuntimeException or one of its subclasses are unchecked exceptions.

The Error class

Throwable's other direct subclass is Error, which describes a serious (even abnormal) problem that a reasonable application should not try to handle--such as running out of memory, overflowing the JVM's stack, or attempting to load a class that cannot be found. Like Exception, Error declares identical constructors to Throwable, inherits Throwable's methods, and doesn't declare any of its own methods.

You can identify Error subclasses from the convention that their class names end with Error. Examples include OutOfMemoryError, LinkageError, and StackOverflowError. All three types belong to the java.lang package.

Throwing exceptions

A C library function notifies calling code of an exception by setting the global errno variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:

  1. Use the throw statement to throw an exception object.
  2. Use the throws clause to inform the compiler.

Later sections will focus on catching exceptions and cleaning up after them, but first let's learn more about throwables.

The throw statement

Java provides the throw statement to throw an object that describes an exception. Here's the syntax of the throw statement :

throw throwable;

The object identified by throwable is an instance of Throwable or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception or RuntimeException. Here are a couple of examples:

throw new FileNotFoundException("unable to find file " + filename); throw new IllegalArgumentException("argument passed to count is less than zero");

The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method's handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.

The throws clause

You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws clause to the method's header. This clause has the following syntax:

throws checkedExceptionClassName (, checkedExceptionClassName)*

A throws clause consists of keyword throws followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:

public static void main(String[] args) throws ClassNotFoundException { if (args.length != 1) { System.err.println("usage: java ... classfile"); return; } Class.forName(args[0]); }

This example attempts to load a classfile identified by a command-line argument. If Class.forName() cannot find the classfile, it throws a java.lang.ClassNotFoundException object, which is a checked exception.

Checked exception controversy

The throws clause and checked exceptions are controversial. Many developers hate being forced to specify throws or handle the checked exception(s). Learn more about this from my Are checked exceptions good or bad? blog post.