【问题标题】:Java: Meaning of catch (final SomeException e)?Java:catch 的含义(final SomeException e)?
【发布时间】:2011-03-31 19:36:09
【问题描述】:

final 在下面的 Java 表达式中做了什么?

catch (final SomeExceptionType e)

【问题讨论】:

    标签: java exception exception-handling final


    【解决方案1】:

    基本意思是:

    将“SomeExceptionType”捕获到变量“e”中,并保证在处理异常期间我们不会为“e”分配不同的异常。

    大多数情况下这是矫枉过正,就好像我正在将异常捕获到临时变量名中(e 仅对异常处理块有效),我不必如此严格地监管自己以至于不相信自己分配同一变量名的不同(可能已创建)异常。

    也就是说,也许这个块是由一群志同道合的人大量维护的,一个人只是想非常确定 e 是最初捕获的异常。

    ----根据评论编辑----

    我想不出一个非常好的理由来做这件事。由于“e”不是成员(静态或其他),因此类文件编译后不会使用名称“e”。另一种说法是,当你进入 JVM 字节码的异常处理块时,该对象不会被分配给 JVM 处理框架可访问的任何成员名称,它会被推送到 Thread 的内部处理堆栈中。当前帧。

    即使两个线程可以访问同一个对象,每个线程也会有自己的框架,因此编译器从一个框架的内部堆栈中删除了“e”名称,其他线程无法更改。

    考虑到这一点,声明“e”final 的唯一好处是确保未来的编码人员不会在进入块后意外设置“e”。也许他们的目的是让代码在多线程环境中更加健壮,但是临时变量(名称仅在块中有效的变量)在编译后没有名称,它们被推送到帧的堆栈中。

    这就是为什么

    public int safe() {
      int x = 5;
      x = x + 5;
      return x;
    }
    

    通常被认为是线程安全的,因为它这样做(在伪字节码中)

    (In the thread's current frame)
    push 5
    push 5
    add integers
    return
    

    虽然这不是线程安全的

    int x = 5;
    
    public void unsafe() {
      x = 5;
      x = x + 5;
      return x;
    }
    

    因为它这样做

    (in the thread's current frame)
    push "this"
    push 5
    set member x
    push "this"
    get member x
    push 5
    add integer
    set member x
    get member x
    return
    

    后一个字节码表明,交错两个线程使用成员 x 作为中介创建线程间通信,而第一个代码块不能进行任何线程间通信,因为没有中介。

    【讨论】:

    • 这种情况什么时候会发生?我猜是在多线程环境中,但你能用一个小例子来说明这一点吗?
    • @Nocturne:多线程与它无关。由于变量 'e' 是处理程序块的本地变量,因此永远不能从其他线程访问它。我认为如果你有一个又长又复杂的处理程序块,它可能会有点用——但在这种情况下,你可能做错了。
    • @Nocturne:通过将“e”标记为final,作者禁止重新分配“e”以引用catch块中的不同对象。它是一种维护安全机制,而不是一种并发辅助,以这种方式使用。在代码编译期间将捕获重新分配“e”的尝试。
    • 我怀疑final 可能只是自动重构的产物。我无法确切地想象哪个重构可能导致了这种情况,但是重构尽可能多地保留最终结果是很常见的。
    • 我不知道人们用匿名类填充他们的异常捕获块。然而,这个问题并没有表明任何匿名类都在这个块中,所以它似乎(没有进一步阐述)仍然是不必要的,除非你真的认为它会在这样的块中被重新分配。
    【解决方案2】:

    目前它意味着final 与任何局部变量非常相似,除了它总是“明确分配”。

    在最近的 JDK7 版本中,a Project Coin language change 允许它指示一定程度的隐式静态类型正在进行。单个catch 可以通过公共基类型捕获许多不同的已检查异常,并在仅具有捕获或声明可以(静态而言)在try 中抛出的那些异常的封闭上下文中重新抛出。 (请参阅链接以获得更好的解释。)

    【讨论】:

      【解决方案3】:

      变量上的final关键字意味着该变量只能被赋值一次,并且由于这里的赋值是由编译器完成的,这意味着该变量不能在以后的代码中更改。

      这是一个重要的属性,因为它对维护者意味着这个特定的变量在任何使用它的地方都会有这个特定的值,并且没有必要跟踪它的变化。这被认为非常有用,以至于 Eclipse 中的“清理”操作允许在任何可能的地方添加“最终”,我相信您所看到的是这种自动清理的结果 ,因为大多数人类程序员会保持 catch-block 简短,因此不需要这样的指示。

      【讨论】:

        【解决方案4】:

        问题,“final 做什么?”在此问题的其他答案中以及hereherehere 中已解决。但是在 try-catch 块的上下文中,Java Language Specification (JLS) §4.12.4 states(强调我自己的):

        • try-with-resources 语句(§14.20.3)的资源和multi-catch 子句(§14.20)的异常参数是隐式声明为final
        • uni-catch 子句(第 14.20 节)的异常参数可能实际上是最终的,而不是明确声明为最终的。这样的参数永远不会隐式声明为 final

        在多捕获子句中

        final 关键字添加到multi-catch 子句 只是明确表明variable 是隐式最终的事实。通常,每当final 关键字传达有助于使您的代码更具可读性/可维护性的附加信息时,请使用它。

        在 uni-catch 子句中

        另一方面,uni-catch 子句 中的异常参数从不 隐含地为final。因此,在 uni-catch 子句 中使用 final 关键字可以防止发生以下情况:

        try {
             throw new Exception();
        catch (Exception e){
            e = null;
            e.printStackTrace(); //throws a NullPointerException
        }
        

        在这个简单的例子中问题很明显。但有两种情况可能不太明显,需要使用final

        1. 如果 catch 块更复杂,可能会意外重新分配。 (虽然,如果 catch 块很复杂,你可能做错了。)
        2. 防止代码维护期间出现问题。将final 添加到异常变量将确保在编译时捕获重新分配,而不是运行时

        作为一般经验法则,在 uni-catch 子句中使用 final 关键字 in the same way you would use the final keyword for a method parameter:

        JLS§4.12.4 :声明一个变量 final 可以作为有用的文档,说明它的值不会改变,并且可以帮助避免编程错误。

        【讨论】:

        • 我不确定“finally”的答案是否与“final”的问题有很大关系
        • @EdwinBuck 同意。它看起来像一个错字;答案的上下文与“finally”块没有任何关系。我更新了答案以删除结尾的“ly”
        猜你喜欢
        • 1970-01-01
        • 2016-08-19
        • 1970-01-01
        • 1970-01-01
        • 2015-02-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-25
        相关资源
        最近更新 更多