理解“最后”堵塞

发布于 2024-09-09 04:58:40 字数 2227 浏览 9 评论 0原文

我编写了七个测试用例来理解 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()) 却不能工作?

I've written seven test cases for understanding the behavior of the finally block. What is the logic behind how finally works?

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

Why builder = null is not working?

Why does builder.append("+1") work whereas count++( in trySeven()) does not work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

话少心凉 2024-09-16 04:58:40

一旦你执行了返回,覆盖它的唯一方法是执行另一个返回(如 从 Java 中的 finally 块返回,这几乎总是一个坏主意),或者突然完成。你的测试永远不会从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一样。

Once you do the return, the only way to override that is to do another return (as discussed at Returning from a finally block in Java, this is almost always a bad idea), or otherwise complete abruptly. Your tests don't ever return from a finally.

JLS §14.1 defines abrupt completion. One of the abrupt completion types is a return. The try blocks in 1,2,3,4, and 7 abruptly complete due to returns. As explained by §14.20.2, if the try block completes abruptly for a reason R besides a throw, the finally block is immediately executed.

If the finally block completes normally (which implies no return, among other things), "the try statement completes abruptly for reason R.". In other words, the return initiated by the try is left intact; this applies to all your tests. If you return from the finally, "the try statement completes abruptly for reason S (and reason R is discarded)." (S here being the new overriding return).

So in tryOne, if you did:

finally {
            builder = null;
            return builder;
        }

this new return S would override the original return R.

For builder.append("+1") in tryFour, keep in mind StringBuilder is mutable, so you're still returning a reference to the same object specified in the try. You're just doing a last minute mutation.

tryFive and trySix are straight-forward. Since there is no return in the try, the try and finally both complete normally, and it executes the same as if there was no try-finally.

太阳公公是暖光 2024-09-16 04:58:40

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

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

Let's start with use case you'll see more often - you have a resource that you must close to avoid a leak.

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

In this case, we have to close the statement when we're done, so we don't leak database resources. This will ensure that in the case of an Exception being thrown, we will always close our Statement before the function exits.

try { ... } finally { ... } blocks are meant for ensuring that something will always execute when the method terminates. It's most useful for Exception cases. If you find yourself doing something like this:

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

You're not really using finally properly. There is a performance penalty to this. Stick to using it when you have Exception cases that you must clean up from. Try refactoring the above to this:

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;
}
情绪少女 2024-09-16 04:58:40

当你离开 try 块时,finally 块就会被执行。 “return”语句做了两件事,一是设置函数的返回值,二是退出函数。通常这看起来像一个原子操作,但在 try 块内,它将导致finally 块在设置返回值之后、函数退出之前执行。

返回执行:

  1. 分配返回值
  2. run 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
   }

The finally block is executed when you leave the try block. The "return" statement does two things, one it sets the return value of the function and two it exits the function. Normally this would look like an atomic operation but within a try block it will cause the finally block to execute after the return value was set and before the function exits.

Return execution:

  1. Assign return value
  2. run finally blocks
  3. exit function

Example one (primitive):

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
}

Example two(reference):

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
}

Example three (reference):

   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
   }

Example four (return):

   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
   }
韵柒 2024-09-16 04:58:40

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

存在差异的原因是 builder 是通过引用传递的。 builder=null 更改 builder本地副本。 builder.append("+1") 影响父级保存的副本。

builder = null and builder.append("+1") are working. It's just that they're not affecting what you're returning. The function returns what the return statement has, regardless of what happens afterward.

The reason there is a difference is because builder is passed by reference. builder=null changes the local copy of builder. builder.append("+1") affects the copy held by the parent.

骄兵必败 2024-09-16 04:58:40

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

Why builder = null is not working?
Because you are setting the local reference to null which will not change the content of the memory. So it is working, if you try to access the builder after finally block then you'll get null.
Why builder.append("+1") work?
Because you are modifying the content of the memory using the reference,that's why it should work.
Why count++ does not work in testFive()?
It is working fine with me. It outputs 100 as expected.

-柠檬树下少年和吉他 2024-09-16 04:58:40

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

Consider what the compiler is actually doing for the return statement, for instance in tryOne(): it copies a reference to builder back to the calling function's environment. After it's done this, but before control goes back to the calling function, the finally block executes. So you have something more like this, in practice:

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

Or, in terms of the order that statements actually get executed (ignoring possible exceptions, of course), it looks more like this:

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

So setting builder = null does run, it just doesn't do anything useful. However, running builder.append("something") will have a visible effect, since both temp and builder refer to the same (mutable) object.

Likewise, what's really happening in trySeven() is something more like this:

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

In this case, since we're dealing with an int, the copies are independent, so incrementing one doesn't affect the other.

All that said, the fact remains that putting return statements in a try-finally block is quite clearly confusing, so if you've got any kind of choice in the matter, you'd be better off rewriting things so that all your return statements are outside any try-finally blocks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文