异常(Exception)

1、什么是异常?
以下两种情况不是异常:
语法错误:编译不通过
逻辑错误:编译通过,运行也有结果,就是结果不对

当语法正确,逻辑也正确时,在运行期间因为一些其他的因素,例如:用户输入的数据不合法,网络不通畅,硬盘容量不够等,
导致程序无法正常运行。这样的不正常问题称为异常。

2、在Java中如何表示不同的异常问题?
Java用对象表示,Java把不同的问题(异常)用不同的Java异常类型来表示,当发生异常时,就会创建对应类型的“对象”。

3、在Java中如何处理异常问题?
(1)遇到异常,就停止运行。程序中没有编写处理的相关代码。
(2)可以处理异常,让程序继续运行,就需要用到try...catch结构
异常对象创建完之后,需要“抛”出,“抛出”后由“try...catch结构”中的catch进行“捕获/抓”,然后处理。

“抛”<-->"捕获“成功 程序继续
“抛”<-->“捕获”失败 程序挂了

4、异常的体系结构
(1)在Java中所有异常的根类型是java.lang.Throwable类。当然Throwable的父类仍然是Object。
看API:Throwable 类是 Java 语言中所有错误或异常的超类。
A:只有当对象是此类(或其子类之一)的实例(对象)时,才能通过 Java 虚拟机或者 Java throw 语句抛出。  “抛”
B:只有此类或其子类之一才可以是 catch 子句中的参数类型。       “捕获”

(2)Java中所有异常类型可以分为两大类,即Throwable分为两大派别:
java.lang.Error:用于指示合理的应用程序不应该试图捕获的严重问题。
        例如:VirtualMachineError 虚拟机错误,包括 OutOfMemoryError(堆内存溢出错误), StackOverflowError(栈内存溢出的错误)等。
java.lang.Exception:它指出了合理的应用程序想要捕获的条件。
        例如:ArrayIndexOutOfBoundsException,NullPointerException,ClassCastException,ArithmeticException等等
(3)Exception又可以分为两大类:
运行时异常(非受检异常):RuntimeException系列,即RuntimeException类型或它的子类类型
       无论程序运行时是否会真的发生xx运行时异常,在“编译”期间,编译器是不会给出提示的,即编译器不会强制程序必须处理xx异常。
       直到程序运行时,异常真的发生了,才知道,可能发生xx异常。
       例如:ArrayIndexOutOfBoundsException,NullPointerException,ClassCastException,ArithmeticException等等都是运行时异常。
编译时异常(受检异常):除了运行时异常以外的异常类型
       无论程序运行时是否会真的发生xx编译时异常,在“编译”期间,编译器是会给出提示,告诉程序员,某个代码可能发生xx异常,请做出处理,否则编译不通过。
        例如: java.io.FileNotFoundException等

问题:遇到一个新的异常,能否判断它是编译时异常还是运行时异常。
   看编译器是否提示,不提示就是运行时异常,提示就是编译时异常。

 

public class TestExceptionType {
    public static void main(String[] args) {
        //运行时异常
        int[] arr = {1,2,3,4,5};
        System.out.println(arr[0]);//运行时正常的,编译器不会给我们提示,下标是合理范围
       // System.out.println(arr[6]);//运行时会发生ArrayIndexOutOfBoundsException,但是编译器并不会给我们提示下标不在合理范围

        System.out.println("-----------------------");
        //编译时异常
        try {
            //File:单词代表文件,InputStream单词代表输入的数据流
            //想要从"d:/1.txt"读取数据
            //以下这句代码,编译器检测到  new FileInputStream("d:/1.txt") “可能发生” java.io.FileNotFoundException文件找不到异常
            FileInputStream fis = new FileInputStream("d:/1.txt"); //必须对这句代码做出处理,以免真的发生异常,否则编译不通过
        } catch (FileNotFoundException e) {
            System.out.println("发生异常了");
        }

        System.out.println("-------------------------------");
        //判断某个异常是编译时异常还是运行时异常
        Scanner input = new Scanner(System.in);
        System.out.print("请输入一个整数:");
        int num = input.nextInt(); //当输入“张”,发生了java.util.InputMismatchException
        System.out.println("num = " + num);

        System.out.println("-------------------------------");
        //判断某个异常是编译时异常还是运行时异常
//        Thread.sleep(1000);//InterruptedException 编译时异常,程序还没运行,编译器就提示我,需要处理
    }
}

