如何处理抛出已检查异常的静态final字段初始化程序

51

我遇到这样一个问题,我想声明一个带有初始化语句的static final字段,并且该初始化语句会声明抛出已检查异常。通常情况下,它看起来像这样:

public static final ObjectName OBJECT_NAME = new ObjectName("foo:type=bar");

我的问题在于ObjectName构造函数可能会抛出各种已检查的异常,而我不关心它们(因为我知道我的名称是有效的,如果它不是有效的话,它彻底崩溃也没关系)。Java编译器不允许我忽略这个异常(因为它是一个已检查的异常),我不想使用以下方式:

public static final ObjectName OBJECT_NAME;
static {
    try {
        OBJECT_NAME = new ObjectName("foo:type=bar");
    } catch (final Exception ex) {
        throw new RuntimeException("Failed to create ObjectName instance in static block.", ex);
    }
}
因为静态代码块真的非常难以阅读。有人有关于如何以一种好的、清晰的方式处理这种情况的建议吗?

我的个人解决方案是抛出一个名为CheckedExceptionsAreAPainInTheAssSometimesException的运行时异常。程序将会崩溃。 - Bassinator
4个回答

53

如果您不喜欢静态块(有些人不喜欢),则另一种选择是使用静态方法。我IRC,Josh Bloch推荐这样做(经过快速检查,显然不在Effective Java中)。

public static final ObjectName OBJECT_NAME = createObjectName("foo:type=bar");

private static ObjectName createObjectName(final String name) {
    try {
        return new ObjectName(name);
    } catch (final SomeException exc) {
        throw new Error(exc);
    }  
}

或者:

public static final ObjectName OBJECT_NAME = createObjectName();

private static ObjectName createObjectName() {
    try {
        return new ObjectName("foo:type=bar");
    } catch (final SomeException exc) {
        throw new Error(exc);
    }  
}

(编辑:更正第二个示例,使其返回方法而不是分配static。)


2
我之前没有想过这个,但现在看到了,我可以百分之百确定我很久以前就用过这种方法。我会使用它,因为我不喜欢静态块,而且我希望我的代码对初学者来说易读(毕竟你永远不知道谁会在你之后维护你的代码 :))。 - Romain
给我一个编译器错误,说“必须返回类型为ObjectName的结果”——一个简单的解决方案是在catch块中使用return null吗?但这样调试有点奇怪。 - Don Cheadle
我认为你指的是第59条款:避免不必要地使用受检异常(《Effective Java》第二版)。在那一条中,Bloch建议代码作者考虑如果其客户端“遭遇异常后是否可以采取某些有用的行动”。这与本例不同,本例的问题不在于所抛出的异常是否合法,而在于如何最佳处理异常。从查看java.lang.Error文档来看,我感觉在此处抛出错误并非最佳选择。 - nullstellensatz
“错误”应该用于指示程序存在“严重问题”的“异常情况”。在Bloch给出的例子中,只有在永远不应该运行的代码中才会抛出AssertionError,例如在switch块的default部分或者一个不可实例化类的私有构造函数中。在我看来,考虑到这种异常情况并不是不可能发生的,抛出RuntimeException比抛出Error更为合适。 - nullstellensatz
@nullstellensatz 这是六年前的事了!我当时提到的是 Bloch 的建议(在过去十年中可能已经改变),即创建静态方法进行构造,而不是使用实例初始化器。在 Kindle 上搜索《Effective Java》第二版,似乎并没有出现这本书。/ 抛出异常的意图是表示类已经损坏,必须死亡。那是一个错误。(将被包装在java.lang.ExceptionInInitializerError中。)Error也比无意义的RuntimeException更易读。 - Tom Hawtin - tackline

18

你的代码完全有效。我并不觉得它难以理解。其他方式只会让它更糟糕。对于初学者来说,其他的方式只会让他们更难以阅读,因为他们大多数人不熟悉那些方式。只需按照有关元素在代码中排序的标准约定即可。例如,不要将静态初始化程序放在代码的一半或整个底部,也不要在整个类中分散多个静态初始化程序。只需将一个放在顶部,在静态声明后。


这是一个非常有价值的观点(所以我投了赞成票,尽管没有接受它),GPP也表述得最好。我不会使用那种方法,因为正如你所说,这对于初学者来说只是难以理解/阅读...而且我不能保证在我之后维护它的人有经验 :)。 - Romain
我不确定我是否更喜欢那种方式。通过将其重构为“private static”方法,您可能会失去监督。通常做法是将这些类型的方法(实用程序方法)放置在类的整个底部。但好吧,现在有IDE,所以您可以轻松点击前进。 - BalusC

5

static块不难阅读。因此,我建议使用这种解决方案。 但是,您可以将对象包装在另一个对象中,例如ObjectNameWrapper,它与您的ObjectName共享一个interface,其构造函数调用您的ObjectName构造函数,并隐藏所有发生的已检查异常。但是,我仍然建议使用静态选项。


2
介绍另一个对象似乎有些晦涩。 - Tom Hawtin - tackline
1
我完全同意你的观点。你的静态方法建议更好。 - Bozho

4

你可以使用使用Lombok的@SneakyThrows注解来实现

public static final ObjectName OBJECT_NAME = createObjectName();

@SneakyThrows(SomeException.class)
private static ObjectName createObjectName() {
    return new ObjectName("foo:type=bar");
}

这个注解使得一个受检查的异常表现得像一个未经检查的异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接