Java解惑之try catch finally

写给自己:

技术关注过于分散往往导致不能专注,长时间的浮躁、纠结最终的结果只是太多珍贵东西浪费,程序员拥有好奇心、求知欲本是件好事,但学会驾驭这些东西才是真正的成熟,坚持并抵住诱惑、潜心而无视喧闹,这是现在自己要做的。

转入正文:

此文起因是由于论坛中出现的这两个讨论贴:

http://www.iteye.com/topic/1112358

http://www.iteye.com/topic/1112387

至于这个问题是否值得深究我们不做讨论,人跟人观点不一样,我就觉得很有意思,所以可以试着分析一下。

不过要提前说明一下,可能有的地方我的理解并不正确或者措辞并不恰当,还希望高手指正。

首先,还是先看下问题,代码如下:

Java代码

  1. private static void foo() {
  2. try {
  3. System.out.println("try");
  4. foo();
  5. } catch (Throwable e) {
  6. System.out.println("catch");
  7. foo();
  8. } finally {
  9. System.out.println("finally");
  10. foo();
  11. }
  12. }
  13. public static void main(String[] args) {
  14. foo();
  15. }

这个会输出什么呢?

要理解这个问题,我们先讲一些其他的东西

1) Java Stacks:

所谓Java栈,描述的是一种Java中方法执行的内存模型,Java栈为线程私有,线程中每一次的方法调用(或执行),JVM都会为该方法分配栈内存,即:栈帧(Stack Frame),分配的栈帧用于存放该方法的局部变量表、操作栈(JVM执行的所有指令都是围绕它来完成的)、方法编译后的字节码指令信息和异常处理信息等,JVM指定了一个线程可以请求的栈深度的最大值,如果线程请求的栈深度超过这个最大值,JVM将会抛出StackOverflowError,注意,此处抛出的时Error而不是Exception。

下面我们来看一张图(引自Inside Java Virtual Machine)

由上图可知:

在一个JVM实例中(即我们运行的一个Java程序)可以同时运行多个线程,而每个线程都拥有自己的Java栈,此栈为线程私有,随着线程内方法的不断调用,线程内的栈深度不断增加,直到溢出。而当一个方法执行完毕(return或throw),该方法所对应的线程内的栈帧被JVM回收,线程内的栈深度会相应的变小,直到线程的终结。

2) Java的异常体系:

在Java的异常体系中,java.lang.Throwable是所有异常的超类,继承于Object,直接子类为Error和Exception,其中Error和RuntimeException(Exception的子类)为unchecked,即:无需用户捕获,除RuntimeException以外的其他Exception都为checked,即:用户必须捕获,否则编译无法通过。

因为Throwable处于Java异常体系的最顶层,所以Java抛出的任何Error和Exception都会被其捕获,包括StackOverflowError。

3) Finally到底是怎么回事?

Finally通常会配合try、catch使用,在每一处的try或catch将要退出该方法之前,JVM都会保证先去调用finally的代码,这里所说的退出不单单是指return语句,try或catch中异常的抛出也会导致相应方法的退出(当然,前提是不被catch捕获以及不被finally跳转)。在执行finally代码时,如果finally代码本身没有退出的语句(return或抛出异常),finally执行完毕后还会返回try或catch,由try或catch执行退出指令。

语言总是缺乏表现力,看代码吧。

