Java가 정적 초기화 블록에서 확인 된 예외를 발생시키지 않는 이유는 무엇입니까?


135

Java가 정적 초기화 블록에서 확인 된 예외를 발생시키지 않는 이유는 무엇입니까? 이 디자인 결정의 이유는 무엇입니까?


정적 블록에서 어떤 종류의 상황에 어떤 예외를 던지겠습니까?
Kai Huppmann

1
나는 그런 일을하고 싶지 않습니다. 정적 블록 내에서 확인 된 예외를 포착 해야하는 이유를 알고 싶습니다.
missingfaktor

그렇다면 확인 된 예외가 어떻게 처리 될 것으로 기대하십니까? 그것이 당신을 귀찮게한다면, 그냥 던져 new RuntimeException ( "Telling message", e);
Thorbjørn Ravn Andersen 님

18
@ ThorbjørnRavnAndersen Java는 실제로 해당 상황에 대한 예외 유형을 제공합니다. docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d

@ smp7d 아래의 kevinarpe 답변과 StephenC의 의견을 참조하십시오. 정말 멋진 기능이지만 함정이 있습니다!
Benj

답변:


122

소스에서 이러한 확인 된 예외를 처리 할 수 ​​없기 때문입니다. 초기화 프로세스를 제어 할 수 없으며 try-catch로 둘러 쌀 수 있도록 소스에서 static {} 블록을 호출 할 수 없습니다.

점검 된 예외로 표시된 오류를 처리 할 수 ​​없으므로 점검 된 예외 정적 블록을 던지는 것을 허용하지 않기로 결정했습니다.

정적 블록은 검사 된 예외를 발생 시키지 않아야 하지만 여전히 검사되지 않은 / 런타임 예외가 발생하도록 허용합니다. 그러나 위의 이유에 따르면 이들 중 어느 것도 처리 할 수 ​​없습니다.

요약하면,이 제한은 개발자가 응용 프로그램을 복구 할 수없는 오류를 초래할 수있는 무언가를 작성하지 못하게합니다 (또는 적어도 어렵게 만듭니다).


69
실제로이 답변은 정확하지 않습니다. 정적 블록에서 예외를 throw 할 수 있습니다. 할 수없는 일은 확인 된 예외가 정적 블록 에서 전파 되도록 하는 것입니다 .
Stephen C

16
Class.forName (..., true, ...);을 사용하여 동적 클래스로드를 직접 수행하는 경우이 예외를 처리 할 수 ​​있습니다. 물론 이것은 매우 자주 접하는 것이 아닙니다.
LadyCailin

2
static {throw new NullPointerExcpetion ()}-컴파일되지 않습니다!
Kirill Bazarov

4
@KirillBazarov 항상 예외를 발생시키는 정적 이니셜 라이저가있는 클래스는 컴파일되지 않습니다 (왜 그렇게해야합니까?). 던져진 문장을 if- 절로 감싸면 좋습니다.
Kallja

2
이 경우 초기화 프로그램이 어떤 경우에도 정상적으로 완료 될 가능성이 없기 때문에 @Ravisha. try-catch를 사용하면 println에서 예외가 발생하지 않을 수 있으므로 초기화 프로그램이 예외없이 완료 될 수 있습니다. 컴파일 오류가되는 예외의 무조건 결과입니다. 이에 대한 JLS를 참조하십시오. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 그러나 귀하의 경우에 간단한 조건을 추가하여 컴파일러가 여전히 바보 일 수 있습니다.static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801

67

점검 된 예외를 발견하고이를 점검되지 않은 예외로 다시 던져서 문제점을 해결할 수 있습니다. 이 확인되지 않은 예외 클래스는 래퍼로 잘 작동합니다 java.lang.ExceptionInInitializerError.

