Java 通过异常处理错误(下)

7.java标准异常

Throwable这个java类用来表示任何可以作为异常被抛出的类.Throwable对象可以分成两种, 一种时error, 用来表示编译时和系统错误, 一般用户不需要关心, 需要关心得是另一种Exception, 表示可以被抛出的基本类型, 在java类库, 用户方法,以及运行时故障可能抛出Exception行异常, 所以java一般更关心Exception.

7.1 特例: RuntimeException

如果调用了null的对象, 就会抛出NullPointerException, 这不需要你亲自动手去抛出, 而是java会自动抛出, 而NullPointerException属于运行时异常, 是继承自RuntimeException, 运行时异常不需要亲自去动手捕获, java会自动帮你捕获;

public class NeverCaught{
    static void f() {
        throw new RuntimeException("From f()");
    }
    static void g() {
        f();
    }
    public static void main(String[] args) {
        g();
    }
}

对于这种异常类型, 不需要异常说明, 其输出就被报告给了System.err

Exception in thread "main" java.lang.RuntimeException: From f()
    at NeverCaught.f(NeverCaught.java:3)
    at NeverCaught.g(NeverCaught.java:6)
    at NeverCaught.main(NeverCaught.java:9)
[Finished in 1.0s with exit code 1]

记住只有RuntimeException及其子类是可以在代码中忽略的, 其他类型的异常处理有编译器强制执行.

8.使用finally进行处理

对于一些代码, 无论try块是否有异常抛出, 都能得到处理, 比如对文件操作, 执行完了总是应该把文件关闭掉, 这时候就可以在异常处理最后再加上finally字句

class ThreeException extends Exception{}

public class FinallyWorks {
    static int count = 0;
    public static void main(String[] args) {
        while(true) {
            try {
                if (count++ == 0) {
                    throw new ThreeException();
                }
                System.out.println("No Exception");
            } catch (ThreeException e) {
                System.out.println("ThreeException");
            } finally {
                System.out.println("In finally clause");
                if (count == 2) break;
            }

        }
    }
}
ThreeException
In finally clause
No Exception
In finally clause

如果try中有return呢? 是否还是能总是被执行呢?

public class MultipleReturn {
    public static void f(int i) {
        System.out.println("Initialization that requires cleanup");
        try {
            System.out.println("point 1");
            if (i == 1) return;
            System.out.println("point 2");
            if (i == 2) return;
            System.out.println("point 3");
            if (i == 3) return;
            System.out.println("End");
            return;
        } finally {
            System.out.println("Performing cleanup");
        }
    }
    public static void main(String[] args) {
        for (int i = 1; i <= 4; i++) {
            f(i);
        }
    }
}

结果是肯定的当然是能的

Initialization that requires cleanup
point 1
Performing cleanup
Initialization that requires cleanup
point 1
point 2
Performing cleanup
Initialization that requires cleanup
point 1
point 2
point 3
Performing cleanup
Initialization that requires cleanup
point 1
point 2
point 3
End
Performing cleanup

遗憾: 异常丢失, 异常作为程序出错的标志, 应该不能被忽略, 然而还是有可能被轻易的忽略的, 用某些特殊的方式使用finally子句, 就会发生这种情况:

class VeryImportantException extends Exception {
    public String toString() {
        return "A very important exception!";
    }
}

class HoHumException extends Exception {
    public String toString() {
        return "A trivial exception";
    }
}

public class LostMessage {
    void f() throws VeryImportantException{
        throw new VeryImportantException();
    }

    void g() throws HoHumException {
        throw new HoHumException();
    }
    public static void main(String[] args) {
        try {
            LostMessage lost = new LostMessage();
            try {
                lost.f();
            } finally {
                lost.g();
            }
        } catch (Exception e) {
            System.out.println(e);
        }

    }
}

结果只输出了A trivial exception, VeryImportantException咋被吃掉了? 被finally子句中抛出HoHumException给取代了.
更加粗暴的一种异常丢失情形:

try {
    throw new Exception();
} finally {
    return;
}

9. 异常限制

  1. 当子类覆盖父类的方法时, 只能抛出基类方法异常说明中列出的异常
  2. 异常限制对构造器不起作用,派生类的构造器的异常说明必须不少于基类构造器的异常说明。且派生类构造器不能捕获基类构造器抛出的异常。
  3. 不抛出性。派生类方法可以不抛出任何异常,即使它是基类所定义的异常。
  4. 尽管在继承过程中,编译器对异常说明所强制要求,但是异常说明本身不是方法的一部分,方法是由方法的名字与参数的类型组成的。
  5. 异常限制与继承规则不同。某个方法的异常说明 不能变大,只能变小或者不变。

10 构造器

如果异常发生了, 所有东西总是能被正确的清理吗?
大多数情况下是可以的, 但是遇到构造器就会有各种问题, 比如在构造器中打开了一个文件,
进行异常捕获, 应该在finally中关闭文件吗? 万一如果文件都还没有打开就抛出异常, 那么关闭文件就没有意义了, 最安全方法就是嵌套使用try

import java.io.*;

class InputFile{
    private BufferedReader in;
    public InputFile(String fname) throws Exception {
        try {
            in = new BufferedReader(new FileReader(fname));
        } catch(FileNotFoundException e) {
            System.out.println("Could not open " + fname);
            throw e;
        } catch (Exception e) {
            try {
                in.close();
            } catch (IOException e2) {
                System.out.println("in.close() unsucessful");
            }
            throw e;
        } finally {

        }
    }

    public String getLine() {
        String s;
        try {
            s = in.readLine();
        } catch (IOException e) {
            throw new RuntimeException("readLIne() failed");
        }
        return s;
    }

    public void dispose() {
        try {
            in.close();
            System.out.println("dispose() successful");
        } catch (IOException e2) {
            throw new RuntimeException("in.close() failed");
        }
    }
}

public class Cleanup {
    public static void main(String[] args) {
        try {
            InputFile in = new InputFile("Cleanup.java");
            try {
                String s;
                while((s = in.getLine()) != null) {
                    // System.out.println(s);
                     
                }
            } catch (Exception e) {
                System.out.println("Caught Exception in main");
            } finally {
                in.dispose();
            }
        } catch (Exception e) {
            System.out.println("InputFile construction failed");
        }
    }
}

11 异常匹配

抛出异常的时候, 异常处理系统会按照代码的书写顺序找出”最近”的处理程序, 找到匹配的处理程序后, 就认为异常将得到处理, 然后就不在继续查找.
查找的时候并不要求异常同处理程序所声明的异常完全匹配, 派生类的对象也可以匹配基类的处理程序:

class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

class Human {
    public static void main(String[] args)  {
        try {
            throw new Sneeze();
        } catch (Sneeze s) {
            System.out.println("Caught Sneeze");
        } catch (Annoyance a) {
            System.out.println("Caught Annoyance");
        }
        try {
            throw new Sneeze();
        } catch (Annoyance a) {
            System.out.println("Caught Annoyance");
        }
    }
}

输出是

Caught Sneeze
Caught Annoyance

可以看到如果捕获了sneeze异常, 那么下一句就不会再执行了, 而且也可以将这一句去掉也是能运行的, 但是如果交换这两句的话, 就会出现错误, 以为这样第二个catch子句就永远无法访问了.

    原文作者:DongBold
    原文地址: https://www.jianshu.com/p/7edb9acc2116
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