Java谜题畅读版之异常谜题

谜题36:优柔寡断

下面的程序打印的是什么呢?甚至,它是合法的吗?
public class Indecisive {
    public static void main(String[] args) {
        System.out.println(decision());
    }
    static boolean decision() {
        try {
            return true;
        } finally {
            return false;
        }
    }
}
这个程序会打印false。我是这么理解的,在try catch里面如果有return,则它会为返回值准备好位置,并把值放进去。如果finally里面也有return,它会覆盖返回值位置上的值。

谜题37:极端不可思议

下面的三个程序每一个都会打印些什么?不要假设它们都可以通过编译。
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();
    }
}
第一个程序不能通过编译, 因为catch捕获的是一个checked exception,而try里面并没有语句抛出checked exception,这是不行的。
第二个程序能通过编译,但不会打印任何东西。虽然try里面没有抛出任何异常,但捕获unchecked exception是可以的。
第三个程序可以打印Hello World。这里涉及到子类在复写方法的时候需要怎么处理父类声明的Exception问题。子类只是不可以扩大父类声明的Exception范围。

谜题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 {
}
本程序不能通过编译,编译器会提示:variable USER_ID might already have been assigned
对于final的变量,编译器很难确定它是否已经被赋值了。除非它明确地看到没有赋值的地方。

谜题39:您好,再见!

下面的程序打印什么?
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(0)方法将停止当前线程和所有其他当场死亡的线程. 也就是说finally级别没有它高。但是,你可以通过addShutdownHook来做一些exit之前必须处理的事情:
public class HelloGoodbye1 {
    public static void main(String[] args) {
       System.out.println("Hello world");
       Runtime.getRuntime().addShutdownHook(
             new Thread() {
                public void run() {
                       System.out.println("Goodbye world");
                }
            });
       System.exit(0);
    }
}

谜题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");
        }
    }
}
这个题目的标题和说明性的文字有点迷惑你,实际上这个程序的问题和构造函数里面抛异常没有关系。它和下面是等价的:
public class Reluctant {
    private Reluctant inst = new Reluctant();
    public static void main(String[] args) {
        Reluctant  r = new Reluctant();
    }
}
这个程序因为他有一个私有成员变量,私有成员变量在初始化的时候会调用构造函数,然而这一过程会一直递归调用下去,直到StackOverflowError。

谜题41:域和流

这个程序试图把数据从源文件流拷贝到目标文件流,然后关闭所有它打开的流,哪怕它遇到异常。它能正常处理吗?
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();
     }
}
这个程序这样的写法,是我以前喜欢的做法, 但自从工作中发生了一次事故之后,我再也不这样写了。当时我也在finally里面做一些其他连接的清理工作,然后关闭数据库连接池,程序结构和上面一样。后来这个程序出了大问题, 每当它运行几天之后,就抛错说连接池已经被用光。 问题出在那些清理工作抛出了新的异常,这个程序的debug也是很艰难的,因为不是每次都会抛异常。或者说只有在一定条件下才会。
对于稀缺资源,比如连接池,流,像下面这样关闭他们,几乎是必须的。
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
          }
    }
}

谜题42:异常为循环而抛

下面的程序大意是,对多维数组中的每一个数组,检查他们是否满足thirdElementIsThree这个规则。下面的程序正确吗?
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;
    }
}
这个程序犯了3个错误:
1 用了恐怖的while(true)作循环;
2 用catch Exception来做为退出循环的条件;

3 错误地使用了&;

谜题43:异常地危险

请写出一个程序, 程序里面会抛出没有被catch住的checked exception, 但是不会被编辑器拒绝.
有两种方法可以实现, 1是利用了Class.newInstance方法, 该方法会调用无参的构造函数, 如果该构造函数抛出checked exception的话, 编译器是检测不到的.

public class Thrower {
    public Thrower() throws IOException{
        throw new IOException();
    }
    
    public static void main(String[] args)
            throws InstantiationException, IllegalAccessException{
        Thrower thrower = Thrower.class.newInstance();
    }
}
Class.newInstance的文档继续描述道“Constructor.newInstance方法通过将构造器抛出的任何异常都包装在一个(受检查的)InvocationTargetException异常中而避免了这个问题.
第二种方式是利用了泛型.
class TigerThrower<T extends Throwable> {
 public static void sneakyThrow(Throwable t) {
     new TigerThrower<Error>().sneakyThrow2(t);
 }
 private void sneakyThrow2(Throwable t) throws T {
     throw (T) t;
 }
 
 public static void main(String[] args){
     sneakyThrow(new IOException());
 }
}

谜题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();
        }
    }
}
如果说没有try finally, 他就是一个简单的递归调用,直到StackOverflowError. 但是try finally把问题搞复杂了.
Java虚拟机对栈的深度限制到了某个预设的水平。当超过这个水平时,VM就抛出StackOverflowError。为了让我们能够更方便地考虑程序的行为,我们假设栈的深度为3,这比它实际的深度要小得多。现在让我们来跟踪其执行过程。
main方法调用workHard,而它又从其try语句块中递归地调用了自己,然后它再一次从其try语句块中调用了自己。在此时,栈的深度是3。当workHard方法试图从其try语句块中再次调用自己时,该调用立即就会以StackOverflowError而失败。这个错误是在最内部的finally语句块中被捕获的,在此处栈的深度已经达到了3。在那里,workHard方法试图递归地调用它自己,但是该调用却以StackOverflowError而失败。这个错误将在上一级的finally语句块中被捕获,在此处站的深度是2。该finally中的调用将与相对应的try语句块具有相同的行为:最终都会产生一个StackOverflowError。这似乎形成了一种模式,而事实也确实如此。

那么,究竟大到什么程度呢?有一个快速的试验表明许多VM都将栈的深度限制为1024,因此,调用的数量就是1+2+4+8…+21,024=2^1,025-1,而抛出的异常的数量是2^1,024。假设我们的机器可以在每秒钟内执行1010个调用,并产生1010个异常,按照当前的标准,这个假设的数量已经相当高了。在这样的假设条件下,程序将在大约1.7×10291年后终止。为了让你对这个时间有直观的概念,我告诉你,我们的太阳的生命周期大约是1010年,所以我们可以很确定,我们中没有任何人能够看到这个程序终止的时刻。尽管它不是一个无限循环,但是它也就算是一个无限循环吧。

[转载请注明来自http://blog.csdn.net/sunxing007]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值