Java代码

  1. public class TCF {
  2. static int f1() {
  3. try {
  4. return 1;
  5. } finally {
  6. System.out.print("f1");
  7. }
  8. }
  9. static int f2() {
  10. try {
  11. throw new Exception("try error");
  12. } catch (Exception e) {
  13. return 2;
  14. } finally {
  15. System.out.print("f2");
  16. }
  17. }
  18. static int f3() {
  19. try {
  20. throw new RuntimeException("try error");
  21. } catch (ArithmeticException e) {
  22. return 3;
  23. } finally {
  24. System.out.print("f3");
  25. }
  26. }
  27. static int f4() {
  28. try {
  29. throw new Exception("try error");
  30. } catch (Exception e) {
  31. throw new RuntimeException("catch error");
  32. } finally {
  33. System.out.print("f4");
  34. }
  35. }
  36. static int f5() {
  37. try {
  38. throw new Exception("try error");
  39. } catch (Exception e) {
  40. throw new RuntimeException("catch error");
  41. } finally {
  42. System.out.print("f5");
  43. return 5;
  44. }
  45. }
  46. static int f6() {
  47. try {
  48. throw new Exception("try error");
  49. } catch (Exception e) {
  50. throw new RuntimeException("catch error");
  51. } finally {
  52. System.out.print("f6");
  53. throw new RuntimeException("finally error");
  54. }
  55. }
  56. public static void main(String[] args) {
  57. System.out.println(" : " + f1());
  58. try {
  59. System.out.println(" : " + f2());
  60. } catch (Exception e) {
  61. System.out.println(" : " + e.getMessage());
  62. }
  63. try {
  64. System.out.println(" : " + f3());
  65. } catch (Exception e) {
  66. System.out.println(" : " + e.getMessage());
  67. }
  68. try {
  69. System.out.println(" : " + f4());
  70. } catch (Exception e) {
  71. System.out.println(" : " + e.getMessage());
  72. }
  73. try {
  74. System.out.println(" : " + f5());
  75. } catch (Exception e) {
  76. System.out.println(" : " + e.getMessage());
  77. }
  78. try {
  79. System.out.println(" : " + f6());
  80. } catch (Exception e) {
  81. System.out.println(" : " + e.getMessage());
  82. }
  83. }
  84. }

输出如下:

解释如下:

声明:我们把每一个可以导致方法退出的点称为结束点。

f1方法: try中return 1代表着try方法块的结束点,jvm会在该结束点执行之前,执行finally,finally代码块本身没有结束点,所以执行完finally后会返回try方法块,然后执行讨try中的return 1,所以结果输出如上。

f2方法:try中throw代表着try方法块的结束点,但是由于有catch的存在,并且catch可以捕获try中抛出的异常,所以catch在某种意义上延续了try的生命周期,try catch此时组成了一个新的整体,try中的throw不再代表一个结束点,而catch中return 2此时代表try catch整体的结束点,这时没有任何语句可以延续try catch的生命周期,JVM知道try catch产生了一个结束点,将要结束方法的执行,所以JVM在这个结束点执行之前立即执行finally,因为finally没有结束点,所以finally执行完毕返回catch,然后执行该catch中的return 2,所以输出结果如上。

f3方法:f3和f2的区别在于f3的catch是捕获ArithmeticException,而我们在try中抛出的是RuntimeException,所以catch没能捕获该异常,也就无法延续try的生命周期,所以try的throw形成一个结束点,JVM获知try将要结束该方法的执行,所以马上调用finally,因为finally内部没有结束点,所以会返回try,然后try抛出自己的异常,输出结果如上。

f4方法:f4和f2本质相同,只不过f2中catch是以return 2作为自己的结束点,而f4中catch是以抛出异常作为自己的结束点,输出如上。

f5方法:f5和f4大部分相同,catch延续try的生命周期,try catch组成一个整体,而这个整体的结束点由catch抛出异常产生,区别就在于下面的部分,JVM知道try catch整体将要结束该方法的执行,所以马上调用finally,而在f5的finally内部有自己的结束点,即:return 5,这样finally自己就结束了整个方法的执行,而不会返回catch,由catch抛出异常,结束该方法的执行,所以会有如上的输出。

f6方法:f6和f5大致相同,只不过在f6的finally中是以抛出异常作为自己的结束点,进而结束方法的执行,输出结果如上。

至此,对于try catch finally的使用都应该大致明白了,但是JVM为什么会这么做呢?它内部究竟是怎么实现的呢?

让我们从字节码的角度来分析一下JVM的try catch finally运行机制。

声明:下面描述的并不是真正的Java字节码,我们只是为了表述方便而模拟出来的。

任何Java类中的方法最终都会编译成字节码,由JVM解释执行,一个Java方法最终形成的就是一串字节码流,下面模拟我们的第一个字节码流:

如果把这段字节码给JVM,JVM就会顺序执行1、2、3、4指令,很简单吧,下面看另一个:

JVM遇到这段指令,会先执行1、2、3,到第4条指令时发现是一个goto语句,所以就跳过5,继而执行6、7,依旧是很简单,然后下一个:

JVM执行1、2、3,然后跳到第7行,执行4、5、6,然后又跳回第5行,执行return 1,看看是否似曾相识,对,它就是我们f1方法的原型!

当我们用javac把f1编译后,生成class文件内部的字节码原理上就和上面的一样,好,既然我们可以模拟f1了,那让我们再来模拟一下f2:

