웹사이트 검색

Java의 예외 처리


소개

예외는 프로그램 실행 중에 발생할 수 있는 오류 이벤트이며 정상적인 흐름을 방해합니다. Java는 Java 예외 처리로 알려진 예외 시나리오를 처리하는 강력하고 객체 지향적인 방법을 제공합니다.

Java의 예외는 사용자가 입력한 잘못된 데이터, 하드웨어 오류, 네트워크 연결 오류 또는 다운된 데이터베이스 서버와 같은 다양한 상황에서 발생할 수 있습니다. 특정 예외 시나리오에서 수행할 작업을 지정하는 코드를 예외 처리라고 합니다.

예외 던지기 및 잡기

Java는 명령문을 실행하는 동안 오류가 발생하면 예외 객체를 생성합니다. 예외 개체에는 메서드 계층 구조, 예외가 발생한 줄 번호 및 예외 유형과 같은 많은 디버깅 정보가 포함되어 있습니다.

메서드에서 예외가 발생하면 예외 개체를 생성하여 런타임 환경에 전달하는 과정을 "예외 발생\이라고 합니다. 프로그램의 정상적인 흐름이 중단되고 Java Runtime이 실행됩니다. 환경(JRE)은 예외에 대한 핸들러를 찾으려고 시도합니다. 예외 핸들러는 예외 객체를 처리할 수 있는 코드 블록입니다.

  • 예외 처리기를 찾는 논리는 오류가 발생한 메서드에서 검색하는 것부터 시작됩니다.
  • 적절한 핸들러가 없으면 호출자 메서드로 이동합니다.
  • 등.

따라서 메서드의 호출 스택이 A->B->C이고 메서드 C에서 예외가 발생하면 적절한 핸들러에 대한 검색이 에서 이동합니다. C->B->A.

적절한 예외 처리기가 발견되면 처리를 위해 예외 개체가 처리기로 전달됩니다. 핸들러는 "예외 포착\이라고 합니다. 적절한 예외 핸들러가 없으면 프로그램이 종료되고 예외에 대한 정보를 콘솔에 출력합니다.

Java 예외 처리 프레임워크는 런타임 오류만 처리하는 데 사용됩니다. 컴파일 시간 오류는 프로그램이 실행되지 않는 코드를 작성하는 개발자가 수정해야 합니다.

Java 예외 처리 키워드

Java는 예외 처리 목적으로 특정 키워드를 제공합니다.

  1. throw – 오류가 발생하면 예외 개체가 생성되고 Java 런타임이 이를 처리하기 위해 처리를 시작한다는 것을 알고 있습니다. 때로는 코드에서 명시적으로 예외를 생성해야 할 수도 있습니다. 예를 들어 사용자 인증 프로그램에서 암호가 null인 경우 클라이언트에 예외를 발생시켜야 합니다. throw 키워드는 예외를 처리하기 위해 런타임에 예외를 발생시키는 데 사용됩니다.
  2. throws – 메서드에서 예외를 던지고 처리하지 않을 때 메서드 시그니처에 throws 키워드를 사용하여 호출자 프로그램이 던질 수 있는 예외를 알려야 합니다. 방법으로. 호출자 메서드는 이러한 예외를 처리하거나 throws 키워드를 사용하여 호출자 메서드에 전파할 수 있습니다. throws 절에서 여러 예외를 제공할 수 있으며 main() 메서드와 함께 사용할 수도 있습니다.
  3. try-catch – 코드에서 예외 처리를 위해 try-catch 블록을 사용합니다. try는 블록의 시작이고 catch는 예외를 처리하기 위해 try 블록의 끝에 있습니다. try 블록이 있는 여러 catch 블록을 가질 수 있습니다. try-catch 블록도 중첩될 수 있습니다. catch 블록에는 Exception 유형의 매개변수가 필요합니다.
  4. finally – finally 블록은 선택 사항이며 try-catch 블록과 함께만 사용할 수 있습니다. 예외는 실행 프로세스를 중단시키므로 닫히지 않을 일부 리소스가 열려 있을 수 있으므로 finally 블록을 사용할 수 있습니다. finally 블록은 예외 발생 여부에 관계없이 항상 실행됩니다.

