12.异常

1.1Java异常体系图

  • Throwable  (实现类描述Java的错误和异常)
  • Error (错误)Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
  • Exceprion (异常)如果程序出现了异常,那么一般就需要通过代码去处理了。
  • RuntimeException 在编译器是不检查的,出现问题后,需要我们回来修改代码。
  • 非RuntimeException 编译器就必须处理的,否则程序不能通过编译,就更不能正常运行了。

疑问:程序出现了不正常的情况,怎么能区分到底是错误还是异常呢?

  • 如果不正常情况的消息是以Error结尾的,那么则代表了这是一个错误。
  • 如果不正常情况的消息是以Exception结尾的,那么则代表是一个异常。

1.2Throwable类

  • toString() 输出该异常的类名。(返回的是用于描述该异常情况的类的完整类名。包名+类名=完整类名)
  • getMessage() 输出异常的信息,需要通过构造方法传入异常信息(例如病态信息)。
  • printStackTrace() 打印栈信息。
public class Demo30 {     
    public static void main(String[] args) {
        Throwable throwable = new Throwable("想吐。。。");
        System.out.println(throwable.toString());//输出该异常的类名  java.lang.Throwable: 想吐。。。
        System.out.println(throwable.getMessage());//输出异常的信息 想吐。。。
        throwable.printStackTrace();//打印栈信息  java.lang.Throwable: 想吐。。。at com.boxiaoyuan.test.Demo30.main(Demo30.java:6)    
    }
}

1.3程序中的异常处理