5、异常的抛出机制
描述一下过程:
    当某句代码发生异常后,会在这句代码处先“停”下来,然后根据当前的“问题”的情况,创建对应类型的“异常对象”,
    接着把这个异常对象就“抛”出来。
    如果刚刚发生异常的代码的外围没有try...catch可以“捕获”它,那么该句代码所在方法就会结束,
    并且把异常抛给方法的“调用者”,依次类推,如果一直到达main方法,都没有处理,那么就会导致程序挂了。

    如果某句代码发生异常后,当“异常对象”抛出后,外围有try...catch可以“捕获”它,那么try...catch下面的代码仍然会继续,
    程序不会挂掉。

public class TestExceptionStackTrace {
    public static void main(String[] args) {
        a(); //已经是最上级了,到main,仍然没有处理,就会导致程序挂了。
        System.out.println("main");//不执行
    }

    public static void a(){
        b();
        System.out.println("a");//不执行
    }
    public static void b(){
        c();
        System.out.println("b");//不执行
    }
    public static void c(){
        System.out.println(1/0);//发生异常
        System.out.println("c"); //不执行
    }
}

6、异常处理的方式:try...catch
(1)语法结构
它也是属于复合语句中的一种,其他复合语句有:if...else,switch..case,循环等
try{
    可能发生异常的代码
}catch(异常的类型1 参数名){
    处理异常的代码
}catch(异常的类型2 参数名){
    处理异常的代码
}catch(异常的类型3 参数名){
    处理异常的代码
}...

(2)一段可能发生异常的代码需要加try...catch结构,可以用快捷键:Ctrl + Alt + T
        选中代码,然后按Ctrl + Alt + T快捷键

 (3)执行特点
 A:try中如果没有发生异常,那么catch全部都不执行
 B:try中如果发生异常,发生异常的代码处停下来后,try中剩下的代码就不走了,转而去找匹配的catch执行。
   catch的匹配过程是从上往下依次比较,找到就进入这个catch执行,后面的catch就不看了。
 C:如果try中如果发生异常,并且所有catch都没有匹配,那么就会导致当前方法结束,把异常抛给“调用者”,
   如果已经是main的话,那么程序就挂了。

(4)catch中()的类型只能是异常类型,Throwable或它的子类类型。
    catch中()的类型如果是并列关系,顺序无所谓。
    catch中()的类型如果是包含关系(有父子类关系),必须子上父下
    如果多个catch的{}中处理代码一致,可以简写成如下形式(JDK1.7后支持)
    catch(异常类型1 | 异常类型2 | 异常类型3 参数名){ //要求这些异常是没有父子类关系的。
    }

 (5)在catch的{}中经常看到输出异常的语句
 A:标准异常输出:   e.printStackTrace(); 这里的e是catch()中的参数名,它代表“捕获的”异常对象。
    printStackTrace()方法是将此 throwable 及其追踪输出至标准错误流。
            里面包含异常的对象类型、异常的描述信息message、异常对象经过的方法栈的路线(痕迹)等。
B:标准的错误流:
    System.err.println(要打印的异常信息);
C:普通信息打印
    System.out.println("发生问题了");

说明:System.err和 e.printStackTrace()都是标准的错误流, 他和System.out打印信息的顺序是不能保证的

public class TestTryCatch {
    public static void main(String[] args) {
        try {
            System.out.println("args[0] = " +args[0]);
            System.out.println("args[1] = " +args[1]);
            //从命令行接收2个整数,并且求它们的商
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            System.out.println(a/b);
        } catch(NumberFormatException e){//当命令行参数输入的不是整数时,会报NumberFormatException数字格式化异常
            e.printStackTrace();
//            System.err.println("数字格式化异常");
//            System.err.println(e);//java.lang.NumberFormatException: For input string: "张三"
//            System.out.println("发生问题了");
        } catch(ArithmeticException e){//当第二个命令行参数输入0时,会发生ArithmeticException
            e.printStackTrace();
        } catch (ArrayIndexOutOfBoundsException e) { //没有传入命令行参数,或者命令行参数的个数小于2
            //args[0]或args[1]发生下标越界异常
            e.printStackTrace();
        } catch(Exception e){ //父在下,子在上
            e.printStackTrace();
        }
/*        try {
            System.out.println("args[0] = " +args[0]);
            System.out.println("args[1] = " +args[1]);
            //从命令行接收2个整数,并且求它们的商
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            System.out.println(a/b);
        } catch(NumberFormatException | ArithmeticException | ArrayIndexOutOfBoundsException  e){
            e.printStackTrace();
        }*/

        System.out.println("下面的代码可以继续执行...");
    }
}