샘플 코드 :

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

    static {
        try {
            _staticInstance = new YieldCurveConfigHelper();
        }
        catch (IOException | SAXException | JAXBException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}

1
@DK : Java 버전이이 유형의 catch 절을 지원하지 않을 수 있습니다. catch (Exception e) {대신 보십시오 .
kevinarpe

4
그렇습니다, 당신은 이것을 할 수 있습니다, 그러나 그것은 정말로 나쁜 생각입니다. 검사되지 않은 예외는 클래스 및 클래스에 종속 된 다른 클래스를 클래스 언로드로만 해결할 수있는 실패 상태로 만듭니다. 그것은 일반적으로 불가능하며 System.exit(...)(또는 이에 상응하는) 유일한 옵션입니다
Stephen C

1
@StephenC 우리는 "부모"클래스가로드되지 않으면 코드가 작동하지 않기 때문에 종속 클래스를로드 할 필요가 없다고 생각할 수 있습니까? 어쨌든 그런 종속 클래스를로드 해야하는 경우의 예를 제공해 주시겠습니까? 감사합니다
Benj

코드가 동적으로로드하려고하면 어떻습니까? 예를 들어 Class.forName을 통해.
Stephen C

21

다음과 같이 보일 것입니다 (이것은 유효한 Java 코드 가 아닙니다 )

// Not a valid Java Code
static throws SomeCheckedException {
  throw new SomeCheckedException();
}

그러나 당신이 그것을 잡는 위치에 광고하는 방법? 확인 된 예외는 catch가 필요합니다. 클래스를 초기화 할 수있는 (또는 이미 초기화 되었기 때문에 그렇지 않을 수도있는) 예제를 상상해 보시고 복잡성을 주목하기 위해 다른 정적 이니셜 라이저에 예제를 넣습니다.

static {
  try {
     ClassA a = new ClassA();
     Class<ClassB> clazz = Class.forName(ClassB.class);
     String something = ClassC.SOME_STATIC_FIELD;
  } catch (Exception oops) {
     // anybody knows which type might occur?
  }
}

그리고 또 다른 불쾌한 일-

interface MyInterface {
  final static ClassA a = new ClassA();
}

ClassA에 확인 된 예외가 발생하는 정적 초기화 프로그램이 있다고 상상해보십시오.이 경우 MyInterface ( '숨겨진'정적 초기화 프로그램이있는 인터페이스)는 예외를 처리하거나 처리해야합니다. 인터페이스에서 예외 처리? 그대로 두는 것이 좋습니다.


7
main확인 된 예외를 던질 수 있습니다. 분명히 그것들은 처리 할 수 ​​없습니다.
기계 달팽이

@Mechanicalsnail : 재미있는 점. Java의 내 정신 모델에서 main()스택 추적을 사용하여 예외를 인쇄하는 실행중인 스레드에 "매직"(기본) Thread.UncaughtExceptionHandler가 있다고 가정하고 System.err를 호출합니다 System.exit(). 결국이 질문에 대한 대답은 아마도 "Java 디자이너가 그렇게 말했기 때문"일 것입니다.
kevinarpe

7

Java가 정적 초기화 블록에서 확인 된 예외를 발생시키지 않는 이유는 무엇입니까?

기술적으로는이 작업을 수행 할 수 있습니다. 그러나 검사 된 예외는 블록 내에서 발견되어야합니다. 확인 된 예외는 블록 밖으로 전파 될 수 없습니다 .

기술적으로, 검사되지 않은 예외가 정적 초기화 블록 ( 1) 으로부터 전파되도록하는 것도 가능하다 . 그러나 이것을 의도적으로 수행하는 것은 정말 나쁜 생각입니다! 문제는 JVM 자체가 확인되지 않은 예외를 잡아서 랩핑하여 다시로 던진다는 것 ExceptionInInitializerError입니다.

주의 : 그것은 Error일반적인 예외 는 아닙니다. 복구를 시도해서는 안됩니다.

대부분의 경우 예외를 포착 할 수 없습니다.

public class Test {
    static {
        int i = 1;
        if (i == 1) {
            throw new RuntimeException("Bang!");
        }
    }

    public static void main(String[] args) {
        try {
            // stuff
        } catch (Throwable ex) {
            // This won't be executed.
            System.out.println("Caught " + ex);
        }
    }
}

$ java Test
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Bang!
    at Test.<clinit>(Test.java:5)

try ... catch위에서 ExceptionInInitializerError2 를 붙잡을 수있는 곳은 없습니다 .

어떤 경우에는 그것을 잡을 수 있습니다. 예를 들어,을 호출하여 클래스 초기화를 트리거 한 경우 Class.forName(...)호출을에 묶고 a 또는 그 이후를 try잡을 수 있습니다 .ExceptionInInitializerErrorNoClassDefFoundError

당신이 시도 할 경우, 복구 에서 ExceptionInInitializerError당신은 장애물로 실행되기 쉽다. 문제는 오류를 발생시키기 전에 JVM이 문제를 일으킨 클래스를 "실패"로 표시한다는 것입니다. 당신은 단순히 그것을 사용할 수 없습니다. 또한 실패한 클래스에 의존하는 다른 클래스도 초기화하려고하면 실패한 상태가됩니다. 앞으로 나아가는 유일한 방법은 모든 실패한 클래스를 언로드하는 것입니다. 즉 동적으로로드 된 코드에 대한 실현 가능한 3 하지만, 일반적으로는 없습니다.

1-정적 블록 이 검사되지 않은 예외를 무조건 발생시키는 경우 컴파일 오류 입니다.

2 - 당신은 수있는 기본 캐치되지 않는 예외 핸들러를 등록하여 차단을 할 수있을, 그러나 그것은 당신의 "주"스레드를 시작할 수 없습니다 때문에, 복구 할 수 없습니다.

3-실패한 클래스를 복구하려면 해당 클래스를로드 한 클래스 로더를 제거해야합니다.


이 디자인 결정의 이유는 무엇입니까?

처리 할 수없는 예외를 발생시키는 코드를 작성 하지 않도록 프로그래머를 보호합니다 !

앞에서 본 것처럼 정적 초기화 프로그램의 예외는 일반적인 응용 프로그램을 브릭으로 바꿉니다. 언어 디자이너가 할 수있는 최선의 방법은 체크 된 사례를 컴파일 오류로 처리하는 것입니다. (불행히도, 확인되지 않은 예외에 대해서도이를 수행하는 것은 실용적이지 않습니다.)


코드가 정적 초기화 프로그램에서 예외를 발생시키기 위해 "필요한"경우 어떻게해야합니까? 기본적으로 두 가지 대안이 있습니다.

  1. 블록 내 예외에서 (full!) 복구가 가능하면 그렇게하십시오.

  2. 그렇지 않으면 정적 초기화 블록 (또는 정적 변수의 초기화 자)에서 초기화가 발생하지 않도록 코드를 재구성하십시오.


정적 초기화를 수행하지 않도록 코드를 구성하는 방법에 대한 일반적인 권장 사항이 있습니까?
MasterJoe2


1
1) 나는 없다. 2) 그들은 나쁘게 들린다. 내가 남긴 의견을 참조하십시오. 그러나 위의 답변에서 내가 말한 것을 반복하고 있습니다. 내 답변을 읽고 이해하면 해당 "솔루션"이 솔루션이 아니라는 것을 알게 될 것입니다.
Stephen C

