了解 'finally' 块

5

我已经编写了七个测试用例来理解finally块的行为。那么finally是如何工作的逻辑呢?

package core;

public class Test {
    public static void main(String[] args) {
        new Test().testFinally();
    }

    public void testFinally() {
        System.out.println("One = " + tryOne());
        System.out.println("Two = " + tryTwo());
        System.out.println("Three = " + tryThree());
        System.out.println("Four = " + tryFour());
        System.out.println("Five = " + tryFive());
        System.out.println("Six = " + trySix());
        System.out.println("Seven = " + trySeven());
    }

    protected StringBuilder tryOne() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder = null;
        }
    }

    protected String tryTwo() {
        String builder = "Cool";
        try {
            return builder += "Return";
        }
        finally {
            builder = null;
        }
    }

    protected int tryThree() {
        int builder = 99;
        try {
            return builder += 1;
        }
        finally {
            builder = 0;
        }
    }

    protected StringBuilder tryFour() {
        StringBuilder builder = new StringBuilder();
        try {
            builder.append("Cool");
            return builder.append("Return");
        }
        finally {
            builder.append("+1");
        }
    }

    protected int tryFive() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count++;
        }
        return count;
    }

    protected int trySix() {
        int count = 0;
        try {
            count = 99;
        }
        finally {
            count = 1;
        }
        return count;
    }

    protected int trySeven() {
        int count = 0;
        try {
            count = 99;
            return count;
        }
        finally {
            count++;
        }
    }
}

为什么builder = null不起作用?

为什么builder.append("+1")有效,而在 trySeven ()中使用count ++无效?

6个回答

10

一旦你执行了return语句,唯一覆盖它的方式就是再次执行return语句(正如在Returning from a finally block in Java中所讨论的那样,这几乎总是一个不好的主意),或者以其他方式突然结束。你的测试用例永远不会从finally块中返回。

JLS §14.1定义了突然结束。其中一种突然结束类型是返回。第1、2、3、4和7个try块由于返回而突然结束。根据§14.20.2的解释,如果try块以除throw以外的原因R突然结束,则立即执行finally块。

如果finally块正常完成(这意味着没有返回等情况),“try语句将因原因R突然结束。”换句话说,由try引发的返回保持不变;这适用于所有测试用例。如果你从finally块中返回,“try语句将因原因S突然结束(原因R被丢弃)。”(这里的S指的是新的覆盖返回语句)。

因此,在tryOne中,如果你执行了:

finally {
            builder = null;
            return builder;
        }

这个新的返回值S会覆盖原来的返回值R。

对于tryFour中的builder.append("+1"),请记住StringBuilder是可变的,因此您仍然返回指定在try中的相同对象的引用。您只是在最后一刻进行了修改。

tryFivetrySix很直观。由于try中没有返回值,在try和finally都正常完成的情况下,它的执行与没有try-finally时相同。


2

让我们从常见的用例开始 - 您有一个资源需要关闭以避免泄漏。

public void deleteRows(Connection conn) throws SQLException {
    Statement statement = conn.createStatement();
    try {
        statement.execute("DELETE * FROM foo");
    } finally {
        statement.close();
    }
}

在这种情况下,我们必须在完成时关闭语句,以避免泄漏数据库资源。这将确保在抛出异常的情况下,在函数退出之前我们始终会关闭语句。
try { ... } finally { ... } 块用于确保方法终止时总是会执行某些操作。它在异常情况下最有用。如果你发现自己正在做像这样的事情:
public String thisShouldBeRefactored(List<String> foo) {
    try { 
        if(foo == null) {
            return null;
        } else if(foo.length == 1) {
            return foo.get(0);
        } else {
            return foo.get(1);
        }
    } finally {
        System.out.println("Exiting function!");
    }
}

你没有正确使用finally关键字。这样会带来性能损失。在必须清理异常情况时才使用它。尝试将上述代码重构为以下形式:

public String thisShouldBeRefactored(List<String> foo) {
    final String result;

    if(foo == null) {
        result = null;
    } else if(foo.length == 1) {
        result = foo.get(0);
    } else {
        result = foo.get(1);
    }

    System.out.println("Exiting function!");

    return result;
}

2

当你离开try块时,finally块将会执行。"return"语句有两个作用:一个是设置函数的返回值,另一个是退出函数。通常情况下这看起来像一个原子操作,但在try块内部,它会导致finally块在设置返回值后、函数退出前被执行。

返回执行过程如下:

  1. 分配返回值
  2. 执行finally块
  3. 退出函数

示例一(基本类型):

int count = 1;//Assign local primitive count to 1
try{
  return count; //Assign primitive return value to count (1)
}finally{
  count++ //Updates count but not return value
}

例子二(参考):

StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
try{
    return sb;//return a reference to StringBuilder
}finally{
    sb.append("hello");//modifies the returned StringBuilder
}

第三个例子(参考):

   StringBuilder sb = new StringBuilder();//Assign sb a new StringBuilder
   try{
      return sb;//return a reference to StringBuilder
   }finally{
      sb = null;//Update local reference sb not return value
   }

例子四(返回):

   int count = 1;   //assign count
   try{
      return count; //return current value of count (1)
   }finally{
      count++;      //update count to two but not return value
      return count; //return current value of count (2) 
                    //replaces old return value and exits the finally block
   }

0

考虑编译器实际上为返回语句所做的工作,例如在tryOne()中:它将对builder的引用复制回调用函数的环境。在完成此操作之后,但在控制返回到调用函数之前,finally块将执行。因此,在实践中,您会得到类似于以下内容:

protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    try {
        builder.append("Cool");
        builder.append("Return");
        StringBuilder temp = builder;
        return temp;
    } finally {
        builder = null;
    }
}

或者,就语句实际执行的顺序而言(当然,忽略可能的异常),它看起来更像这样:
protected StringBuilder tryOne() {
    StringBuilder builder = new StringBuilder();
    builder.append("Cool");
    builder.append("Return");
    StringBuilder temp = builder;
    builder = null;
    return temp;
}

因此,设置 builder = null 是有效的,但它不会做任何有用的事情。然而,运行 builder.append("something") 将会 产生可见效果,因为 temp 和 builder 都引用同一个(可变)对象。

同样地,在 trySeven() 中真正发生的事情更像是这样:

protected int trySeven() {
    int count = 0;
    count = 99;
    int temp = count;
    count++;
    return temp;
}

在这种情况下,由于我们处理的是int类型,所以副本是独立的,因此增加一个不会影响另一个。
话虽如此,事实仍然存在:在try-finally块中放置return语句显然很令人困惑,因此如果您有任何选择的余地,最好重写代码,使所有return语句都在任何try-finally块之外。

0

builder = nullbuilder.append("+1") 都是有效的。只是它们没有影响到你所返回的内容。函数返回的是return语句中的内容,不管之后发生了什么。

这种差异的原因是因为builder是按引用传递的。 builder=null 改变了本地副本的builderbuilder.append("+1") 影响了父级持有的副本。


0
为什么 `builder = null` 不起作用? 因为你将本地引用设置为 null,这不会改变内存中的内容。所以它是有效的,如果你在 finally 块之后尝试访问 builder,则会得到 null。 为什么 `builder.append("+1")` 起作用?因为你使用引用修改了内存中的内容,所以它应该起作用。 为什么 `count++` 在 testFive() 中不起作用?在我这里正常工作。它按预期输出 100。

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