7、异常处理的方式:try...catch...finally
(1)语法结构
它也是属于复合语句中的一种,其他复合语句有:if...else,switch..case,循环等
try{
    可能发生异常的代码
}catch(异常的类型1 参数名){
    处理异常的代码
}catch(异常的类型2 参数名){
    处理异常的代码
}catch(异常的类型3 参数名){
    处理异常的代码
}...
finally{
    当某些代码,无论try中是否发生异常,也不管catch是否可以捕获异常,甚至就算try或catch中有return语句,
    也要求它必须执行,这样的代码放到finally中。
    例如:资源关闭和释放等相关代码(IO流,网络连接等)

    除了一种情况不执行,在try..catch中遇到了System.exit(0);
}

public class TestTryCatchFinally {
    public static void main(String[] args) {
        try {
            System.out.println("args[0] = " +args[0]);
            System.out.println("args[1] = " +args[1]);
            //从命令行接收2个整数,并且求它们的商
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            System.out.println(a/b);
//            return ; //结束main方法
            System.exit(0); //退出JVM
        } catch(NumberFormatException e){//当命令行参数输入的不是整数时,会报NumberFormatException数字格式化异常
            e.printStackTrace();
        } catch(ArithmeticException e){//当第二个命令行参数输入0时,会发生ArithmeticException
            e.printStackTrace();
        } finally{
            System.out.println("就算天塌下来,也要执行,除非JVM退出,才不执行");
        }
    }
}

8、异常处理的关键字:throws
(1)什么情况下需要用到throws关键字?
当某个方法体中,可能发生xx异常,但是在这个方法中,又不想处理(或处理不了)时,可以把异常抛给调用者,
并且在方法签名中用throws声明可能发生的异常类型。这样的话,调用者在调用方法时,就知道需要处理什么异常了。

(2)throws后面可以是编译时异常,也可以是运行时异常。
如果throws后面是编译时异常,那么调用者调用这个方法时,编译器会要求调用者必须做出处理;
如果throws后面是运行时异常,那么调用者调用这个方法时,编译器不会要求调用者必须做出处理,如果调用者想要处理时,就有据可依。

(3)throws语法
【修饰符】 返回值类型  方法名(【形参列表】)throws 异常列表 {
}
说明:异常列表可以是多个异常,没有顺序要求,异常类型之间使用逗号分隔

(4)throws只是表名当前方法可能发生xx异常,当前方法没有处理,让调用者处理,如果调用者也不处理,一直到main也throws,就会导致程序挂了。
 结论:Java中真正处理异常的是try..catch,throws只是让调用者处理而已或者延迟处理,不是表示不用处理。

 (5)父类的方法被重写时,关于throws有要求
 A:如果父类的被重写方法,没有通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,也不能通过throws声明抛出编译时异常
 B:如果父类的被重写方法,通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,只能通过throws声明抛出同样的编译时异常或其子类异常。
C:父类被重写方法是否抛出运行时异常,和子类重写时是否抛出运行时异常无关。


方法重写的要求总结:
  方法名:必须相同
  形参列表:必须相同
  返回值类型:
     基本数据类型和void:必须相同
     引用数据类型:<=
  权限修饰符:>=,不能是private,跨包不能是缺省的
  throws异常类型:如上
  其他修饰符:不能是static, final

