只想把基础打好之-异常

在这里说说一些容易忽略的知识点。

  1. 对异常来说最重要的就是异常的类名。要做到见名知义。比如NullPointerException,IllegalStateException等,它们都只是简单地继承了RuntimeException。
public class NullPointerException extends RuntimeException {
    private static final long serialVersionUID = 5162710183389028792L;

    /**
     * Constructs a {@code NullPointerException} with no detail message.
     */
    public NullPointerException() {
        super();
    }

    /**
     * Constructs a {@code NullPointerException} with the specified
     * detail message.
     *
     * @param   s   the detail message.
     */
    public NullPointerException(String s) {
        super(s);
    }
}
  1. printStackTrace()方法所提供人信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。
public class WhoCalled {
    static void f(){
        try {
            throw new Exception();
        }catch (Exception e){
                  for(StackTraceElement ste:e.getStackTrace()){
                      System.out.println(ste.getMethodName());
                  }
        }
    }
    static void g(){f();}
    static void h(){g();}
    public static void main(String[] args){
        f();
        System.out.println("#############################");
        g();
        System.out.println("#############################");
        h();
        System.out.println("#############################");
    }
}

运行结果:

f
main
invoke0
invoke
invoke
invoke
main
#############################
f
g
main
invoke0
invoke
invoke
invoke
main
#############################
f
g
h
main
invoke0
invoke
invoke
invoke
main
#############################
  1. fillInStackTrack()用来更新栈信息,fillInStackTrack()方法将返回一个Throwable对象。通过下面的例子会直观一些。
public class Rethrowing {
    public static void f() throws Exception{
        System.out.println("f方法里的原始异常");
        throw new Exception("从f方法里面抛出的异常");
    }
    public static void g() throws Exception{
        try {
            f();
        } catch (Exception e) {
            System.out.println("g方法里面的catch代码块");
            e.printStackTrace(System.out);
            throw e;
        }
    }
    public static void h()throws Exception{
        try {
            f();
        } catch (Exception e) {
            System.out.println("h方法里面的代码块");
            e.printStackTrace(System.out);
            throw (Exception) e.fillInStackTrace();
        }
    }
    public static void main(String[] args){
        try {
            g();
        } catch (Exception e) {
            System.out.println("main方法中调用g方法");
            e.printStackTrace(System.out);
        }
        System.out.println("#####################");
        try {
            h();
        } catch (Exception e) {
            System.out.println("main方法中调用h方法");
            e.printStackTrace(System.out);
        }

    }
}

运行结果:

f方法里的原始异常
g方法里面的catch代码块
java.lang.Exception: 从f方法里面抛出的异常
    at exception.Rethrowing.f(Rethrowing.java:9)
    at exception.Rethrowing.g(Rethrowing.java:13)
    at exception.Rethrowing.main(Rethrowing.java:31)
main方法中调用g方法
java.lang.Exception: 从f方法里面抛出的异常
    at exception.Rethrowing.f(Rethrowing.java:9)
    at exception.Rethrowing.g(Rethrowing.java:13)
    at exception.Rethrowing.main(Rethrowing.java:31)
#####################
f方法里的原始异常
h方法里面的代码块
java.lang.Exception: 从f方法里面抛出的异常
    at exception.Rethrowing.f(Rethrowing.java:9)
    at exception.Rethrowing.h(Rethrowing.java:22)
    at exception.Rethrowing.main(Rethrowing.java:38)
main方法中调用h方法
java.lang.Exception: 从f方法里面抛出的异常
    at exception.Rethrowing.h(Rethrowing.java:26)
    at exception.Rethrowing.main(Rethrowing.java:38)

4.异常链,Throwable的子类在构造器中都可以一个cause对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得在当前位置创建并抛出新的异常,也能通过这个异常链追踪最初发生的位置。需要注意的是,在Throwabe的子类中,只有三种基本的异常类提供了带cause参数构造器它们是Error,Exception和RuntimeException。如果要把其它类型的异常链接起来,应该使用initCause()方法而不是构造器。比如说:

