如何捕获 Java class 字段初始化时发生的异常?

How to catch exception which happens at initialization of Java class field?

如果在 class 字段的初始化过程中发生异常,您将如何捕获它?

例如:

class a{
    int a = 1 / 0;
}

此处异常发生在字段级别。

我知道我能做到:

class a {
    a() {
        try {
            this.a = 1 / 0;
        } catch (Throwable a) {}
    }

    int a;
}

但是出于好奇,是否可以在初始化字段的同时进行?

附加信息:我问这个是因为在我最新的项目中我有一个字段我想初始化为对象的新实例,只写 a = new Object(); 会很酷,但我可以不是因为该特定类型的构造函数抛出已检查的异常。

is it possible to do it while initializing the field?

您可以定义一个方法:

class a {
  int a = aValue();

  private int aValue() {
    try
    {
      return 1/0;
    }
    catch (Throwable a){
      // ...but now you need to return something, or (re)throw an exception.
    }
  }
}

或使用实例初始化器:

class a {
  int a;

  {
    try
    {
      this.a=1/0;
    }
    catch (Throwable a){
      // You don't need to do anything here, unless `a` were `final`.
    }
  }
}

但请注意,实例初始化器被内联到构造函数中(或者至少是任何显式或隐式调用 super(...) 的构造函数,而不是 this(...)),因此这实际上与像问题一样将其放入构造函数中。

要抓住这些真的很难。因此,强烈建议确保静态初始化程序 不会 抛出任何可能想要捕获的东西。 (例如,抛出 OutOfMemoryError 没问题,不太可能有人想要编写代码来捕获它并执行替代路径或尝试解决问题)。

这通常从用方法调用替换静态初始值设定项开始。替换:

static int a; static {a = 1/0; }

与:

static int a = calculateA();

private static int calculateA() {
    return 1/0;
}

当然,这只是整个过程中的一步。将初始化代码(calculateA 方法)移动到一个单独的 class 中,现在您可以自行测试它,甚至 运行 都不会陷入捕获静态初始化期间抛出的异常的问题 class.

一旦你解决了这个问题,你就可以使用这个 'trick' 来解决这个问题。假设 a 的值对于此 class 中的 2 个方法是必需的。然后,'defer'异常:

public class Example {
    private static final int a;
    private static final Throwable aProblem;

    static {
        int a = 0;
        Throwable aProblem = null;
        try {
            a = calculateA();
        } catch (RuntimeException e) {
            aProblem = e;
        }
        Example.a = a;
        Example.aProblem = aProblem;
    }

    private static int calculateA() { return 1/0; }

    public static void apiMethodUsingA1() {
        if (aProblem != null) throw aProblem;
        return a;
    }

    public static void apiMethodUsingA2() {
        if (aProblem != null) throw aProblem;
        return a + 5;
    }
}

如果有none个可用,比如A不是你写的,不能改,那么你必须把Aclass委托为'bad API / crappy library',并且当你面对这样一个库时,你会做你经常做的事情:解决它,接受你需要编写难以维护/丑陋的代码,如果它真的很糟糕,写一个包装器来隔离问题。甚至可以使用反射。

这是将异常隔离到代码块中的一种可靠方法:

package com.foo;

class Example {
    static int a = 1/0;
}

class Main {
  public static void main(String[] args) throws Exception {
    try {
      Class<?> c = Class.forName("com.foo.Example");
    } catch (ExceptionInInitializerError e) {
      System.err.println("Hey I caught it");
      Throwable actualException = e.getCause();
      // do something with it here
      actualException.printStackTrace(); // not this - this is for debugging only!
    }
  }
}