4

Java 언어 사양을 살펴보십시오. 정적 초기화 프로그램 이 실패하면 확인 된 예외로 갑자기 완료 될 수있는 경우 컴파일 시간 오류라고 명시되어 있습니다 .


5
그래도 질문에 대답하지 않습니다. 그는 그것이 컴파일 시간 오류 인 이유 를 물었다 .
Winston Smith

JLS는 확인 된 예외 만 언급하기 때문에 RuntimeError를 던질 수 있습니다.
Andreas Dolk

맞습니다. 그러나 결코 스택 트레이스로 보지 못할 것입니다. 따라서 정적 초기화 블록에주의해야합니다.
EJB

2
@EJB : 이것은 올바르지 않습니다. 방금 시도해 보았고 다음 코드는 시각적 스택 public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: whathappenswhenastaticblockthrowsanexception at Main.<clinit>(Main.java:6) Caused by: java.lang.ClassNotFoundException: whathappen...
추적을 제공했습니다

"Caused by"부분은 아마도 더 관심이있는 스택 추적을 보여줍니다.
LadyCailin

2

작성한 코드는 정적 초기화 블록을 호출 할 수 없으므로 checked를 throw하는 것은 유용하지 않습니다 exceptions. 가능하다면 확인 된 예외가 발생했을 때 jvm은 어떻게합니까? Runtimeexceptions전파됩니다.


1
글쎄요, 지금 이해합니다. 이와 같은 질문을 게시하는 것은 매우 어리석은 일이었습니다. 그러나 아아 ... 지금은 그것을 삭제할 수 없습니다. :( 그럼에도 불구하고, 당신의 응답을 한 ...
missingfaktor

1
@fast, 실제로, 확인 된 예외는 RuntimeExceptions로 변환되지 않습니다. 바이트 코드를 직접 작성하면 정적 이니셜 라이저 내에서 확인 된 예외를 마음의 내용으로 던질 수 있습니다. JVM은 예외 점검에 전혀 신경 쓰지 않습니다. 순전히 Java 언어 구문입니다.
안티 모니

0

예를 들면 다음과 같습니다. Spring의 DispatcherServlet (org.springframework.web.servlet.DispatcherServlet)은 확인 된 예외를 포착하고 확인되지 않은 다른 예외를 발생시키는 시나리오를 처리합니다.

static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    }

1
이것은 확인되지 않은 예외를 포착 할 수 없다는 문제에 접근합니다. 대신 클래스와 클래스에 의존하는 다른 클래스를 복구 할 수없는 상태로 만듭니다.
Stephen C

@StephenC-복구 가능한 상태를 원하는 간단한 예를 들어 주시겠습니까?
MasterJoe2

가설 적으로 ... IOException에서 복구하여 응용 프로그램을 계속할 수 있도록하려는 경우. 그렇게하려면 예외를 잡아서 실제로 처리해야합니다 ... 확인되지 않은 예외는 던지지 마십시오.
Stephen C

-5

확인 된 예외도 throw하여 컴파일 할 수 있습니다 ....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}

3
예, 그러나 당신은 정적 블록 내에서 그것을 잡고 있습니다. 정적 블록 내부에서 외부로 확인 된 예외를 throw 할 수 없습니다.
ArtOfWarfare
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.