Java解惑4-异常谜题

来源:互联网 发布:淘宝客网站怎么建 编辑:程序博客网 时间:2024/06/05 11:41

谜题36:优柔寡断

下面这个可怜的小程序并不能很好地做出其自己的决定。它的decision方法将返回true,但是它还返回了false。那么,它到底打印的是什么呢?甚至,它是合法的吗? public class Indecisive {     public static void main(String[] args) {        System.out.println(decision());    }    static boolean decision() {        try {            return true;        } finally {            return false;        }    } }
                      题目: false

                      原因:

原因就是在一个try-finally语句中,finally语句块总是在控制权离开try语句块时执行的[JLS 14.20.2]。无论try语句块是正常结束的,还是意外结束的,情况都是如此。一条语句或一个语句块在它抛出了一个异常,或者对某个封闭型语句执行了一个break或continue,或是象这个程序一样在方法中执行了一个return时,将发生意外结束。它们之所以被称为意外结束,是因为它们阻止程序去按顺序执行下面的语句。

当try语句块和finally语句块都意外结束时,在try语句块中引发意外结束的原因将被丢弃,而整个try-finally语句意外结束的原因将于finally语句块意外结束的原因相同。在这个程序中,在try语句块中的return语句所引发的意外结束将被丢弃,而try-finally语句意外结束是由finally语句块中的return造成的。简单地讲,程序尝试着(try)返回(return)true,但是它最终(finally)返回(return)的是false。

丢弃意外结束的原因几乎永远都不是你想要的行为,因为意外结束的最初原因可能对程序的行为来说会显得更重要。对于那些在try语句块中执行break、continue或return语句,只是为了使其行为被finally语句块所否决掉的程序,要理解其行为是特别困难的。

总之,每一个finally语句块都应该正常结束,除非抛出的是不受检查的异常。千万不要用一个return、break、continue或throw来退出一个finally语句块,并且千万不要允许将一个受检查的异常传播到一个finally语句块之外去。

对于语言设计者,也许应该要求finally语句块在未出现不受检查的异常时必须正常结束。朝着这个目标,try-finally结构将要求finally语句块可以正常结束[JLS 14.21]。return、break或continue语句把控制权传递到finally语句块之外应该是被禁止的,任何可以引发将被检查异常传播到finally语句块之外的语句也同样应该是被禁止的。


谜题37:极端不可思议

