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")起作用,而count++( in trySeven ())不起作用

4

6 回答 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。

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

tryFive并且trySix是直截了当的。由于 try 中没有 return,try 和 finally 都正常完成,它的执行与没有 try-finally 一样。

于 2010-07-12T05:45:23.780 回答
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;
}
于 2010-07-12T05:52:26.087 回答
2

finally 块在您离开 try 块时执行。“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
   }
于 2010-07-12T06:22:52.637 回答
0

builder = null并且builder.append("+1") 正在工作。只是它们不会影响您返回的内容。return无论之后发生什么,该函数都会返回语句所具有的内容。

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

于 2010-07-12T05:45:05.040 回答
0

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

于 2010-07-12T05:57:21.067 回答
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 块之外。

于 2010-07-12T06:13:39.577 回答