做网站的注意什么,wordpress7牛云插件,是想建个网站 用本地做服务器,服务器运维作者 | 王磊来源 | Java中文社群#xff08;ID#xff1a;javacn666#xff09;转载请联系授权#xff08;微信ID#xff1a;GG_Stone#xff09;在 Java 语言中 try-catch-finally 看似简单#xff0c;一副人畜无害的样子#xff0c;但想要真正的“掌控”它#xff0… 作者 | 王磊来源 | Java中文社群IDjavacn666转载请联系授权微信IDGG_Stone在 Java 语言中 try-catch-finally 看似简单一副人畜无害的样子但想要真正的“掌控”它却并不是一件容易的事。别的不说咱就拿 fianlly 来说吧别看它的功能单一但使用起来却“暗藏杀机”若您不信咱来看下面的这几个例子...坑1finally中使用return若在 finally 中使用 return那么即使 try-catch 中有 return 操作也不会立马返回结果而是再执行完 finally 中的语句再返回。此时问题就产生了如果 finally 中存在 return 语句则会直接返回 finally 中的结果从而无情的丢弃了 try 中的返回值。① 反例代码public static void main(String[] args) throws FileNotFoundException {System.out.println(执行结果: test());
}private static int test() {int num 0;try {// num1,此处不返回num;return num;} catch (Exception e) {// do something} finally {// num2,返回此值num;return num;}
}
以上代码的执行结果如下② 原因分析如果在 finally 中存在 return 语句那么 try-catch 中的 return 值都会被覆盖如果程序员在写代码的时候没有发现这个问题那么就会导致程序的执行结果出错。③ 解决方案如果 try-catch-finally 中存在 return 返回值的情况一定要确保 return 语句只在方法的尾部出现一次。④ 正例代码public static void main(String[] args) throws FileNotFoundException {System.out.println(执行结果: testAmend());
}
private static int testAmend() {int num 0;try {num 1;} catch (Exception e) {// do something} finally {// do something}// 确保 return 语句只在此处出现一次return num;
}
坑2finally中的代码“不执行”如果说上面的示例比较简单那么下面这个示例会给你不同的感受直接来看代码。① 反例代码public static void main(String[] args) throws FileNotFoundException {System.out.println(执行结果: getValue());
}
private static int getValue() {int num 1;try {return num;} finally {num;}
}
以上代码的执行结果如下② 原因分析本以为执行的结果会是 2但万万没想到竟然是 1用马大师的话来讲「我大意了啊没有闪」。有人可能会问如果把代码换成 num那么结果会不会是 2 呢很抱歉的告诉你并不会执行的结果依然是 1。那为什么会这样呢想要真正的搞懂它我们就得从这段代码的字节码说起了。以上代码最终生成的字节码如下// class version 52.0 (52)
// access flags 0x21
public class com/example/basic/FinallyExample {// compiled from: FinallyExample.java// access flags 0x1public init()VL0LINENUMBER 5 L0ALOAD 0INVOKESPECIAL java/lang/Object.init ()VRETURNL1LOCALVARIABLE this Lcom/example/basic/FinallyExample; L0 L1 0MAXSTACK 1MAXLOCALS 1// access flags 0x9public static main([Ljava/lang/String;)V throws java/io/FileNotFoundException L0LINENUMBER 13 L0GETSTATIC java/lang/System.out : Ljava/io/PrintStream;NEW java/lang/StringBuilderDUPINVOKESPECIAL java/lang/StringBuilder.init ()VLDC \u6267\u884c\u7ed3\u679c:INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;INVOKESTATIC com/example/basic/FinallyExample.getValue ()IINVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL1LINENUMBER 14 L1RETURNL2LOCALVARIABLE args [Ljava/lang/String; L0 L2 0MAXSTACK 3MAXLOCALS 1// access flags 0xAprivate static getValue()ITRYCATCHBLOCK L0 L1 L2 nullL3LINENUMBER 18 L3ICONST_1ISTORE 0L0LINENUMBER 20 L0ILOAD 0ISTORE 1L1LINENUMBER 22 L1IINC 0 1L4LINENUMBER 20 L4ILOAD 1IRETURNL2LINENUMBER 22 L2FRAME FULL [I] [java/lang/Throwable]ASTORE 2IINC 0 1L5LINENUMBER 23 L5ALOAD 2ATHROWL6LOCALVARIABLE num I L0 L6 0MAXSTACK 1MAXLOCALS 3
}
这些字节码的简易版本如下图所示想要读懂这些字节码首先要搞懂这些字节码所代表的含义这些内容可以从 Oracle 的官网查询到英文文档https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html磊哥在这里对这些字节码做一个简单的翻译iconst 是将 int 类型的值压入操作数栈。istore 是将 int 存储到局部变量。iload 从局部变量加载 int 值。iinc 通过下标递增局部变量。ireturn 从操作数堆栈中返回 int 类型的值。astore 将引用存储到局部变量中。有了这些信息之后我们来翻译一下上面的字节码内容 0 iconst_1 在操作数栈中存储数值 11 istore_0 将操作数栈中的数据存储在局部变量的位置 02 iload_0 从局部变量读取值到操作数栈3 istore_1 将操作数栈中存储 1 存储在局部变量的位置 14 iinc 0 by 1 把局部变量位置 0 的元素进行递增1操作7 iload_1 将局部位置 1 的值加载到操作数栈中8 ireturn 返回操作数栈中的 int 值
通过以上信息也许你并不能直观的看出此方法的内部执行过程没关系磊哥给你准备了方法执行流程图通过以上图片我们可以看出在 finally 语句iinc 0, 1执行之前本地变量表中存储了两个信息位置 0 和位置 1 都存储了一个值为 1 的 int 值。而在执行 finallyiinc 0, 1之前只把位置 0 的值进行了累加之后又将位置 1 的值1返回给了操作数栈所以当执行返回操作ireturn时会从操作数栈中读到返回值为 1 的结果因此最终的执行是 1 而不是 2。③ 解决方案关于 Java 虚拟机是如何编译 finally 语句块的问题有兴趣的读者可以参考《The JavaTM Virtual Machine Specification, Second Edition》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上Java 虚拟机会把 finally 语句块作为 subroutine对于这个 subroutine 不知该如何翻译为好干脆就不翻译了免得产生歧义和误解直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是还有另外一个不可忽视的因素那就是在执行 subroutine也就是 finally 语句块之前try 或者 catch 语句块会保留其返回值到本地变量表Local Variable Table中待 subroutine 执行完毕之后再恢复保留的返回值到操作数栈中然后通过 return 或者 throw 语句将其返回给该方法的调用者invoker。因此如果在 try-catch-finally 中如果有 return 操作**一定要确保 return 语句只在方法的尾部出现一次**这样就能保证 try-catch-finally 中所有操作代码都会生效。④ 正例代码private static int getValueByAmend() {int num 1;try {// do something} catch (Exception e) {// do something} finally {num;}return num;
}
坑3finally中的代码“非最后”执行① 反例代码public static void main(String[] args) throws FileNotFoundException {execErr();
}
private static void execErr() {try {throw new RuntimeException();} catch (RuntimeException e) {e.printStackTrace();} finally {System.out.println(执行 finally.);}
}
以上代码的执行结果如下从以上结果可以看出 finally 中的代码并不是最后执行的而是在 catch 打印异常之前执行的这是为什么呢② 原因分析产生以上问题的真实原因其实并不是因为 try-catch-finally当我们打开 e.printStackTrace 的源码就能看出一些端倪了源码如下从上图可以看出当执行 e.printStackTrace() 和 finally 输出信息时使用的并不是同一个对象。finally 使用的是标准输出流System.out而 e.printStackTrace() 使用的却是标准错误输出流System.err.println它们执行的效果等同于public static void main(String[] args) {System.out.println(我是标准输出流);System.err.println(我是标准错误输出流);
}
而以上代码执行结果的顺序也是随机的而产生这一切的原因我们或许可以通过标准错误输出流System.err的注释和说明文档中看出我们简单的对以上的注释做一个简单的翻译“标准”错误输出流。该流已经打开并准备接受输出数据。通常此流对应于主机环境或用户指定的显示输出或另一个输出目标。按照惯例即使主要输出流out 输出流已重定向到文件或其他目标位置该输出流err 输出流也能用于显示错误消息或其他信息这些信息应引起用户的立即注意。从源码的注释信息可以看出标准错误输出流System.err和标准输出流System.out使用的是不同的流对象即使标准输出流并定位到其他的文件也不会影响到标准错误输出流。那么我们就可以大胆的猜测二者是独立执行的并且为了更高效的输出流信息二者在执行时是并行执行的因此我们看到的结果是打印顺序总是随机的。为了验证此观点我们将标准输出流重定向到某个文件然后再来观察 System.err 能不能正常打印实现代码如下public static void main(String[] args) throws FileNotFoundException {// 将标准输出流的信息定位到 log.txt 中System.setOut(new PrintStream(new FileOutputStream(log.txt)));System.out.println(我是标准输出流);System.err.println(我是标准错误输出流);
}
以上代码的执行结果如下当程序执行完成之后我们发现在项目的根目录出现了一个新的 log.txt 文件打开此文件看到如下结果从以上结果可以看出标准输出流和标准错误输出流是彼此独立执行的且 JVM 为了高效的执行会让二者并行运行所以最终我们看到的结果是 finally 在 catch 之前执行了。③ 解决方案知道了原因那么问题就好处理我们只需要将 try-catch-finally 中的输出对象改为统一的输出流对象就可以解决此问题了。④ 正例代码private static void execErr() {try {throw new RuntimeException();} catch (RuntimeException e) {System.out.println(e);} finally {System.out.println(执行 finally.);}
}
改成了统一的输出流对象之后我手工执行了 n 次并没有发现任何问题。坑4finally中的代码“不执行”finally 中的代码一定会执行吗如果是之前我会毫不犹豫的说“是的”但在遭受了社会的毒打之后我可能会这样回答正常情况下 finally 中的代码一定会执行的但如果遇到特殊情况 finally 中的代码就不一定会执行了比如下面这些情况在 try-catch 语句中执行了 System.exit在 try-catch 语句中出现了死循环在 finally 执行之前掉电或者 JVM 崩溃了。如果发生了以上任意一种情况finally 中的代码就不会执行了。虽然感觉这一条有点“抬杠”的嫌疑但墨菲定律告诉我们如果一件事有可能会发生那么他就一定会发生。所以从严谨的角度来说这个观点还是成立的尤其是对于新手来说神不知鬼不觉的写出一个自己发现不了的死循环是一件很容易的事不是嘛① 反例代码public static void main(String[] args) {noFinally();
}
private static void noFinally() {try {System.out.println(我是 try~);System.exit(0);} catch (Exception e) {// do something} finally {System.out.println(我是 fially~);}
}
以上代码的执行结果如下从以上结果可以看出 finally 中的代码并没有执行。② 解决方案排除掉代码中的 System.exit 代码除非是业务需要但也要注意如果在 try-cacth 中出现了 System.exit 的代码那么 finally 中的代码将不会被执行。总结本文我们展示了 finally 中存在的一些问题有很实用的干货也有一些看似“杠精”的示例但这些都从侧面印证了一件事那就是想完全掌握的 try-catch-finally 并不是一件简单的事。最后在强调一点如果 try-catch-finally 中存在 return 返回值的操作那么一定要确保 return 语句只在方法的尾部出现一次参考 鸣谢阿里巴巴《Java开发手册》 developer.ibm.com/zh/articles/j-lo-finally
往期推荐
final的8个小细节听说只有高手才知道你知道几个Java中的Switch都支持String了为什么不支持long对象复制的7种方法还是Spring的最好用关注我每天陪你进步一点点