예외 처리 예

package com.journaldev.exceptions;

import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionHandling {

	public static void main(String[] args) throws FileNotFoundException, IOException {
		try {
			testException(-5);
			testException(-10);
		} catch(FileNotFoundException e) {
			e.printStackTrace();
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			System.out.println("Releasing resources");
		}
		testException(15);
	}

	public static void testException(int i) throws FileNotFoundException, IOException {
		if (i < 0) {
			FileNotFoundException myException = new FileNotFoundException("Negative Integer " + i);
			throw myException;
		} else if (i > 10) {
			throw new IOException("Only supported for index 0 to 10");
		}
	}
}

  • testException() 메서드는 throw 키워드를 사용하여 예외를 발생시킵니다. 메서드 서명은 throws 키워드를 사용하여 호출자가 발생할 수 있는 예외 유형을 알립니다.
  • main() 메서드에서 main() 메서드의 try-catch 블록을 사용하여 예외를 처리하고 있습니다. 처리하지 않을 때는 main() 메서드의 throws 절을 사용하여 런타임에 전파합니다.
  • testException(-10)은 예외 때문에 절대 실행되지 않고 finally 블록이 실행됩니다.

printStackTrace()는 디버깅 목적으로 Exception 클래스의 유용한 메서드 중 하나입니다.

이 코드는 다음을 출력합니다.

Output
java.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 절을 사용할 수 없습니다.
  • try 문에는 catch 블록 또는 finally 블록이 있어야 하며 두 블록을 모두 가질 수 있습니다.
  • try-catch-finally 블록 사이에는 코드를 작성할 수 없습니다.
  • 단일 try 문으로 여러 catch 블록을 가질 수 있습니다.
  • try-catch 블록은 if-else 문과 유사하게 중첩될 수 있습니다.
  • try-catch 문이 있는 finally 블록은 하나만 가질 수 있습니다.

Java 예외 계층

앞에서 설명한 것처럼 예외가 발생하면 예외 개체가 생성됩니다. Java 예외는 계층적이며 상속은 다양한 유형의 예외를 분류하는 데 사용됩니다. Throwable은 Java Exceptions Hierarchy의 상위 클래스이며 ErrorException이라는 두 개의 하위 개체가 있습니다. 예외는 확인된 예외와 런타임 예외로 더 나뉩니다.

  1. 오류: 오류는 적용 범위를 벗어나는 예외적인 시나리오이며 예상하고 복구할 수 없습니다. 예를 들어 하드웨어 오류, JVM(Java Virtual Machine) 충돌 또는 메모리 부족 오류가 있습니다. 이것이 우리가 오류의 별도 계층 구조를 갖고 이러한 상황을 처리하려고 시도해서는 안 되는 이유입니다. 일반적인 오류 중 일부는 OutOfMemoryErrorStackOverflowError입니다.
  2. 확인된 예외: 확인된 예외는 프로그램에서 예상하고 복구를 시도할 수 있는 예외적인 시나리오입니다. 예: FileNotFoundException. 우리는 이 예외를 포착하고 사용자에게 유용한 메시지를 제공하고 디버깅 목적으로 적절하게 기록해야 합니다. Exception은 모든 Checked Exception의 상위 클래스입니다. Checked 예외를 발생시키는 경우 동일한 메서드에서 catch하거나 throws를 사용하여 호출자에게 전파해야 합니다. 키워드.
  3. 런타임 예외: 런타임 예외는 잘못된 프로그래밍으로 인해 발생합니다. 예를 들어 배열에서 요소를 검색하려고 합니다. 요소를 검색하기 전에 먼저 배열의 길이를 확인해야 합니다. 그렇지 않으면 런타임에 ArrayIndexOutOfBoundException이 발생할 수 있습니다. RuntimeException은 모든 Runtime Exception의 상위 클래스입니다. 메서드에서 런타임 예외throw하는 경우 메서드 시그니처 throws 절에 이를 지정할 필요가 없습니다. 더 나은 프로그래밍으로 런타임 예외를 피할 수 있습니다.