new RuntimeException的子类(RuntimeException的其它子类的对象);
Exception的子类的对象.initCause(RuntimeException的子类对象);

5.在try块里有return,finally是会执行的。那如果finally也有return呢,会返回哪个呢?

public class FinallyTest {
    public static void main(String[] args){
System.out.println(testFinally());
    }
    private static String testFinally(){
        try{
            return "try";
        }finally {
            return "finally";
        }
    }
}

运行结果:

finally

这种结构会导致代码可读性变差。
5.异常的不足就是会导致异常丢失。异常作为程序出错的标志决不应该被忽略。然而某些使用finally子句就会发生这种情况。

public class LostException {
    class VeryImportantException extends Exception{
        @Override
        public String toString() {
            return "very important exception";
        }
    }
    class HoHumException extends Exception{
        @Override
        public String toString() {
            return "hohum exception";
        }
    }
    void f() throws VeryImportantException{
        throw new VeryImportantException();
    }
    void dispose() throws HoHumException{
        throw new HoHumException();
    }
    public static void main(String[] args){
        try{
            LostException lost=new LostException();
            try{
                lost.f();
            }finally {
                lost.dispose();
            }
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

运行结果:

hohum exception

从输出结果中看到VeryImportantException不见了,它被finally子句中的HoHumException取代。这是相当严重的缺陷。
6.异常的限制,当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这些限制很有用,因为这意味着,当基类使用的代码应用到派生类对象的时候,一样能够工作,当然这是面向对象的基本概念。异常也不例外。

public class ExceptionTest {
    class BaseException extends Exception{};
    class Foul extends BaseException{};
    class Strike extends BaseException{};
    abstract class Inning{
        public Inning () throws BaseException{}
        public void event () throws BaseException{}
        public abstract void atBat() throws Strike,Foul;
        public void walk(){};
    }
    class StormException extends Exception{};
    class RainedOut extends StormException{};
    class PopFoul extends Foul{};
    interface Storm{
        public void event() throws RainedOut;
        void rainHard() throws RainedOut;
    }
    public class StormyInning extends Inning implements Storm{
        //因为Inning的构造器抛出异常,所以它的继承类必须处理父类的异常,当然也可以抛出自己想要处理的异常
        public StormyInning()  throws BaseException,Foul{
        }
        //下面这个方法不能通过编译,因为在Inning方法中并没有抛出异常
//        @Override
//        public void walk() throws PopFoul{
//
//        }
        //接口不能基类中的方法添加异常,所以下面也不能编译
//        @Override
//        public void event() throws RainedOut{
//
//        }
        @Override
        public void event() {
        }
        //重写的方法可以抛出继承的异常
        @Override
        public void atBat() throws PopFoul {
        }
        @Override
        public void rainHard() throws RainedOut {
        }
    }
}

在Inning类中。可以看到构造器和event方法都声明将抛出异常,但实际上并没有抛出,这种方式使你强制用户去捕获可能在覆盖的event版本中增加的异常。

7.被检查异常并不总是好的,它有时候会使问题变得复杂,因为它强制你在还没有准备好处理问题的时候加上catch子句。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 121,117评论 16 134
  • 小编费力收集:给你想要的面试集合 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JA...
    八爷君阅读 3,664评论 1 115
  • 多态 任何域的访问操作都将有编译器解析,如果某个方法是静态的,它的行为就不具有多态性 java默认对象的销毁顺序与...
    yueyue_projects阅读 585评论 0 1
  • 2017年10月21日,我参加了小康哥和红红姐的婚礼。如果不是你,我又怎么会飞了几百公里,去参加这场无关紧要的婚礼...
    左离殇阅读 214评论 0 0
  • 01. 周末一帮闺密聚会,女人们在一起聊的无非是孩子,聊着聊着安安就叹气了。 安安比我们大,孩子已经小学六年级了,...
    无为育儿阅读 171评论 3 3