What is the preferred Throwable to use in a private utility class constructor?
有效的Java(第二版),项目4,讨论使用私有构造函数来执行非实例化。这是这本书的代码示例:
1 2 3 4 5
| public final class UtilityClass {
private UtilityClass() {
throw new AssertionError();
}
} |
然而,AssertionError似乎不是正确的投掷方式。没有"断言",这就是API如何定义断言错误的使用。
在这种情况下,是否有一个不同的Throwable?人们通常只是用一条信息抛出一个通用的Exception吗?或者为这个写一个自定义的Exception是常见的吗?
这是非常琐碎的,但比任何事情都重要,我想我只是从风格和标准的角度对它好奇而已。
- 我在下面的问题上(相当大的)赏金,问自己是否应该把AssertionError放在这里,或者是否应该使用assert false语句。
有一个断言:"我断言永远不会调用这个构造函数"。所以,实际上,AssertionError在这里是正确的。
- 谢谢。我想我只是在找错断言者了。
- 强烈反对,见下文。您需要能够找到异常开始的地方;您还应该让异常给您,或者一个天真的代码阅读器,一个好的提示发生了什么。与断言没有关联的断言错误两者都没有。
- @查理:堆栈跟踪是否足够显示?另外,一个异常会被catch捕获(异常E),一个错误(正确地)不会。
- 我同意克莱特斯在这里的评论,stack trace是非常宝贵的,异常链接是自切片面包以来最好的特性,因为它允许您将stack跟踪保存得更远更广。-)
- 不正确,见JavaDoc:Java.Sun.com/J2SE/1.4.2/DOCS/API/Java/Lang/AdvestOrthReal.HT和ZWNJ;
- @javadoc的charlie说,"抛出来表示断言失败了。"您将断言视为"使用断言关键字",而不是"程序员将其视为bug"。
- 你是对的,我是。这就是为什么它被称为"断言"语句。就像我不会使用NullPointerException来指示一个值为0而不是范围内的数字的名称一样,即使数字是作为索引的。
我喜欢包括布洛赫的评论:
1
| // Suppress default constructor for noninstantiability |
或者更好的方法是将其放入错误中:
1 2 3 4
| private UtilityClass()
{
throw new AssertionError("Suppress default constructor for noninstantiability");
} |
- 他在问,为什么江户的类型是1(4),而不是其他的类型?
UnsupportedOperationException听起来是最合适的,尽管选中的异常会更好,因为它可能会警告某人在编译时错误地实例化类。
- 呵呵,宣布它"扔了",不管怎样扔了一个断言者。-)
- @Chrisjester Young,不,如果您声明它"throwwable",那么更好的实现是抛出另一个异常,它具有断言错误的cause。
不,不,不,恕我直言,除非是来自断言,否则永远不要扔一个江户。如果你想要一个断言者,把它和assert(false)一起扔。然后读代码的人可以稍后找到它。
更好的是,定义自己的例外,比如CantInstantiateUtilityClass。然后你会得到代码,上面写着
1 2 3 4 5
| try {
// some stuff
} catch (CantInstantiateUtilityClass e) {
// react
} |
让接球手的读者知道发生了什么。
更新
每隔一段时间,就有一个该死的蠢货在这里游荡,然后在事实发生四年后再次投了反对票。所以,我要注意的是,标准仍然将AssertionError定义为一个失败的断言的结果,而不是一些初学者认为应该抛出的东西来代替一个定义明确的信息异常。可悲的是,良好的异常规则可能是Java编程中最不被鼓励的技能。
- 强烈反对。(bloch,而不是block,顺便说一下。)"断言错误"的问题是,如果关闭断言,则无法抛出断言。人们应该通过寻找断言者或断言来找到断言。
- 我也强烈反对将cant实例化实用程序类作为异常类型:首先,它在末尾不包含单词exception或error,其次,断言错误允许您给出错误消息,这应该用于提示人们发生了什么。
- 该死,我认识乔希十年了,你以为我能拼出他的名字。好的,如果您愿意,可以在上面标记异常;但除非是由断言引起的,否则它不应该是断言错误。新阅读代码的人不会有任何线索,您的示例也没有提供线索。
- -1异常将被某些(可能是随机的)catch(异常E)块捕获。不会有错误。为一些只能通过修改源代码或拥有自定义类加载器才能发生的事情创建自定义异常是糟糕的建议。
- -1为什么您的代码必须捕获cant实例化实用程序类?这是不应该发生的事情。这正是断言合适的时候。其他人指出了assert关键字的问题。
- 因为(1)程序不希望通过放弃和死亡来处理错误。要么这个程序正在捕获断言错误(这可以消除您的反对意见),要么它通过异常终止来处理错误。
- 并且因为(2)定义了一个断言错误,引用,"抛出表示断言失败"。Java.Sun.com/J2SE/1.4.2/DOCS/API/Java/Lang/AdvestalError。HT和ZWNJ;不是"当J随机程序员认为发生了一件坏事时抛出的"。
- 调用构造函数是一个bug,因此我们应该断言它从未被调用。assert关键字存在严重缺陷,不应使用。这使得"throw new assertionerror()"成为新的断言机制。
- 我不同意,如果调用构造函数,程序应该放弃并终止,因为它显然是一个bug。断言(false)还将导致程序放弃并死亡,同时出现断言错误,因此我不确定您为什么喜欢使用断言关键字。
- 我敢肯定,当你的飞机控制系统放弃而死亡,而不是试图恢复时,你会很兴奋。异常终止不是一种可靠性策略。
- 更糟糕的是,飞机系统在一个不一致的状态下混在一起,没有一个程序员认为是可能的。最好放弃,让飞行员接管。:)但说真的,如果你在起搏器等设备上工作的话,禁用断言是可以的。其他人都应该避免。
- 关于您的更新:实际文本是Thrown to indicate that an assertion has failed.,它没有说明我们讨论的是谁的断言-至少我有关于我的代码的断言,您可能没有。但是,不管怎么说,整个事情都是吹毛求疵——没有人会用一个好的理由(可能不会实例化类X的实例)和一个指向实际构造函数的堆栈跟踪来曲解这种异常。
- 还有,既然你问……不,不,如果一个程序认为良好的错误处理意味着默默地忽略每一个异常并继续默默地破坏状态,那么我真的不会感到任何安全感——我敢打赌,这不是飞机软件实现这些功能的方式。我认为捕捉程序中一些定义良好的点中的所有错误并从中重新启动比这更有意义。
- @VOO,如果你认为这是我的建议,你就是个瘾君子。当然,你必须对这个断言做些什么。但是一个未捕获的异常除了导致程序失败之外什么也不做。现在,在Erlang中,这是一种语言支持的理解良好的机制,您可以失败并期望重新启动一个新进程。在Java中,例外,简单地杀死JVM,这不是一个选项。这样想:如果您定义了一个适合域的异常,那么您就拥有了使恢复成为可能的信息。…
- 使用断言器错误,您就知道断言失败了——在这一点上,使用throw new AssertionError,即使在那时,代码也在欺骗您。现在考虑一下,如果一个程序员发现某个程序中的AssertionErrors已经关闭了断言,那么他会是多么的不满足呢?这种方法(1)适用于运行时系统,(2)适用于维护程序员,(3)使断言关闭时出现JVM错误。
- 是的,似乎不可能弄清楚导致You may not instantiate class X.的断言错误和指向类x的构造函数的堆栈跟踪可能意味着什么。现在,关于如何捕获错误并重新启动是一个单独的主题,在这种情况下,为每个错误情况定义唯一的异常可能是有意义的,但是对于所有99.99%的程序员(也适用于Java中的所有其他异常),这也不重要。
- 这是为什么Java异常在这个领域中经常是无用的或者几乎是这样的。
- @克里斯杰斯特·杨,你说过,"人们应该通过寻找AssertionError和assert找到断言。"你从哪里得到的?是否有引文/资料来源,或者只是一个假设?因为人们/解析器应该只通过查找assert来查找断言的相反假设,如果不是更有效的默认立场,也是同样的。
- 在这一点上,仅仅有20年的使用语言的经验,作为Sun的高级Java架构师,以及Sun的Java认证测试和Sun的第一个J2EE架构课程的作者。我没有引用其他权威,因为我是权威。
非法访问错误怎么办?:)
- 我不确定我是否喜欢这个,因为它是不兼容类changeError的一个子类。但听起来不错。:)
- 通常,编译器会捕获此错误;如果类的定义发生了不兼容的更改,则此错误只能在运行时发生。
当代码要求将JUnit作为依赖项包括在Maven测试范围test内时,则直接使用Assertion.fail()方法,并受益于清晰度的显著提高。
1 2 3 4 5
| public final class UtilityClass {
private UtilityClass() {
fail("The UtilityClass methods should be accessed statically");
}
} |
当超出测试范围时,您可以使用类似于以下内容的东西,这需要像上面一样使用静态导入。import static pkg.Error.fail;
1 2 3 4 5 6 7 8 9
| public class Error {
private static final Logger LOG = LoggerFactory. getLogger(Error. class);
public static void fail (final String message ) {
LOG. error(message );
throw new AssertionError (message );
// or use your preferred exception
// e.g InstantiationException
}
} |
以下用法。
1 2 3 4 5
| public class UtilityClassTwo {
private UtilityClassTwo () {
Error. fail("The UtilityClass methods should be accessed statically");
}
} |
在最惯用的形式中,它们都归结为:
1 2 3 4 5
| public class UtilityClassThree {
private UtilityClassThree() {
assert false :"The UtilityClass methods should be accessed statically";
}
} |
内置异常之一,UnsupportedOperationException可以被引发到指示"不支持请求的操作"。
- 对于我来说,UnsupportedOperationException似乎是最具描述性的一个。
您可以创建自己的类扩展Throwable,例如:
1
| class NoninstantiabilityError extends Throwable |
这有以下优点:
- 名称表示问题所在
- 因为它直接延伸到Throwable,所以它不太可能被意外捕获。
- 因为它直接扩展了Throwable,所以检查并意外调用相应的构造函数需要捕获异常
使用实例:
1 2 3 4 5 6 7
| public final class UtilityClass {
private UtilityClass() throws NoninstantiabilityError {
throw new NoninstantiabilityError();
}
...
} |
断开的断言意味着您已经断开了代码的合同规范。所以这是正确的。
但是,正如我假设您将在私下实例化一个实例,它还将调用构造函数并导致错误-除非您有另一个构造函数?
- 我的理解是,这个实用程序类只提供了一些静态方法,因此不会调用构造函数。
- 马修是对的。从技术上讲,甚至不需要抛出任何内容,因为构造函数是私有的。抛出某些东西可以确保类本身不调用构造函数。