예외 클래스의 몇 가지 유용한 메서드

Java Exception 및 모든 하위 클래스는 특정 메서드를 제공하지 않으며 모든 메서드는 기본 클래스인 Throwable에서 정의됩니다. Exception 클래스는 근본 원인을 쉽게 식별하고 유형에 따라 Exception을 처리할 수 있도록 다양한 종류의 Exception 시나리오를 지정하기 위해 생성됩니다. . Throwable 클래스는 상호 운용성을 위해 Serializable 인터페이스를 구현합니다.

Throwable 클래스의 몇 가지 유용한 메서드는 다음과 같습니다.

  1. public String getMessage() – 이 메서드는 ThrowableString 메시지를 반환하며 생성자를 통해 예외를 생성하는 동안 메시지를 제공할 수 있습니다.
  2. public String getLocalizedMessage() – 이 메서드는 서브클래스가 호출 프로그램에 로캘별 메시지를 제공하기 위해 재정의할 수 있도록 제공됩니다. 이 메서드의 Throwable 클래스 구현은 getMessage() 메서드를 사용하여 예외 메시지를 반환합니다.
  3. public synchronized Throwable getCause() – 이 메서드는 예외의 원인을 반환하거나 원인을 알 수 없는 경우 null을 반환합니다.
  4. public String toString() – 이 메서드는 Throwable에 대한 정보를 String 형식으로 반환하고 반환된 String에는 <Throwable 클래스 및 현지화된 메시지.
  5. public void printStackTrace() – 이 메서드는 표준 오류 스트림에 스택 추적 정보를 인쇄합니다. 이 메서드는 오버로드되며 PrintStream 또는 PrintWriter를 스택 추적 정보를 파일 또는 스트림에 쓰기 위한 인수.

Java 7 자동 리소스 관리 및 Catch 블록 개선 사항

단일 try 블록에서 많은 예외를 catch하는 경우 catch 블록 코드가 대부분 중복 코드로 구성되어 있음을 알 수 있습니다. 오류를 기록하십시오. Java 7에서 기능 중 하나는 단일 catch 블록에서 여러 예외를 catch할 수 있는 개선된 catch 블록이었습니다. 다음은 이 기능이 있는 catch 블록의 예입니다.

catch (IOException | SQLException ex) {
    logger.error(ex);
    throw new MyException(ex.getMessage());
}

예외 개체가 최종적이며 catch 블록 내에서 수정할 수 없는 것과 같은 몇 가지 제약이 있습니다. Java 7 Catch 블록 개선에서 전체 분석을 읽어보세요.

대부분의 경우 리소스를 닫기 위해 finally 블록을 사용합니다. 리소스가 소진되면 닫는 것을 잊고 런타임 예외가 발생하는 경우가 있습니다. 이러한 예외는 디버깅하기 어려우며 해당 리소스를 사용하고 있는 각 위치를 조사하여 리소스를 닫고 있는지 확인해야 할 수도 있습니다. Java 7에서 개선 사항 중 하나는 try-with-resources였습니다. try 문 자체에서 리소스를 생성하고 try-catch 내에서 사용할 수 있습니다. 블록. 실행이 try-catch 블록에서 나오면 런타임 환경은 이러한 리소스를 자동으로 닫습니다. 다음은 이 개선 사항이 포함된 try-catch 블록의 예입니다.

try (MyResource mr = new MyResource()) {
	System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
	e.printStackTrace();
}

사용자 지정 예외 클래스 예제