本谜题测试的是你对某些规则的掌握程度,这些规则用于声明从方法中抛出并被catch语句块所捕获的异常。下面的三个程序每一个都会打印些什么?不要假设它们都可以通过编译: import java.io.IOException;public class Arcane1 {    public static void main(String[] args) {        try {            System.out.println("Hello world");        } catch(IOException e) {            System.out.println("I've never seen               println fail!");        }    }}public class Arcane2 {    public static void main(String[] args) {        try {            // If you have nothing nice to say, say nothing        } catch(Exception e) {            System.out.println("This can't                 happen");        }    }}interface Type1 {    void f() throws CloneNotSupportedException;}interface Type2 {    void f() throws InterruptedException;}interface Type3 extends Type1, Type2 {}public class Arcane3 implements Type3 {    public void f() {        System.out.println("Hello world");    }    public static void main(String[] args) {        Type3 t3 = new Arcane3();        t3.f();    }}

第一个程序,Arcane1,展示了被检查异常的一个基本原则。它看起来应该是可以编译的:try子句执行I/O,并且catch子句捕获IOException异常。但是这个程序不能编译,因为println方法没有声明会抛出任何被检查异常,而IOException却正是一个被检查异常。语言规范中描述道:如果一个catch子句要捕获一个类型为E的被检查异常,而其相对应的try子句不能抛出E的某种子类型的异常,那么这就是一个编译期错误[JLS 11.2.3]。

第二个程序,Arcane2,看起来应该是不可以编译的,但是它却可以。它之所以可以编译,是因为它唯一的catch子句检查了Exception。尽管JLS在这一点上十分含混不清,但是捕获Exception或Throwble的catch子句是合法的,不管与其相对应的try子句的内容为何。尽管Arcane2是一个合法的程序,但是catch子句的内容永远的不会被执行,这个程序什么都不会打印。

上述分析的缺陷在于对“Type3.f可以抛出在Type1.f上声明的异常和在Type2.f上声明的异常”所做的假设。这并不正确,因为每一个接口都限制了方法f可以抛出的被检查异常集合。一个方法可以抛出的被检查异常集合是它所适用的所有类型声明要抛出的被检查异常集合的交集,而不是合集。因此,静态类型为Type3的对象上的f方法根本就不能抛出任何被检查异常。因此,Arcane3可以毫无错误地通过编译,并且打印Hello world。

总之,第一个程序说明了一项基本要求,即对于捕获被检查异常的catch子句,只有在相应的try子句可以抛出这些异常时才被允许。第二个程序说明了这项要求不会应用到的冷僻案例。第三个程序说明了多个继承而来的throws子句的交集,将减少而不是增加方法允许抛出的异常数量。本谜题所说明的行为一般不会引发难以捉摸的bug,但是你第一次看到它们时,可能会有点吃惊。


谜题38:不受欢迎的宾客

本谜题中的程序所建模的系统,将尝试着从其环境中读取一个用户ID,如果这种尝试失败了,则缺省地认为它是一个来宾用户。该程序的作者将面对有一个静态域的初始化表达式可能会抛出异常的情况。因为Java不允许静态初始化操作抛出被检查异常,所以初始化必须包装在try-finally语句块中。那么,下面的程序会打印出什么呢? public 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 { }
略:  USER_ID为final!


谜题39:您好,再见!


下面的程序在寻常的Hello world程序中添加了一段不寻常的曲折操作。那么,它将会打印出什么呢? public class HelloGoodbye {    public static void main(String[] args) {        try {            System.out.println("Hello world");            System.exit(0);        } finally {            System.out.println("Goodbye world");        }    } }

当System.exit被调用时,虚拟机在关闭前要执行两项清理工作。首先,它执行所有的关闭挂钩操作,这些挂钩已经注册到了Runtime.addShutdownHook上。这对于释放VM之外的资源将很有帮助。务必要为那些必须在VM退出之前发生的行为关闭挂钩。
VM执行在System.exit被调用时执行的第二个清理任务与终结器有关。如果System.runFinalizerOnExit或它的魔鬼双胞胎Runtime.runFinalizersOnExit被调用了,那么VM将在所有还未终结的对象上面调用终结器。这些方法很久以前就已经过时了,而且其原因也很合理。无论什么原因,永远不要调用System.runFinalizersOnExit和Runtime.runFinalizersOnExit:它们属于Java类库中最危险的方法之一[ThreadStop]。调用这些方法导致的结果是,终结器会在那些其他线程正在并发操作的对象上面运行,从而导致不确定的行为或导致死锁。
总之,System.exit将立即停止所有的程序线程,它并不会使finally语句块得到调用,但是它在停止VM之前会执行关闭挂钩操作。当VM被关闭时,请使用关闭挂钩来终止外部资源。通过调用System.halt可以在不执行关闭挂钩的情况下停止VM,但是这个方法很少使用。

谜题40:不情愿的构造器

尽管在一个方法声明中看到一个throws子句是很常见的,但是在构造器的声明中看到一个throws子句就很少见了。下面的程序就有这样的一个声明。那么,它将打印出什么呢? public 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");        }    }}
结果:  但是当你尝试着去运行它时,就会发现它压根没有去做这类的事情:它抛出了StackOverflowError异常,为什么呢?       
private Reluctant internalInstance = new Reluctant(); should be  
private static Reluctant internalInstance = new Reluctant(); 

谜题41:域和流

下面的方法将一个文件拷贝到另一个文件,并且被设计为要关闭它所创建的每一个流,即使它碰到I/O错误也要如此。遗憾的是,它并非总是能够做到这一点。为什么不能呢,你如何才能订正它呢? 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();        } }
解决方式是将每一个close都包装在一个嵌套的try语句块中。下面的finally语句块的版本可以保证在两个流上都会调用close: } finally {     if (in != null) {          try {              in.close();          } catch (IOException ex) {              // There is nothing we can do if close fails          }     if (out != null)          try {              out.close();          } catch (IOException ex) {              // There is nothing we can do if close fails          }    } }从5.0版本开始,你可以对代码进行重构,以利用Closeable接口: } finally {     closeIgnoringException(in);     closeIgnoringEcception(out);}private static void closeIgnoringException(Closeable c) {     if (c != null) {           try {             c.close();           } catch (IOException ex) {             // There is nothing we can do if close  fails           }     }}

谜题42:异常为循环而抛

下面的程序循环遍历了一个int类型的数组序列,并且记录了满足某个特定属性的数组个数。那么,该程序会打印出什么呢? public class Loop {    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;    }}
    略:  与或的问题

谜题43:异常地危险

在JDK1.2中,Thread.stop、Thread.suspend以及其他许多线程相关的方法都因为它们不安全而不推荐使用了[ThreadStop]。下面的方法展示了你用Thread.stop可以实现的可怕事情之一。     // Don’t do this - circumvents exception checking!    public static void sneakyThrow(Throwable t) {Thread.currentThread().stop(t); // Deprecated!!    }
略。

谜题44:切掉类

请考虑下面的两个类: public class Strange1 {    public static void main(String[] args) {        try {            Missing m = new Missing();        } catch (java.lang.NoClassDefFoundError ex) {            System.out.println("Got it!");        }    }}public class Strange2 {    public static void main(String[] args) {        Missing m;        try {            m = new Missing();        } catch (java.lang.NoClassDefFoundError ex) {            System.out.println("Got it!");        }    } }Strange1和Strange2都用到了下面这个类: class Missing {    Missing() { }}

捕获类,最好使用

public class Strange {    public static void main(String[] args) throws      Exception{        try {            Object m = Class.forName("Missing").                       newInstance();        } catch (ClassNotFoundException ex) {            System.err.println("Got it!");        }    }}

谜题45:令人疲惫不堪的测验

本谜题将测试你对递归的了解程度。下面的程序将做些什么呢? public class Workout {    public static void main(String[] args) {        workHard();        System.out.println("It's nap time.");    }    private static void workHard() {        try {            workHard();        } finally {            workHard();        }    }}
略。











原创粉丝点击