public class TestThrows {
    public static void main(String[] args) {
//        copyFile1("d:/1.txt","d:/atguigu/1.txt");


        copyFile2("d:/1.txt","d:/atguigu/1.txt");//编译器没有检测到copyFile2方法可能发生xx异常
        //如果此时加try...catch,它知道要处理什么异常

        System.out.println("--------------------");
        Father f = new Son();
        try {
            f.test();
        } catch (IOException e) { //此时catch异常类型是看父类Father中的test方法throws的异常类型,运行时子类Son中不能抛出比它大的异常,否则就有问题了
            e.printStackTrace();
        }
    }

    /**
     * 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
     * srcFile参数就是 "d:/1.txt"
     * destFile参数就是 "d:/atguigu/1.txt"
     * @param srcFile String 源文件的路径+文件名
     * @param destFile String 目标文件的路径+文件名
     */
    public static void copyFile1(String srcFile, String destFile) throws FileNotFoundException, IOException,RuntimeException {
        //...里面代码省略
        //这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
    }

    /**
     * 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
     * srcFile参数就是 "d:/1.txt"
     * destFile参数就是 "d:/atguigu/1.txt"
     * @param srcFile String 源文件的路径+文件名
     * @param destFile String 目标文件的路径+文件名
     */
    public static void copyFile2(String srcFile, String destFile) {
        //...里面代码省略
        //这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
    }
}

class Father{
    public void method(){
        //...省略
    }

    public void test()throws IOException{

    }
}
class Son extends Father{
/*    @Override
    public void method() throws Exception{//错误,因为method在父类中没有抛出编译时异常
        //...省略
    }*/

    @Override
    public void method() throws RuntimeException{//运行时异常没有限制
        //...省略
    }

/*    public void test()throws IOException{//throws 一样可以

    }*/

/*    public void test()throws FileNotFoundException{//throws 小的异常,  FileNotFoundException是IOException的子类

    }*/

/*    public void test()throws Exception{//throws 大的异常,错误

    }*/
}

8、异常处理的关键字:throws
(1)什么情况下需要用到throws关键字?
当某个方法体中,可能发生xx异常,但是在这个方法中,又不想处理(或处理不了)时,可以把异常抛给调用者,
并且在方法签名中用throws声明可能发生的异常类型。这样的话,调用者在调用方法时,就知道需要处理什么异常了。

(2)throws后面可以是编译时异常,也可以是运行时异常。
如果throws后面是编译时异常,那么调用者调用这个方法时,编译器会要求调用者必须做出处理;
如果throws后面是运行时异常,那么调用者调用这个方法时,编译器不会要求调用者必须做出处理,如果调用者想要处理时,就有据可依。

(3)throws语法
【修饰符】 返回值类型  方法名(【形参列表】)throws 异常列表 {
}
说明:异常列表可以是多个异常,没有顺序要求,异常类型之间使用逗号分隔

(4)throws只是表名当前方法可能发生xx异常,当前方法没有处理,让调用者处理,如果调用者也不处理,一直到main也throws,就会导致程序挂了。
 结论:Java中真正处理异常的是try..catch,throws只是让调用者处理而已或者延迟处理,不是表示不用处理。

 (5)父类的方法被重写时,关于throws有要求
 A:如果父类的被重写方法,没有通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,也不能通过throws声明抛出编译时异常
 B:如果父类的被重写方法,通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,只能通过throws声明抛出同样的编译时异常或其子类异常。
C:父类被重写方法是否抛出运行时异常,和子类重写时是否抛出运行时异常无关。


方法重写的要求总结:
  方法名:必须相同
  形参列表:必须相同
  返回值类型:
     基本数据类型和void:必须相同
     引用数据类型:<=
  权限修饰符:>=,不能是private,跨包不能是缺省的
  throws异常类型:如上
  其他修饰符:不能是static, final

public class TestThrows {
    public static void main(String[] args) {
//        copyFile1("d:/1.txt","d:/atguigu/1.txt");


        copyFile2("d:/1.txt","d:/atguigu/1.txt");//编译器没有检测到copyFile2方法可能发生xx异常
        //如果此时加try...catch,它知道要处理什么异常

        System.out.println("--------------------");
        Father f = new Son();
        try {
            f.test();
        } catch (IOException e) { //此时catch异常类型是看父类Father中的test方法throws的异常类型,运行时子类Son中不能抛出比它大的异常,否则就有问题了
            e.printStackTrace();
        }
    }