Java는 우리가 사용할 수 있는 많은 예외 클래스를 제공하지만 때로는 사용자 지정 예외 클래스를 만들어야 할 수도 있습니다. 예를 들어 적절한 메시지와 함께 특정 유형의 예외에 대해 호출자에게 알립니다. 오류 코드와 같은 추적을 위한 사용자 정의 필드를 가질 수 있습니다. 예를 들어 텍스트 파일만 처리하는 메서드를 작성하여 다른 유형의 파일이 입력으로 전송될 때 호출자에게 적절한 오류 코드를 제공할 수 있다고 가정해 보겠습니다.

먼저 MyException을 만듭니다.

package com.journaldev.exceptions;

public class MyException extends Exception {

	private static final long serialVersionUID = 4664456874499611218L;

	private String errorCode = "Unknown_Exception";

	public MyException(String message, String errorCode) {
		super(message);
		this.errorCode=errorCode;
	}

	public String getErrorCode() {
		return this.errorCode;
	}
}

그런 다음 CustomExceptionExample을 만듭니다.

package com.journaldev.exceptions;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class CustomExceptionExample {

	public static void main(String[] args) throws MyException {
		try {
			processFile("file.txt");
		} catch (MyException e) {
			processErrorCodes(e);
		}
	}

	private static void processErrorCodes(MyException e) throws MyException {
		switch (e.getErrorCode()) {
			case "BAD_FILE_TYPE":
				System.out.println("Bad File Type, notify user");
				throw e;
			case "FILE_NOT_FOUND_EXCEPTION":
				System.out.println("File Not Found, notify user");
				throw e;
			case "FILE_CLOSE_EXCEPTION":
				System.out.println("File Close failed, just log it.");
				break;
			default:
				System.out.println("Unknown exception occured, lets log it for further debugging." + e.getMessage());
				e.printStackTrace();
		}
	}

	private static void processFile(String file) throws MyException {
		InputStream fis = null;

		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			throw new MyException(e.getMessage(), "FILE_NOT_FOUND_EXCEPTION");
		} finally {
			try {
				if (fis != null) fis.close();
			} catch (IOException e) {
				throw new MyException(e.getMessage(), "FILE_CLOSE_EXCEPTION");
			}
		}
	}
}

우리는 다른 방법에서 얻은 다른 유형의 오류 코드를 처리하는 별도의 방법을 가질 수 있습니다. 그들 중 일부는 사용자에게 알리고 싶지 않기 때문에 소비되거나 일부는 사용자에게 문제를 알리기 위해 되돌아갑니다.

여기서는 Exception을 확장하여 이 예외가 생성될 때마다 메서드에서 처리하거나 호출자 프로그램으로 반환해야 합니다. RuntimeException을 확장하면 throws 절에 지정할 필요가 없습니다.

이것은 디자인 결정이었습니다. 확인된 예외를 사용하면 개발자가 예상할 수 있는 예외를 이해하고 이를 처리하기 위해 적절한 조치를 취하는 데 도움이 되는 이점이 있습니다.

Java의 예외 처리 모범 사례

  • 특정 예외 사용 - 예외 계층 구조의 기본 클래스는 유용한 정보를 제공하지 않습니다. 이것이 Java에 IOException과 같은 많은 예외 클래스가 있고 FileNotFoundException 과 같은 추가 하위 클래스가 있는 이유입니다. , EOFException 등 호출자가 예외의 근본 원인을 쉽게 알 수 있도록 특정 예외 클래스를 항상 throwcatch해야 합니다. 처리합니다. 이렇게 하면 디버깅이 쉬워지고 클라이언트 애플리케이션이 예외를 적절하게 처리하는 데 도움이 됩니다.
  • Throw Early or Fail-Fast – 예외를 가능한 빨리 throw해야 합니다. 위의 processFile() 메서드를 고려하십시오. null 인수를 이 메서드에 전달하면 다음 예외가 발생합니다.

Output
Exception 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)

디버깅하는 동안 예외의 실제 위치를 식별하기 위해 스택 추적을 주의 깊게 살펴봐야 합니다. 아래와 같이 조기에 이러한 예외를 확인하도록 구현 논리를 변경하는 경우:

