《Java 解惑》 第五章 异常之谜

简述:

《Java 解惑》 第五章 异常之谜 - 笔记


内容:

谜题36: try中的return不会影响finally中方法执行

package 异常之谜.优柔寡断;

public class Indecisive {
    public static void main(String[] args){
        System.out.println(decision());
    }

    static boolean decision() {
        try{
            return true;
        } finally {
            return false;
        }
    }
}

结果输出了false ,


原因:

无论try语句块是正常结束的,还是意外结束的,在一个try-finally语句中,finally语句块总是在控制权离开try语句块时执行

警告:不要使用return、break、continue或throw来退出finally语句块,并且不要让受检查的异常传播到finally语句之外



谜题37:受检查的异常不能随意捕获

1)


说明: try语句中没有声明会抛出任何受检查异常,而在catch字句中要捕获一个类型为E的受检查异常

这就是一个编译期错误


2)

package 异常之谜.极端不可思议;

public class Arcane2 {
    public static void main(String[] args) {
        try {
            // do nothing
        } catch (Exception e) {
            System.out.println("exception!!");
        }
    }
}

说明:捕获Exception或Throwable的catch子句是合法的,不管其相对应的try字句的内容为何


3)

Type1.java

public interface Type1 {
    void f() throws CloneNotSupportedException;
}

Type2.java

public interface Type2 {
    void f() throws InterruptedException;
}

Type3.java

public interface Type3 extends Type1, Type2 {
}

Arcane3.java

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();
    }
}


说明:一个方法可以抛出的受检查异常集合是它所适用的所有类型声明要抛出的受检查异常集合的 交集


谜题38:final 字段只有在的确未赋过值的地方才能被赋值



解释:

Java编译器规定,final字段只有在他的确未赋过值的地方才可以被赋值,而本例中try方法中可能会对USER_ID作赋值
编译器谨慎起见组织了catch中的赋值

解决方式,重构静态语句块中代码为一个辅助方法

package 异常之谜.不受欢迎的宾客;

public class UnwelcomeGuest {
    public static final long GUEST_USER_ID = -1;
    
    private static final long USER_ID = getUserIdOrGuest();
    
    private static long getUserIdOrGuest(){
        try {
            return getUserIdFromEnvironment();
        } catch (IdUnavailableException e) {
            System.out.println("Logging in as guest");
            return GUEST_USER_ID;
        }
    }
    
    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 {
    IdUnavailableException(){}
}




谜题39:System.exit 的中断

package 异常之谜.您好_再见;

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时, 虚拟机(VM)在关闭前要执行两项清理工作。首先它执行所有的关闭挂钩操作,这些挂钩已经注册到Runtime.addShutdownHook上。这对释放VM之外的资源很有帮助。 务必要为那些必须在VM退出之前发生的行为关闭挂钩。

package 异常之谜.您好_再见;

public class HelloGoodbye {
    public static void main(String[] args) {
        System.out.println("Hello World!");
        Runtime.getRuntime().addShutdownHook(
            new Thread() {
                @Override
                public void run() {
                    System.out.println("Goodbye World!");
                }
            });
        System.exit(0); //停止所有程序县城,在停止VM之前会执行关闭挂钩操作
    }
}
输出:




谜题40:构造器抛出的异常

package 异常之谜.不情愿的构造器;

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");
        }
    }
}

输出:




说明:

本程序包含了一个无线递归。当你调用一个构造器时,实例变量的初始化操作将先于构造器的程序体而运行。
实例变量的初始化操作将先于构造器的程序体而运行,本例中,internalInstance变量的初始化操作递归调用
了构造器,而该构造器通过再次调用Reluctant构造器而初始化该变量自己的internalInstance字段,而无限递归下去



谜题41:finally中关闭流也要考虑异常

package 异常之谜.字段和流;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class Test {
    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[2014];
            int n;
            while ((n = in.read(buf)) >= 0){
                out.write(buf, 0, n);
            }
        } finally {
            if (in != null)
                in.close();
            if(out != null)
                out.close();
        }
    }
    
    public static void main(String[] args) throws IOException {
        String src = "in.txt";
        String dest = "dest.txt";
        copy(src, dest);
    }
}