JVM执行1、2、3后看到throw语句,就抛出了一个异常,名字为exception,然后,JVM想应该先去执行finally了,执行完finally后,再把那个异常向上抛,但它又一看,原来还有catch部分,它又看看catch内部,居然有它抛出的那个异常(exception),所以JVM就放弃执行finally部分,转而执行catch的相应部分,即4、5、6,然后它遇到goto(goto是由编译生成,因为编译时它看到一个return 2,它知道这是一个结束点,而Java代码中又有finally语句,所以编译器就会在这个return语句之前生成一条goto语句),所以跳到13,执行7,8,9,最后再跳到11行,执行return 2,这样,我们的f2方法就结束了。

我们再模拟一个f4方法的字节码:

在分析上图之前,我先提出一个问题,当try语句块抛出异常,而我们没有写catch语句块或写的catch语句块中不能捕获try中抛出的异常时,JVM还是能帮我们保证finally的执行,它的内部究竟是怎样实现的呢?好,带着这个疑问,我们来看下面的分析:

在try catch finally语法结构中,try是必须的,而catch和finally中我们至少要选一个,由于这样的语法规则,所以我们可以不写catch,而又由于异常有unchecked的类型(或其他原因),所以很有可能即使我们写了catch,try中抛出的异常在我们的catch中还是不能捕获,综上两种情况我们可以得知,不管怎样,只要有try代码块的地方,就有可能存在我们不能捕获或者说无需捕获的异常。而finally的定义又要求,不管try或catch中发生了什么,finally部分必须要执行。可如果JVM不能捕获上面我们描述的那类异常,它就无法得知一个结束点的产生,也就无法在这种结束点产生的情况下调用finally。Java为了使这两种情况可以同时成立,在遇到有try的代码块地方,Java编译器不管我们有没有声明catch,都会为我们生成一个system catch(命名也许并不恰当),而这个catch可以捕获任何异常,这样一来,即使是上面我们讨论的那种异常产生,JVM也能捕获并得知这可能是一个结束点,进而决定finally是否去执行。分析f4,JVM先执行1、2、3,然后抛出一个异常,我们自定义的catch捕获该异常后,执行4、5、6然后又抛出RuntimeException,由于自定义catch中无法再捕获这个异常,所以由system catch来捕获,system catch只做一件事,调用finally,然后rethrow捕获的异常。

最后我们看下f5:

由于finally内部有自己return了(而不是f4中的goto 19),所以finally中的return 5就代表了整个该方法的退出。

最后,我们再上最后一张截图吧:

这个截图和上面那个截图没有什么不一样,只是去掉了[try] [catch] [finally]等标识符,之所以这样做是因为我想展示的是一个更加贴近真实字节码的模拟。

为什么这样就更加贴近真实了呢?

因为JVM是呆板的,它只知道执行,而没有智能。

对于JVM来说,它并不知道哪处是try,哪处是catch,哪处是finally,甚至对于它来说,根本就没有try catch finally的概念,它知道的只有你给我什么指令,我就执行什么指令,没有语法,没有辨别,它内部没有这样的规定说,啊,12到15行是finally语句块,我得注意点,一旦我遇到一个结束点,我先要跳到finally,执行完这个finally后再跳回这个结束点,然后执行这个结束点,JVM内部并没有这样智能的处理,其实它也不需要有这样智能得处理。Java规范中是要求,只要遇到有finally得地方,不管发生什么情况,finally都要执行,但Java中的这个要求并不是直接对JVM提出的,JVM只是执行指令的机器,而把含有Java语法规范的Java源码翻译成字节码指令的是Javac,对,就是Javac,是Javac把这样的Java语法规范翻译成字节码指令流,而在这些字节码指令流中,通过添加一些判断、跳转、返回等指令,使得当JVM在执行这些指令的时候,它的外部表现就是符合Java语法规范的。

你明白我在说什么吗?

我是在说,任何方法编译后的结果只是一串字节码指令流,各个指令间都是等价的,虽然我们在我们的方法中添加上了try catch finally,但这只是Java语法,编译后的字节码是没有这些东西的,编译的过程是按照Java语法规范生成一系列的包含判断、跳转、返回等指令的指令流,以使JVM在执行这些指令流时并不总是顺序执行,你自己想想,Java语法规范要求的finally特性本质上不就是跳转吗?,finally语法规范用通俗的语句来说就是,在一个含有finally的方法的各个结束点执行之前先跳转到finally,执行完finally后再跳回来,执行剩下的部分,就这么简单。所以,Javac在遇到有finally的方法时,就找出各个方法的结束点,并在各个结束点指令之前添加一条跳转指令,跳转到finally,执行完finally之后,再跳转回来,哇,原来就是些如此简单的东西啊。

