【问题标题】:Understanding the 'finally' block理解“finally”块
【发布时间】:2011-03-14 15:45:18
【问题描述】:

我已经编写了七个测试用例来理解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()) 有效?

【问题讨论】:

    标签: java finally try-finally


    【解决方案1】:

    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
       }
    

    【讨论】:

      【解决方案2】:

      一旦你执行了 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没有return,所以try和finally都正常完成,就和没有try-finally一样执行。

      【讨论】:

        【解决方案3】:

        考虑一下编译器实际上对 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 块之外。

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            【解决方案5】:

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

            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;
            }
            

            【讨论】:

              【解决方案6】:

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

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

              【讨论】:

                猜你喜欢
                • 2015-07-11
                • 2013-12-16
                • 2022-01-24
                • 1970-01-01
                • 2019-03-31
                • 2011-08-26
                • 2016-09-19
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多