    /**
     * 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
     * srcFile参数就是 "d:/1.txt"
     * destFile参数就是 "d:/atguigu/1.txt"
     * @param srcFile String 源文件的路径+文件名
     * @param destFile String 目标文件的路径+文件名
     */
    public static void copyFile1(String srcFile, String destFile) throws FileNotFoundException, IOException,RuntimeException {
        //...里面代码省略
        //这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
    }

    /**
     * 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
     * srcFile参数就是 "d:/1.txt"
     * destFile参数就是 "d:/atguigu/1.txt"
     * @param srcFile String 源文件的路径+文件名
     * @param destFile String 目标文件的路径+文件名
     */
    public static void copyFile2(String srcFile, String destFile) {
        //...里面代码省略
        //这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
    }
}

class Father{
    public void method(){
        //...省略
    }

    public void test()throws IOException{

    }
}
class Son extends Father{
/*    @Override
    public void method() throws Exception{//错误,因为method在父类中没有抛出编译时异常
        //...省略
    }*/

    @Override
    public void method() throws RuntimeException{//运行时异常没有限制
        //...省略
    }

/*    public void test()throws IOException{//throws 一样可以

    }*/

/*    public void test()throws FileNotFoundException{//throws 小的异常,  FileNotFoundException是IOException的子类

    }*/

/*    public void test()throws Exception{//throws 大的异常,错误

    }*/
}

9、自定义异常
JRE核心类库中已经提供了很多异常的类型,但是有时候我们仍然想要声明自己的异常类型,使得异常的信息更“见名知意”,
可读性更好。

IllegalArgumentException:非法参数异常
UnsupportedOperationException:不支持该操作的异常

如何自定义异常?
(1)必须继承Throwable或它的子类。
(2)自定义异常类型中建议保留无参构造和(String message)的构造器,其他构造器自选。
(3)自定义异常类型建议实现java.io.Serializable接口(在IO中用的)
   因为后期的异常对象可能被输出到日志文件,所以需要支持序列化操作,这就要求实现java.io.Serializable接口。
   关于序列化的内容在IO部分讲解。

自定义异常如何抛出和处理?
(1)自定义异常对象,必须由程序员手动new,并用throw语句抛出。
(2)一旦throw之后,就和系统的其他异常对象一样了,
A:可以throws
B:可以try...catch
C:不处理会导致系统崩溃

public class TestDefineException {
    public static void main(String[] args)  {
        Account a = new Account();
        try {
            a.withdraw(100);
            System.out.println("取款成功");
        } catch (BalanceNotEnoughException e) {
            System.out.println("取款失败");
            e.printStackTrace();
        }
    }
}
//余额不足异常
class BalanceNotEnoughException extends Exception implements Serializable {
    public BalanceNotEnoughException() {
    }

    public BalanceNotEnoughException(String message) {
        super(message);//调用父类的构造器,message在父类中声明的成员变量
    }
}
//钱数是负数异常
class MoneyIsNegativeException extends RuntimeException implements Serializable{
    public MoneyIsNegativeException() {
    }

    public MoneyIsNegativeException(String message) {
        super(message);
    }
}

class Account{
    private double balance;//这里只关注余额了,没有关注其他的属性

    public double getBalance() {
        return balance;
    }

    public void save(double money){
        if(money<0){
//            throw new IllegalArgumentException(money +"参数有问题,存款金额不能为负数");
            throw new MoneyIsNegativeException(money +"参数有问题,存款金额不能为负数");
            /*
            MoneyIsNegativeException是继承RuntimeException,说明它是运行时异常,在save()方法中,编译器不会要求我们必须处理
             */
        }
        balance += money;
    }
    public void withdraw(double money) throws BalanceNotEnoughException {
        if(money<0){
            throw new IllegalArgumentException(money +"参数有问题,取款金额不能为负数");
        }
        if(money>balance){
//            throw new UnsupportedOperationException("取款金额" + money +"超过余额,余额不足");
            throw new BalanceNotEnoughException("取款金额" + money +"超过余额,余额不足");
            /*
            BalanceNotEnoughException是继承Exception,它是编译时异常,在withdraw方法中,编译器就会要求我们必须处理
             */
        }
        balance -= money;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值