此处有一点需要注意的就是当跳转到finally后,如果finally内部有结束点,finally就不会再跳转回去,JVM直接执行了finally内部的结束点(执行其它地方的结束点会先跳转到finally,但执行finally内部的结束点并不会跳转到其它地方,因为这个结束点已经是在finally内部了,无需跳转,所以JVM直接执行了这个结束点,整个方法执行结束),这样finally自己就结束了方法的执行。

最后再说明一点:在一个含有try catch finally的方法中,try语句块内部,catch语句块内部和finally语句块内部的所有语句都有与之对应的字节码指令,所以Javac在编译这些部分的时候,直接编译。而至于try catch finally这三个关键字,它们并没有与之对应的字节码指令,它们只是语法上的定义,Javac在遇到这三个关键字时,会通过其它指令(例如:跳转、返回指令)的组合来实现这种语法要求。

总结一下,try catch finally有两个作用:

1: 把一个方法的字节码指令流分成三个部分,并标识出,哪个部分是try,哪个部分是catch,哪个部分是finally。(各个部分内部也可以存在的跳转,但这种跳转是语句层面的跳转(例如:if),并且这种跳转只能在自己内部发生,即:只能跳到自己内部的其它语句,而不能跳到其它部分的其它语句)

2:指明了这三个部分的执行顺序,例如,先执行try,再执行catch,再执行finally,再执行catch。(这种执行顺序也可以认为是一种跳转,而这种跳转是语法层面的跳转,只能在try catch finally这三个部分之间发生,即:一旦发生跳转就会跳转到其它部分的其它语句,而不是跳转到自己内部的其它语句)

说了这么多,其实我们要记住的只有一点,那就是:要想掌握finally,只需要知道在一个方法中,哪些地方是结束点,即:哪些地方会结束该方法的执行,JVM在这个结束点执行之前,会先去执行finally。

还记得当初那个引出这篇文章的小程序吗?估计都忘了,再回忆一下吧,有一段代码如下:

Java代码

  1. private static void foo() {
  2. try {
  3. System.out.println("try");
  4. foo();
  5. } catch (Throwable e) {
  6. System.out.println("catch");
  7. foo();
  8. } finally {
  9. System.out.println("finally");
  10. foo();
  11. }
  12. }
  13. public static void main(String[] args) {
  14. foo();
  15. }

它会输出什么?

在说明这个问题之前,我首先不得不说一个现象,那就是在不同的机子上运行上面的代码会有不同的输出结果,看看我遇到的三种输出:

1:在公司电脑中,直接执行上面的代码,代码及输出如下:

代码:

Java代码

  1. public class JvmMain {
  2. private static void foo() {
  3. try {
  4. System.out.println("try");
  5. foo();
  6. } catch (Throwable e) {
  7. System.out.println("catch");
  8. foo();
  9. } finally {
  10. System.out.println("finally");
  11. foo();
  12. }
  13. }
  14. public static void main(String[] args) {
  15. foo();
  16. }
  17. }

输出:

2:在家里的电脑中,直接执行上面的代码,代码及输出如下:

代码和上面的一样,略。

输出:

3:在家里的电脑中,在原来的基础上添加一个方法,代码及输出如下:

代码:

Java代码

  1. class JvmMain {
  2. public static void foo() {
  3. try {
  4. System.out.println("try");
  5. foo();
  6. } catch (Throwable e) {
  7. System.out.println("catch");
  8. foo();
  9. } finally {
  10. System.out.println("finally");
  11. foo();
  12. }
  13. }
  14. public static void fooAgain() throws Exception {
  15. throw new Exception("fooAgain");
  16. }
  17. public static void main(String[] args) {
  18. foo();
  19. }
  20. }

输出:

纠结了很久,但依旧不知道是怎么回事,可能是因为JDK的版本或发行商不同吧,不知道,期盼高人分析啊。

按照我的理解,输出结果应该是第一种情况,下面我们就基于第一种情况进行分析:

由于程序是层层递归调用,所以栈的深度会不断增加,直到栈溢出。现在假设我们的栈深度最多能有10层(就是说最多可以存放10个栈帧)

