理解'finally'块

时间:2010-07-12 05:38:32

标签: java finally try-finally

我写了七个测试用例来理解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")有效,而count++ trySeven ())有效?

7 个答案:

答案 0 :(得分:11)

一旦你做了返回,唯一的方法是重写另一个返回(如Returning from a finally block in Java所述,这几乎总是一个坏主意),或者突然完成。你的测试永远不会从最终返回。

JLS§14.1定义了突然完成。突然完成类型之一是返回。由于返回,1,2,3,4和7中的try块突然完成。正如§14.20.2所解释的那样,如果try块在抛出之后突然完成R,则会立即执行finally块。

如果finally块正常完成(这意味着没有返回,除其他外),“try语句因为原因R而突然完成”。换句话说,尝试启动的返回保持不变;这适用于所有测试。如果你从finally返回,“try语句突然完成原因S(并且原因R被丢弃)。” (这是新的重要回报)。

所以在tryOne中,如果你这样做了:

finally {
            builder = null;
            return builder;
        }

这个新的返回S将覆盖原始的返回R。

对于builder.append("+1")中的tryFour,请记住StringBuilder是可变的,因此您仍然会返回对try中指定的同一对象的引用。你只是做了最后一分钟的突变。

tryFivetrySix是直截了当的。由于try中没有返回,try和finally都正常完成,并且执行方式就像没有try-finally一样。

答案 1 :(得分:2)

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

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

在这种情况下,我们必须在完成后关闭语句,因此我们不会泄漏数据库资源。这将确保在抛出Exception的情况下,我们将始终在函数退出之前关闭Statement。

尝试{...}最后{...}块用于确保在方法终止时始终执行某些操作。它对Exception案例最有用。如果你发现自己做了这样的事情:

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!");
    }
}

你并没有真正正确地使用它。这会对性能造成损失。如果您有必须清理的异常情况,请坚持使用它。尝试重构以上内容:

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 :(得分:2)

离开try块时执行finally块。 “return”语句执行两个操作,一个设置函数的返回值,另一个设置退出函数。通常这看起来像一个原子操作,但在try块中,它会导致finally块在设置返回值之后和函数退出之前执行。

返回执行:

  1. 指定返回值
  2. run finally blocks
  3. 退出功能
  4. 示例一(原语):

    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
       }
    

答案 3 :(得分:0)

builder = nullbuilder.append("+1") 正在工作。只是他们没有影响你的回归。该函数返回return语句所具有的内容,无论之后发生了什么。

之所以存在差异,是因为builder是通过引用传递的。 builder=null更改builder本地副本。 builder.append("+1")会影响父母所持有的副本。

答案 4 :(得分:0)

为什么builder = null不起作用?
因为您将本地引用设置为null,这不会更改内存的内容。所以它正在工作,如果你尝试在finally块之后访问构建器那么你将得到null。
为什么builder.append("+1") work?因为你正在使用引用修改内存的内容,那是为什么它应该工作。
为什么count++在testFive()中不起作用?
它对我很好。它按预期输出100。

答案 5 :(得分:0)

考虑编译器实际为return语句做了什么,例如在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,因此副本是独立的,因此递增副本不会影响另一个。

所有这一切,事实仍然是将return语句置于try-finally块中显然令人困惑,所以如果你在这个问题上有任何选择,那么你最好重写一些东西以便所有你的return语句在任何try-finally块之外。

答案 6 :(得分:-3)

StringBuilder不能为null,它需要一个字符串值。

使用字符串时,空参数通常很糟糕。 count ++未声明??? builder.append(“”)你要追加一个字符串 - 好。 计数=计数++;