private static void processFile(String file) throws MyException {
	if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");

	// ... further processing
}

그런 다음 예외 스택 추적은 명확한 메시지와 함께 예외가 발생한 위치를 나타냅니다.

Output
com.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 Late – Java는 확인된 예외를 처리하거나 메서드 서명에서 선언하도록 강제하므로 때때로 개발자는 예외를 catch하고 오류를 기록하는 경향이 있습니다. 그러나 이 방법은 호출자 프로그램이 예외에 대한 알림을 받지 못하기 때문에 해롭습니다. 적절하게 처리할 수 있는 경우에만 예외를 catch해야 합니다. 예를 들어, 위의 메서드에서 예외를 처리하기 위해 호출자 메서드로 다시 throw하고 있습니다. 다른 방식으로 예외를 처리하려는 다른 애플리케이션에서 동일한 방법을 사용할 수 있습니다. 기능을 구현하는 동안 항상 호출자에게 예외를 다시 던지고 처리 방법을 결정하도록 해야 합니다.
  • 리소스 닫기 – 예외로 인해 프로그램 처리가 중단되기 때문에 finally 블록에서 모든 리소스를 닫거나 Java 런타임이 리소스를 닫을 수 있도록 Java 7 try-with-resources 향상 기능을 사용해야 합니다.
  • 예외 기록 – 우리는 항상 예외 메시지를 기록해야 하며 throw예외는 호출자가 예외가 발생한 이유를 쉽게 알 수 있도록 명확한 메시지를 제공해야 합니다. 우리는 항상 예외를 소비하고 디버깅을 위한 의미 있는 예외 세부 정보를 제공하지 않는 빈 catch 블록을 피해야 합니다.
  • 여러 예외에 대한 단일 catch 블록 – 대부분의 경우 예외 세부 정보를 기록하고 사용자에게 메시지를 제공합니다. 이 경우 단일 catch 에서 여러 예외를 처리하기 위해 Java 7 기능을 사용해야 합니다. 블록. 이 접근 방식을 사용하면 코드 크기가 줄어들고 깔끔해 보일 것입니다.
  • 사용자 지정 예외 사용 – 디자인 타임에 예외 처리 전략을 정의하는 것이 항상 더 좋으며 여러 예외를 던지하고 잡는보다는 사용자 지정 예외를 만들 수 있습니다. 오류 코드가 있는 예외이며 호출자 프로그램은 이러한 오류 코드를 처리할 수 있습니다. 다양한 오류 코드를 처리하고 사용하는 유틸리티 메서드를 만드는 것도 좋은 생각입니다.
  • 명명 규칙 및 패키징 – 사용자 지정 예외를 생성할 때 Exception으로 끝나야 이름 자체에서 예외 클래스라는 것이 명확해집니다. 또한 JDK(Java Development Kit)에서와 같이 패키징해야 합니다. 예를 들어 IOException은 모든 IO 작업의 기본 예외입니다.
  • 예외를 신중하게 사용 - 예외는 비용이 많이 들고 때로는 예외를 throw할 필요가 전혀 없으며 부울 변수를 호출자 프로그램에 반환하여 작업의 성공 여부를 나타낼 수 있습니다. 이는 작업이 선택 사항이고 프로그램이 실패하여 중단되는 것을 원하지 않는 경우에 유용합니다. 예를 들어 타사 웹 서비스에서 데이터베이스의 주식 시세를 업데이트하는 동안 연결이 실패하면 예외가 발생하지 않도록 할 수 있습니다.
  • 발생한 예외 문서화 - Javadoc @throws를 사용하여 메서드에서 발생한 예외를 명확하게 지정합니다. 다른 응용 프로그램에서 사용할 인터페이스를 제공할 때 매우 유용합니다.

결론

이 기사에서는 Java의 예외 처리에 대해 배웠습니다. throwthrows에 대해 배웠습니다. 또한 try(및 try-with-resources), catchfinally 블록에 대해서도 배웠습니다.