当main中调用foo,foo再调foo,层层递归直到填满第10层。此时,栈及方法执行状态为:由于递归调用,10层栈帧全部填满,此时第10层栈帧对应我们最后调用的那个方法,即:foo。而此时,第10层栈帧对应的foo方法的执行状态为:即将在try中再次调用foo方法,并且希望jvm为此方法分配栈帧,即第11层栈帧,用来存放方法的各种信息,但是,此时的问题就出现了,由于栈内存最多只能分配10层栈帧,所以try中的再次调用foo方法将导致StackOverflowError抛出,而根据我们上面所述,因为第10层栈帧对应的foo方法中存在catch,捕获的是Throwable,所以第10层栈帧对应的foo方法的try中抛出的异常并不代表一个结束点,catch为其延续生命周期,jvm进而执行第10层栈帧对应的foo方法的catch,所以会输出“catch”,然后catch再调用foo,并希望jvm为foo分配栈内存,即第11层栈帧,还是因为栈内存够,catch方法也抛出StackOverflowError,这个Error又被System Catch捕获,System Catch调用第10层栈帧对应的foo方法的finally方法,输出finally,然后第10层栈帧对应的foo方法的finally中再调用foo方法,并希望jvm为其分配内存,内存不够,还是抛出StackOverflowError,此时,finally再次抛出异常,由于该异常成为finally的结束点,所以finally不会再返回system catch,抛出system catch 捕获的catch语句块抛出的异常,jvm执行finally的结束点,退出第10层栈帧对应的foo方法,并且把第10层栈帧内存收回,返回到第9层栈帧对应的foo方法的try语句块中(因为是在此调用的第10层栈帧对应的foo方法),此时第9层栈帧对应的foo方法中的try语句块接到第10层栈帧对应的foo方法返回的异常,try语句块无法处理所以继续抛出异常,由于第9层栈帧对应的foo方法中的catch可以捕获该异常,所以进而执行第9层栈帧对应的foo方法中的catch,输出catch字符串,然后第9层栈帧对应的foo方法中的catch代码块再次调用foo,希望jvm为其分配栈内存,jvm检查栈内存,发现第10层栈帧可以用,所以jvm就为其分配第10层栈帧,分配完成之后,jvm开始执行第10层栈帧对应foo方法的第一条语句,即:输出try字符串,然后jvm开始执行第10层栈帧对应foo方法的第二条语句,即再次调用foo方法,并希望jvm为其分配栈内存,jvm检查之后发现,现在10层栈帧都已经用完,无法再分配了,所以抛出StackOverflowError,之后的jvm行为就和刚刚描述的第10层栈帧对应foo方法是一样的了,最终结果是finally中由于调用foo而jvm无法为其分配第11层栈帧,所以finally抛出异常,返回到第9层栈帧对应的foo方法中的catch中,第9层栈帧对应的foo方法中的catch代码块继续抛出该异常,让其他部分处理,第9层栈帧对应的foo方法的system catch捕获该异常,然后调用第9层栈帧对应的foo方法中的finally语句块,finally中的第一条语句输出finally字符串,第二条语句又调用foo方法,jvm又为该foo方法分配第10层栈帧,后续的执行和第9层栈帧对应的foo方法中的catch中调用foo过程是一样的,结果也是返回StackOverflowError到第9层栈帧对应的foo方法中的finally代码块中,然后,第9层栈帧对应的foo方法中的finally代码块继续向上抛出该异常,并退出第9层栈帧对应的foo方法,回收第9层栈帧占用的内存,第8层栈帧对应的foo方法的try代码块接到该异常并继续抛出,然后。。。

后续的部分不再分析,因为我想如果你还没有被绕晕,你肯定是已经理解了,那后续的部分自己已经可以推导出来。

直接看我的推导结果吧,我只分析了栈最上面的三层:

怎样看这个图呢?等号划分三个部分,从上到下依次读取三个部分的字符串输出,如果一个部分中有多行,则把上面的行压倒最下面的行的空白处,例如第二部分,将10压入到9的空白处,形成输出为:catch try catch finally finally try catch finally,把三个部分形成的一个大的字符串和程序的输出结果进行比较,结果完全一样(当然,要从开始抛出异常的地方进行比较)。

按照这种分析,这段递归程序最终的最终会抛出异常,因为最底层的main方法无法处理上一层foo的finally抛出的StackOverflowError,但我在公司跑了一下午都没有出现这种结果,哎,很受打击,但后来我想了想,一下午的时间真的够吗?