说明:
finally中的close方法也可能跑出IOException异常,如果这个正好发生在in.close被调用的时候,那么就会阻止out.close被调用

修改方式, 自定义finally中关闭的方法,从5.0开始,stream中都实现了closeable接口
package 异常之谜.字段和流;

import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class Test {
    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[2014];
            int n;
            while ((n = in.read(buf)) >= 0){
                out.write(buf, 0, n);
            }
        } finally {
            closeIgnoringException(in);
            closeIgnoringException(out);
        }
    }
    
    public static void main(String[] args) throws IOException {
        String src = "in.txt";
        String dest = "dest.txt";
        copy(src, dest);
    }
    
    private static void closeIgnoringException(Closeable c){
        if(c != null){
            try {
                c.close();
            } catch(IOException ex){
                // do if it fails
            }
        }
    }
}



谜题42:不要用异常终止循环


package 异常之谜.异常为循环而抛;

public class Loop {
    private static boolean thirdElementIsThree(int[] a) {
        return a.length>=3 && a[2]==3;
    }
    
    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);
    }
}


说明:
不要使用异常控制循环,应该只为异常条件而使用异常
修改为:
package 异常之谜.异常为循环而抛;

public class Loop {
    private static boolean thirdElementIsThree(int[] a) {
        return a.length>=3 && a[2]==3;
    }
    
    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;
        
        for (int[] test : tests) {
            if(thirdElementIsThree(test))
                successCount++;
        }
        
        
        System.out.println(successCount);
    }
}




谜题43:实现throw语句要做的事情,但是它绕过了编译器所有异常检查操作

package 异常之谜.异常地危险;

public class Test {
    public static void sneakyThrow(Throwable t){
        Thread.currentThread().stop();
    }
    
    public static void main(String[] args) {
        sneakyThrow(new Exception("超级异常"));
        System.out.println("execute after throw!");
    }
}

运行结束后,控制台没有输出什么东西,看来是被异常终止了
下面用Class.newInstance方法
该方法将传播从空的构造器所抛出的任何异常,包括受检查的异常,使用这个方法,可以有效地绕开在其他情况下都会执行的编译器异常检查
package 异常之谜.异常地危险;

public class Thrower {
    private static Throwable t;
    
    private Thrower() throws Throwable {
        throw t;
    }
    
    public static synchronized void sneakyThrow(Throwable t){
        Thrower.t = t;
        try {
            Thrower.class.newInstance();
        } catch (InstantiationException e) {
            throw new IllegalArgumentException();
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException();
        } finally {
            Thrower.t = null;  // Avoid Memory Leak
        }
    }
}


备注:finally语句块中赋值为空,是防止内存泄露
解释:Class.newInstance的文档描述,Constructor。newInstance方法通过将构造器抛出的任何异常都包装在一个(受检查的)InvocationTargetException异常中而避免了这个问题



谜题44:删除类

Strange1.java
package 异常之谜.删除类;

public class Strange1 {
    public static void main(String[] args) {
        try {
            Missing m = new Missing();
        } catch (java.lang.NoClassDefFoundError ex) {
            System.out.println("Got it!");
        }
    }
}

Strange2.java
package 异常之谜.删除类;

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!");
        }
    }
}

Missing.java
package 异常之谜.删除类;

public class Missing {
    Missing() {}
}

测试:
运行Strange1和Strange2之前删除Missing.class 文件,就会发现这两个程序的行为有所不同。
其中一个跑出了一个未被捕获的NoClassDefFoundError异常,而另一个却打印了Got it!

Strange1 运行结果:



Strange2 运行结果:




编写一个能够探测类丢失的程序,用反射来实现
package 异常之谜.删除类;

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




谜题45:无限函数递归


package 异常之谜.令人疲惫不堪的测验;

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();
        }
    }
}













猜你喜欢

转载自blog.csdn.net/anialy/article/details/43891381