Core Java 总结(异常类问题)
http://www.cnblogs.com/kubixuesheng/p/5968347.html
所有代码均在本地编译运行测试,环境为 Windows7 32位机器 + eclipse Mars.2 Release (4.5.2)
2016-10-17 整理
下面的代码输出结果是多少?为什么?并由此总结几个编程规范。
1 class smallT { 2 public static void main(String args[]) { 3 smallT t = new smallT(); 4 int b = t.get(); 5 System.out.println(b); 6 } 7 8 public int get() { 9 try { 10 return 1; 11 } finally { 12 return 2; 13 } 14 } 15 }
原因:查看字节码
public int get(); Code: 0: goto 4 3: pop 4: iconst_2 5: ireturn Exception table: from to target type 0 3 3 any
get方法执行开始,直接产生了一个goto指令跳转到了4处,iconst_2指令将常量2压入操作数栈,跳过了pop指令。然后执行ireturn指令,从当前方法返回2。
如下代码,finally里的代码会不会执行?如执行,是在return前还是后?
1 public class Test1 { 2 public static void main(String[] args) { 3 System.out.println(new Test1().test()); 4 } 5 6 static int test() { 7 int x = 1; 8 try { 9 return x; 10 } finally { 11 ++x; 12 } 13 } 14 }
详细原因看字节码:
static int test(); Code: 0: iconst_1 1: istore_0 2: iload_0 3: istore_2 4: iinc 0, 1 7: iload_2 8: ireturn 9: astore_1 10: iinc 0, 1 13: aload_1 14: athrow
iconst_1: 常数1进栈, istore_0: 栈顶元素1出栈,并把 1 保存在本地变量表的第1个位置里(下标为0的位置),iload_0 将本地变量表第一个位置的 1 推送至栈顶, istore_2: 栈顶元素 1 出栈,并把 1 保存在本地变量表的第3个位置里(下标为2的位置),iinc指令对本地变量表的第一个位置元素+1,iload_2 将本地变量表第3个位置的 1 推送至栈顶,准备执行ireturn指令返回……
下面有关JAVA异常类的描述,说法错误的是?
纯粹的概念问题,下面总结下:
在Java中异常被当做对象来处理,根类是java.lang.Throwable类,在Java中定义了很多异常类(如OutOfMemoryError、NullPointerException、IndexOutOfBoundsException等),这些异常类分为两大类:Error和Exception。
Error是无法处理的异常,比如OutOfMemoryError,一般发生这种异常,JVM会选择终止程序。因此我们编写程序时不需要关心这类异常。Exception,也就是我们经常见到的一些异常情况,比如NullPointerException、IndexOutOfBoundsException,这些异常是我们可以处理的异常。Exception类的异常包括checked exception和unchecked exception(unchecked exception也称运行时异常RuntimeException,当然这里的运行时异常并不是说的运行期间的异常,只是Java中用运行时异常这个术语来表示,Exception类的异常都是在运行期间发生的)。
unchecked exception(非检查异常),也称运行时异常(RuntimeException),比如常见的NullPointerException、IndexOutOfBoundsException。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。 checked exception(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过
图中红色部分为受检查异常。它们必须被捕获,或者在函数中声明为抛出该异常。
下面程序的输出是什么?
下面程序执行结果?
import java.io.IOException; class Arcane1 { public static void main(String[] args) { try { System.out.println("Hello world"); } catch (IOException e) { System.out.println("I've never seenprintln fail!"); } } }
补充一个println方法的源码。
public void println(String x) { synchronized (this) { print(x); newLine(); } }
下面程序执行结果?
class Arcane2 { public static void main(String[] args) { try { } catch (Exception e) { System.out.println("This can't happen"); } } }
下面程序执行结果?
1 interface Type1 { 2 void f() throws CloneNotSupportedException; 3 } 4 5 interface Type2 { 6 void f() throws InterruptedException; 7 } 8 9 interface Type3 extends Type1, Type2 { 10 } 11 12 class Arcane3 implements Type3 { 13 public void f() { 14 System.out.println("Hello world"); 15 } 16 17 public static void main(String[] args) { 18 Type3 t3 = new Arcane3(); 19 t3.f(); 20 } 21 }
这三个程序说明了一项基本要求,即对于捕获被检查异常的catch 子句,只有在相应的try 子句可以抛出这些异常时才被允许。第二个程序说明了这项要求不会应用到的冷僻案例。第三个程序说明了多个继承而来的throws 子句取的是交集,即异常将减少而不是增加。
下面程序执行结果?
class UnwelcomeGuest { public static final long GUEST_USER_ID = -1; private static final long USER_ID; static { try { USER_ID = getUserIdFromEnvironment(); } catch (IdUnavailableException e) { USER_ID = GUEST_USER_ID; System.out.println("Logging in as guest"); } } private static long getUserIdFromEnvironment() throws IdUnavailableException { throw new IdUnavailableException(); } public static void main(String[] args) { System.out.println("User ID: " + USER_ID); } } class IdUnavailableException extends Exception { }
程序将尝试着从其环境中读取一个用户ID,如果这种尝试失败了,则缺省地认为它是一个来宾用户。
下面程序执行结果?
public class Test1 { public static void main(String[] args) { try { System.out.println("Hello world"); System.exit(0); } finally { System.out.println("Goodbye world"); } } }
下面程序执行结果?
class Reluctant { private Reluctant internalInstance = new Reluctant(); public Reluctant() throws Exception { throw new Exception("I'm not coming out"); } public static void main(String[] args) { try { Reluctant b = new Reluctant(); System.out.println("Surprise!"); } catch (Exception ex) { System.out.println("I told you so"); } } }
下面程序执行结果?如有错误如何修改?
class Reluctant { private static Class<Reluctant> engineClass = Reluctant.class; private Engine engine = (Engine) engineClass.newInstance(); public Reluctant() { } } class Engine extends Reluctant { }
我的修改:
下面程序执行结果?
class Reluctant { static void copy(String src, String dest) throws IOException { InputStream in = null; OutputStream out = null; try { in = new FileInputStream(src); out = new FileOutputStream(dest); byte[] buf = new byte[1024]; int n; while ((n = in.read(buf)) > 0) out.write(buf, 0, n); } finally { if (in != null) in.close(); if (out != null) out.close(); } } }
方法将一个文件拷贝到另一个文件,并且被设计为要关闭它所创建的每一个流,即使它碰到I/O 错误也要如此。
下面程序执行结果?
class Reluctant { public static void main(String[] args) { int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 }, { 1, 2, 3 }, { 1, 2, 3, 4 }, { 1 } }; int successCount = 0; try { int i = 0; while (true) { if (thirdElementIsThree(tests[i++])) successCount++; } } catch (ArrayIndexOutOfBoundsException e) { // No more tests to process } System.out.println(successCount); } private static boolean thirdElementIsThree(int[] a) { return a.length >= 3 & a[2] == 3; } }
总之,尽量不要去用异常终止循环,因为这种用法非常不清晰,而且会掩盖bug。要意识到逻辑 AND 和 OR 操作符的存在,并且不要因无意识的误用而受害。
观察下面三个类,按照要求说明执行结果?
class Strange1 { public static void main(String[] args) { try { Missing m = new Missing(); } catch (java.lang.NoClassDefFoundError ex) { System.out.println("Got it!"); } } } class Strange2 { public static void main(String[] args) { Missing m; try { m = new Missing(); } catch (java.lang.NoClassDefFoundError ex) { System.out.println("Got it!"); } } } class Missing { Missing() { } }
编译所有这三个类,然后在运行Strange1 和Strange2 之前删除Missing.class 文件,然后运行前面两个类,结果分别是什么?
0: new 3: dup 4: invokespecial #3; //Method Missing."<init>":()V 7: astore_1 8: goto 20 11: astore_1 12: getstatic #5; // Field System.out:Ljava/io/PrintStream; 15: ldc #6; // String "Got it!" 17: invokevirtual #7;//Method PrintStream.println: (String); V 20: return Exception table: from to target type 0 8 11 Class java/lang/NoClassDefFoundError
Strange2.main 相对应的字节码与其只有一条指令不同:11: astore_2,这是一条将 catch 语句块中的捕获异常存储到捕获参数 ex 中的指令。在 Strange1 中,这个参数是存储在 VM 变量 1 中的,而在Strange2 中,它是存储在VM 变量 2 中的。这就是两个类之间唯一的差异,但是它所造成的程序行为上的差异是很大的!
为了运行一个程序,VM 要加载和初始化包含main 方法的类。在加载和初始化之间,VM 必须链接(link)类。链接的第一阶段是校验,校验要确保一个类是完整的,并且遵循语法要求。两个main都有一个连接点,连接点是汇聚控制流的,上面main的连接点就是指令20.try如果正常结束,就会执行指令8,goto到20,catch语句块结束也要从指令17走到指令20.问题就出在这里,现在有两条路径达到链接点,由于两条路径中各有一个astore(指令7和指令11),Strange1.main在路径1(try到return)中,用VM变量1存储了m,在路径2(catch到return)中又用VM变量1存储异常,因此要进行变量类型合并。所以要检测Missing和NoClassDefFoundError的超类,因为Missing.class已经被删除了,所以问题出现了,要抛出NoClassDefFoundError异常。因为此时还是在main执行之前发生的,所以当然无法捕获了。这个异常是在校验期间、在类被初始化之前,并且在main 方法开始执行之前很早就抛出的。这就解释了为什么没有打印出任何关于这个未被捕获异常的跟踪栈信息。要想编写一个能够探测出某个类是否丢失的程序,请使用反射来引用类而不要使用通常的语言结构。
总之,不要对捕获NoClassDefFoundError 形成依赖。语言规范非常仔细地描述了类初始化是在何时发生的,但是类被加载的时机却不可预测。更一般地讲,捕获 Error 及其子类型几乎是完全不恰当的。这些异常是为那些不能被恢复的错误而保留的。
下面程序中的方法workHard() 执行后会出现什么结果?
class Workout { public static void main(String[] args) { workHard(); System.out.println("It's nap time."); } private static void workHard() { try { workHard(); } finally { workHard(); } } }
要不是有try-finally 语句,该程序的行为将非常明显:workHard 方法递归地调用它自身,直到程序抛出*Error,在此刻它以这个未捕获的异常而终止。但是,try-finally 语句把事情搞得复杂了。当它试图抛出*Error 时,程序将会在finally 语句块的workHard 方法中终止,这样,它就递归调用了自己。这看起来确实就像是一个无限循环的秘方,但是这个程序真的会无限循环下去吗?如果运行它,它似乎确实是这么做的,假设栈的深度为3,这比它实际的深度要小得多。现在让我们来跟踪其执行过程。main 方法调用workHard,而它又从其try 语句块中递归地调用了自己,然后它再一次从其try 语句块中调用了自己。在此时,栈的深度是3。当workHard 方法试图从其try 语句块中再次调用自己时,该调用立即就会以*Error 而失败。这个错误是在最内部的finally 语句块中被捕获的,在此处栈的深度已经达到了3。在那里,workHard 方法试图递归地调用它自己,但是该调用却以*Error 而失败。这个错误将在上一级的finally 语句块中被捕获,在此处站的深度是2。该finally 中的调用将与相对应的try 语句块具有相同的行为:最终都会产生一个*Error。
当栈深度为2的时候,WorkOut 的运行过程如图所示。在这张图中,对workHard 的调用用箭头表示,workHard 的执行用圆圈表示,try 语句块中的调用用向左边的向下箭头表示,finally 语句块中的调用用向右边的向下箭头表示。箭头上的数字描述了调用的顺序。这张图展示了一个深度为0 的调用(即main 中的调用),两个深度为1 的调用,四个深度为2 的调用,总共是7 个调用。那4个深度为2的调用每一个都会立即产生*Error。至少在把栈的深度限制为2 的VM 上,该程序不会是一个无限循环:它在7 个调用和4 个异常之后就会终止。但是对于真实的VM 又会怎样呢?它仍然不会是一个无限循环。其调用图与前面的图相似,只不过要大得多得多而已。那么,究竟大到什么程度呢?
有一个快速的试验表明许多 VM 都将栈的深度限制为1024,因此,调用的数量就是1+2+4+8…+2^1024,而抛出的异常的数量是2^1024。假设我们的机器可以在每秒钟内执行1010 个调用,并产生1010个异常,按照当前的标准,这个假设的数量已经相当高了。在这样的假设条件下,程序将在大约1.7×10^291 年后终止,而太阳的生命周期大约是1010 年,可以很确定,没有人能够看到这个程序终止的时刻。尽管它不是一个无限循环,但是它也就算是一个无限循环吧。
从技术角度讲,调用图是一棵完全二叉树,它的深度就是VM 的栈深度的上限。WorkOut 程序的执行过程等于是在先序遍历这棵树。在先序遍历中,程序先访问一个节点,然后递归地访问它的左子树和右子树。对于树中的每一条边,都会产生一个调用,而对于树中的每一个节点,都会抛出一个异常。它证明了指数算法对于除了最小输入之外的所有情况都是不可行的,它还表明了使用递归可以编写出一个指数算法,但是不推荐。
如下代码的输出是?
以下关于JAVA语言异常处理描述正确的有?
运行时异常与一般异常有何异同?
error和exception有什么区别?
简述Java中的异常处理机制的简单原理和应用。
请写出你最常见到的5个runtime exception。