假设我们的栈的最大深度为2001,那让我们粗略的算算有多少次的栈帧分配和释放的过程?至少是3的2000次方以上吧,这个数量需要多久??而你再看看你自己栈最大深度,远远不止2000吧。

到此为止,所以的分析完毕,但还是有些疑问不能解释:

疑问1:java栈深度是否会根据栈内存使用情况动态变化?

因为在一长串的try输出中,我无意间发现了一个catch,这是我公司电脑的输出,而家里的电脑就没有这种输出。

疑问2:是否会因为jdk版本、发行商或是参数设置的问题导致这段程序的输出结果不同?(上面说的三种输出结果中的1和2)

疑问3:为什么我加了一个没有用到的方法(加的方法必须要抛异常才可以)会改变原来的输出?(上面说的三种输出结果中的2和3)

疑问4:为什么上面的输出有些不换行?

时间: 2024-10-10 04:54:46

Java解惑之try catch finally的相关文章

《Java解惑》读书笔记

 摘选自<Java解惑>一书,之前整理了部分,一直没看完,最近为了督促自己每天读点这本书,决定一天至少更新一个谜题的内容,欢迎讨论. 欢迎关注技术博客http://blog.sina.com.cn/u/1822488043 Java解惑读书笔记 谜题1:奇数性 取余操作的定义: ( a / b ) * b + ( a % b ) = a 其中(a/b)是java运算的结果,也就是a/b是一个整数,比如3/2=1. 所以当取余操作返回一个非零结果的时候,它与左操作数具有相同符号. 请测试你的

Java解惑八:更多库之谜

本文是根据JAVA解惑这本书,做的笔记. 电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题76 将线程的启动方法start(),写成了run(); PS:管程(monitor)锁有待进一步理解. 谜题77 线程中锁的问题. 理解不深刻. 谜题78 反射会造成访问其他包中的非公共类型的成员,引起运行期异常. 谜题79 遮蔽:Thread.sleep()方法遮蔽了自定的方法. 谜题80 反射:如何实例化非静态内部类以及静态内部类.

Java解惑八:很多其它库之谜

本文是依据JAVA解惑这本书,做的笔记. 电子书见:http://download.csdn.net/detail/u010378705/7527721 谜题76 将线程的启动方法start(),写成了run(); PS:管程(monitor)锁有待进一步理解. 谜题77 线程中锁的问题. 理解不深刻. 谜题78 反射会造成訪问其它包中的非公共类型的成员.引起执行期异常. 谜题79 遮蔽:Thread.sleep()方法遮蔽了自定的方法. 谜题80 反射:怎样实例化非静态内部类以及静态内部类.

【java解惑】java编译器对空final赋值的妥协

如下所示代码: public class Example038 { private final static String GUEST_ID = "ape_it"; private final static String ID; static { try { ID = getID(); } catch (GetIDException e) { ID = GUEST_ID; e.printStackTrace(); } } private static String getID() th

【java解惑】&和&&、|和||使用

如下所示代码: public class Example042 { public static void main(String[] args) { int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 }, { 1, 2, 3 }, { 1, 2, 3, 4 }, { 1 } }; System.out.println("func1 out : " + func1(tests)); } private static int func1(int[

【java解惑】关于异常捕获的三条要求

有如下所示代码: public class Example037 { static void output1() { try { System.out.println("如果一个 catch 子句要捕获一个类型为 E 的被检查异常," + "而其相对应的 try 子句不能抛出 E 的某种子类型的异常,那么这就是一个编译期错误"); } catch (IOException e) { System.out.println(""); } } stat

【java解惑】字符串的字节数组构造函数使用

如下代码: public class Example018 { public static void main(String[] args) { byte bs[] = new byte[256]; for (int i = 0; i < 256; i++) { bs[i] = (byte) i; } string(bs);// 调用1 string(bs, "iso-8859-1");// 调用2 string(bs, "gbk");// 调用3 strin

【java解惑】java构造器的那些事儿

如下所示代码: public class Example040 { private Example040 e40 = new Example040(); public Example040() throws Exception { throw new Exception("这里是exception,不是error"); } public void output() { System.out.println("a new class"); } public stati

【java解惑】输入输出流使用后及时关闭问题

如下所示代码: public class Example041 { public static void main(String[] args) throws IOException { Example041 e41 = new Example041(); e41.copy("d:\\微信名ape_it.txt", "d:\\微信名爱题猿.txt"); } private void copy(String src, String dest) throws IOExc