1.3.1捕获异常

  • try{//可能发生异常的代码 }catch(异常类 变量名){//处理}。
  • 如果没有进行try catch处理,出现异常程序就停止。进行处理后,程序会继续执行。

捕获处理要注意的细节:

  • 如果一个try块的代码出现了异常,经过处理之后,那么try-catch块外面的代码可以正常执行。
  • 如果一个try块出现了异常的代码,那么在try块中出现异常代码后面的所有代码都无法正常执行。
  • 一个try块后面可以跟多个catch块,也就是说一个块可以捕获多种异常的类型。
  • 一个try块后面可以跟多个catch块,但是捕获的异常类型必须按照从小到大进行捕获。
public class Demo31 {     
    public static void main(String[] args) {
        div(2, 0);
        System.out.println("over");
    }
    public static void div(int x, int y) {
        try {
            System.out.println(x / y); //可能出现一场的语句,放入try中
        }catch(ArithmeticException e) {  //进行一场匹配
            System.out.println(e.toString());
            System.out.println(e.getMessage());
            e.printStackTrace();
            System.out.println("除数不能为0");
        }
        System.out.println("除法运算");    
    }
}

多个异常

public class Demo32 {     
    public static void main(String[] args) {
        print(2, 0, null);
        System.out.println("over");
    }
    public static void print(int x, int y, int[] arr) {
        try {
            System.out.println(arr[1]);
            System.out.println(x/y);
        }catch(ArithmeticException e) {
            System.out.println(e.getMessage());
            System.out.println(e.toString());
            e.printStackTrace();
            System.out.println("算术异常");
        }catch(ArrayIndexOutOfBoundsException e) {
            System.out.println(e.toString());
            System.out.println(e.getMessage());
            e.printStackTrace();
            System.out.println("数组角标越界");
        }catch(NullPointerException e) {
            System.out.println(e.toString());
            System.out.println(e.getMessage());
            e.printStackTrace();
            System.out.println("空指针异常");
        }
        System.out.println("函数执行完毕");
        
    }
}

多个catch语句之间的执行顺序:

  • 是进行顺序执行,从上到下。
  • 如果多个catch 内的异常有子父类关系。
    • 子类异常在上,父类在最下。编译通过运行没有问题。                 
    • 父类异常在上,子类在下,编译不通过。(因为父类可以将子类的异常处理,子类的catch处理不到)。
    • 多个异常要按照子类和父类顺序进行catch。

1.3.2抛出异常

抛出处理要注意的细节:

  1. 如果一个方法的内部抛出了一个编译时异常对象,那么必须要在方法声明抛出。
  2. 如果调用了一个声明抛出编译时异常类型的方法,那么调用者必须要进行处理,否则编译报错。
  3. 一个方法如果遇到了throw关键字,那么该方法就停止执行。
  4. 在一种情况下只能抛出一种异常对象。

throw与throws的区别:

  1. throw关键字是用于在一个方法的内部抛出异常对象的,throws是用于在方法上声明抛出异常类型的。
  2. throw关键字后面跟的是一个异常的对象,throws后面跟的是异常的类型。
  3. throw关键字一次只能抛出一个异常对象,throws一次可以声明抛出多种异常类型。

什么时候使用抛出处理?什么时候使用捕获处理?

  • 如果需要通知调用者出了异常,那么则需要使用抛出处理。如果与用户直接打交道的代码就使用捕获处理,千万不能抛出,一旦抛出就抛给用户。

定义一个功能,进行除法运算。例如:div(int x,int y),如果除数为0,进行处理。功能内部不想处理,或者处理不了。就抛出使用throw new Exception("除数不能为0"); 进行抛出。抛出后需要在函数上进行声明,告知调用函数者,我有异常,你需要处理。如果函数上不进行throws 声明,编译会报错。例如:未报告的异常 java.lang.Exception;必须对其进行捕捉或声明以便抛出throw  new Exception("除数不能为0")。

public static void div(int x, int y) throws Exception {//声明异常,告知方法调用者
        if(y==0) {
            throw new Exception("除数不能为0");//throw关键字后面接收的是具体的异常对象
        }
        System.out.println(x/y);
}

如何处理声明了异常的函数:

  • try{}catch(){}
public static void main(String[] args) {
        try {
            div(3, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
}
  • 继续抛出
public static void main(String[] args) throws Exception {
        div(3, 0);
}

总结

  1. try语句不能单独存在,可以和catch、finally组成 try...catch...finally、try...catch、try...finally三种结构。
  2. catch语句可以有一个或多个,finally语句最多一个,try、catch、finally这三个关键字均不能单独使用。
  3. try、catch、finally三个代码块中变量的作用域分别独立而不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
  4. 多个catch块的时候,Java虚拟机会匹配其中一个异常类或其子类,就执行这个catch块,而不会再执行别的catch块。(子类在上,父类在下)。
  5. throw语句后不允许有紧跟其他语句,因为这些没有机会执行。
  6. 如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出。

1.4运行时异常和非运行时异常

1.4.1RuntimeException

运行时异常(RunTimeException以及RunTimeException子类):如果一个方法内部抛出了一个运行时异常对象,那么方法声明可以声明抛出也可以不声明抛出,如果调用了一个声明抛出运行时异常类型的方法,那么调用者可以处理也可以不处理。

RunntimeException的子类: 

  • ClassCastException:多态中,可以使用Instanceof 判断,进行规避
  • ArithmeticException:进行if判断,如果除数为0,进行return
  • NullPointerException:进行if判断,是否为null
  • ArrayIndexOutOfBoundsException:使用数组length属性,避免越界

1.4.2非运行时异常(受检异常)

如果出现了非运行时异常必须进行throw或者try{}catch(){}处理,否则编译器报错。

常见的非运行异常有io异常和sql异常。

  • IOException
  • FileNotFoundExcetion
  • SQLException

1.4.3函数的重写和异常

运行时异常

public class Demo34 {     
    public static void main(String[] args) {
        Father father = new Son();
        father.test();
    }
}
class Father{
    void test() throws ClassCastException{
        System.out.println("父类");
        throw new ClassCastException();
    }
}
class Son extends Father{
    void test() {
        System.out.println("子类");
    }
}

函数发生了重写,因为是运行时异常,在父类的test方法中,可以声明throws 也可以不声明throws。

非运行时异常

public class Demo34 {     
    public static void main(String[] args) throws ClassNotFoundException {
        Father father = new Son();
        father.test();
    }
}
class Father{
    void test() throws ClassNotFoundException{
        System.out.println("父类");
        throw new ClassNotFoundException();
    }
}
class Son extends Father{
    void test() {
        System.out.println("子类");
    }
}

声明非运行时异常的方法,在调用时需要处理,所以在main方法调用时throws,多态时,如果子类方法没有抛出非运行时异常,父类方法抛出非运行时异常,需要处理。

总结

  • 1:子类覆盖父类方法时,父类方法抛出异常,子类的覆盖方法可以不抛出异常,或者抛出父类方法的异常或该父类方法异常的子类。
  • 2:父类方法抛出了多个异常,子类覆盖方法时,只能抛出父类异常的子集。
  • 3:父类没有抛出异常子类不可抛出异常。
    • 1:子类发生非运行时异常,需要进行try{}catch的(){}处理,不能抛出。
  • 4:子类不能比父类抛出更多的异常。

1.5finally

finally块是程序在正常情况下或异常情况下都会运行的,比较适合用于既要处理异常又有资源释放的代码。在处理异常的时候该语句块只能有一个。

finally是否用于都执行?

  • 有一种情况,如果JVM退出了System.exit(0),finally就不执行。
  • return都不能停止finally的执行过程。
public class Demo35 {     
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            System.out.println("1创建io流可能会出现异常");
            fileInputStream = new FileInputStream("c:\\aa.txt");//加载硬盘上的文本文件到内存
        } catch (FileNotFoundException e) {
            System.out.println("2.没有找到aa.txt文件,catch执行了");
            e.printStackTrace();
            //return;
            //System.exit(0);
        }finally {
            System.out.println("3.finally执行");
            if(fileInputStream!=null) {//如果流对象为null,说明流对象不存在,没必要关闭资源
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("4.close异常");
                }
            }
            System.out.println("5.finally over");
        }
        
    }
}

1.6自定义异常

如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。

按照国际惯例,自定义的异常应该总是包含如下的构造函数:

  • 一个无参构造函数
  • 一个带有String参数的构造函数,并传递给父类的构造函数。
  • 一个带有String参数和Throwable参数,并都传递给父类构造函数
  • 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

下面是IOException类的完整源代码,可以借鉴。

public class IOException extends Exception
{
    static final long serialVersionUID = 7818375828146090155L;
 
    public IOException()
    {
        super();
    }
 
    public IOException(String message)
    {
        super(message);
    }
 
    public IOException(String message, Throwable cause)
    {
        super(message, cause);
    }
 
    public IOException(Throwable cause)
    {
        super(cause);
    }
}

 

posted @ 2019-10-08 07:43  博小园  阅读(86)  评论(0编辑  收藏  举报
回到顶部