01 异常


一、异常概述、体系

什么是异常? 

● 异常是程序在编译或者执行的过程中可能出现的问题, 注意:语法错误不算在异常体系中

● 比如:数组索引越界、 空指针异常、 日期格式化异常等… 

为什么要学习异常?

● 异常一旦出现了, 如果没有提前处理, 程序就会退出JVM虚拟机而终止.。

● 研究异常并且避免异常, 然后提前处理异常, 体现的是程序的安全, 健壮性 

异常体系图

Error:

● 系统级别问题、JVM退出等,代码无法控制

Exception:

● java.lang包下,称为异常类,它表示程序本身可以处理的问题

● RuntimeException及其子类:运行时异常,编译阶段不会报错。

● 除RuntimeException之外所有的异常:编译时异常,编译期必须处理,否则程序不能通过编译。(日期格式化异常)。

编译时异常和运行时异常

简单来说:

编译时异常就是在编译的时候出现的异常

运行时异常就是在运行的时候出现的异常

二、常见运行时异常

运行时异常

直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。

运行时异常示例

● 数组索引越界异常: ArrayIndexOutOfBoundsException 

● 空指针异常 : NullPointerException, 直接输出没有问题, 但是调用空指针的变量的功能就会报错。 

● 数学操作异常: ArithmeticException 

● 类型转换异常: ClassCastException 

● 数字转换异常: NumberFormatException 

运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误,自己的水平有问题!

【代码示例1】数组索引越界异常: ArrayIndexOutOfBoundsException。

/** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/
        int[] arr = {1, 2, 3};
        System.out.println(arr[2]);
        System.out.println(arr[3]); // 运行出错,程序终止

【代码示例2】空指针异常 : NullPointerException

/** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */
        String name = null;
        System.out.println(name); // null
        System.out.println(name.length()); // 运行出错,程序终止

【代码示例3】3.类型转换异常:ClassCastException。

/** 3.类型转换异常:ClassCastException。 */
        Object o = 23;
        String s = (String) o;  // 运行出错,程序终止

【代码示例4】数学操作异常:ArithmeticException。

 /** 5.数学操作异常:ArithmeticException。 */
        int c = 10 / 0;

【代码示例5】数字转换异常: NumberFormatException。

** 6.数字转换异常: NumberFormatException。 */
        //String number = "23";  //正确
        String number = "23aabbc";
        Integer it = Integer.valueOf(number); // 运行出错,程序终止
        System.out.println(it + 1);

三、常见编译时异常

编译时异常

不是RuntimeException或者其子类的异常,编译阶段就报错,必须处理,否则代码不通过。

编译时异常示例

编译时异常的作用什么:

是担心程序员的技术不行,在编译阶段就爆出一个错误,目的在于提醒不要出错!

编译时异常时可遇不可求遇到了就遇到了呗

【代码示例】

public class ExceptionDemo {
    public static void main(String[] args) throws ParseException {
        String date = "2015-01-12 10:23:21";
        // 创建一个简单日期格式化类:
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
        // 解析字符串时间成为日期对象
        Date d = sdf.parse(date);
        //
        System.out.println(d);
    }
}

四、异常的默认处理流程

① 默认会在出现异常的代码那里自动的创建一个异常对象: ArithmeticException。 

② 异常会从方法中出现的点这里抛出给调用者, 调用者最终抛出给JVM虚拟机。 

③ 虚拟机接收到异常对象后, 先在控制台直接输出异常栈信息数据。 

④ 直接从当前执行的异常点干掉当前程序。 

⑤ 后续代码没有机会执行了, 因为程序已经死亡。 

【示例代码】

public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。。。。。。。");
        chu(10, 0);
        System.out.println("程序结束。。。。。。。。。。");
    }

    public static void chu(int a , int b){
        System.out.println(a);
        System.out.println(b);
        int c = a / b;
        System.out.println(c);
    }
}

执行流程图:

五、编译时异常处理机制

编译时异常是编译阶段就出错的, 所以必须处理, 否则代码根本无法通过 

编译时异常的处理形式有三种:

● 出现异常直接抛出去给调用者, 调用者也继续抛出去。 

出现异常自己捕获处理,不麻烦别人

● 前两者结合, 出现异常直接抛出去给调用者, 调用者捕获处理。 

异常处理方式1 —— throws

● throws:用在方法上,可以将方法内部出现的异常抛出给本方法的调用者处理。

● 这种方式并不好, 发生异常的方法自己不处理异常, 如果异常最终抛出去给虚拟机将引起程序死亡。 

抛出异常格式:

方法 throws 异常1,异常2,异常3 ...{

}

规范格式:

方法 throws EXception{

}

● 代表可以抛出一切异常

【代码示例1】

public class ExceptionDemo01 {

    public static void main(String[] args) throws ParseException, FileNotFoundException {
        System.out.println("程序开始。。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。。");
    }

    public static void parseTime(String date) throws ParseException, FileNotFoundException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
        Date d = sdf.parse(date);
        System.out.println(d);

        InputStream is = new FileInputStream("E:/meinv.jpg");
    }

}

【代码示例2】

public class ExceptionDemo01 {

    public static void main(String[] args) throws Exception {
        System.out.println("程序开始。。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。。");
    }

    public static void parseTime(String date) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(date);
        System.out.println(d);

        InputStream is = new FileInputStream("E:/meinv.jpg");
    }

}

异常处理方式2 —— try ... catch

监视捕获异常,用在方法内部,可以将放法内部出现的异常直接捕获处理

这种方法还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。

格式:

try{
   // 监视可能出现异常的代码!
}catch(异常类型1 变量){
   // 处理异常
}catch(异常类型2 变量){
   // 处理异常
}...

建议格式:

try{
   // 可能出现异常的代码!
}catch (Exception e){
   e.printStackTrace(); // 直接打印异常栈信息
}

Exception可以捕获处理一切异常类型! 

【代码示例1】

点击查看代码
public class ExceptionDemo02 {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。");
    }

    public static void parseTime(String date) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);
        } catch (ParseException e) {
            // 解析出现问题
            System.out.println("出现了解析时间异常哦,走点心!!");
        }

        try {
            InputStream is = new FileInputStream("E:/meinv.jpg");
        } catch (FileNotFoundException e) {
            System.out.println("您的文件根本就没有啊,不要骗我哦!!");
        }
    }
}

【代码示例2】

public class ExceptionDemo02 {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。");
    }

    public static void parseTime(String date) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);

            InputStream is = new FileInputStream("E:/meinv.jpg");
        } catch (FileNotFoundException e) {
           e.printStackTrace(); // 打印异常栈信息
        } catch (ParseException e) {
           e.printStackTrace();
        }
    }
}

【代码示例3】

public class ExceptionDemo02 {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。");
    }

    public static void parseTime(String date) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);

            InputStream is = new FileInputStream("E:/meinv.jpg");
        } catch (FileNotFoundException|ParseException e) {
            e.printStackTrace(); // 打印异常栈信息
        }
    }
}

【代码示例4】

public class ExceptionDemo02 {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        parseTime("2011-11-11 11:11:11");
        System.out.println("程序结束。。。。");
    }

    public static void parseTime(String date) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");
            Date d = sdf.parse(date);
            System.out.println(d);

            InputStream is = new FileInputStream("E:/meinv.jpg");
        } catch (Exception e) {
            e.printStackTrace(); // 打印异常栈信息
        }
    }
}

异常处理方式3 —— 前两者结合

● 方法直接将异通过throws抛出去给调用者 

● 调用者收到异常后直接捕获处理。 

 【代码示例】

public class ExceptionDemo03 {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。");
        try {
            parseTime("2011-11-11 11:11:11");
            System.out.println("功能操作成功~~~");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("功能操作失败~~~");
        }
        System.out.println("程序结束。。。。");
    }

    public static void parseTime(String date) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy、MM-dd HH:mm:ss");
        Date d = sdf.parse(date);
        System.out.println(d);

        InputStream is = new FileInputStream("D:/meinv.jpg");
    }

}

运行结果:

程序开始。。。。
java.text.ParseException: Unparseable date: "2011-11-11 11:11:11"
	at java.text.DateFormat.parse(DateFormat.java:366)
	at com.itheima.d4_exception_runtimeException.ExceptionDemo.parseTime(ExceptionDemo.java:23)
	at com.itheima.d4_exception_runtimeException.ExceptionDemo.main(ExceptionDemo.java:12)
功能操作失败~~~
程序结束。。。。

异常处理的总结:

在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。

实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式似乎也都是可以的。

六、运行时异常的处理机制

运行时异常编译阶段不会出错, 是运行时才可能出错的, 所以编译阶段不处理也可以。 

按照规范建议还是处理: 建议在最外层调用处集中捕获处理即可。 

public class Test {
    public static void main(String[] args) {
        System.out.println("程序开始。。。。。。。。。。");
        try {
            chu(10, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("程序结束。。。。。。。。。。");
    }

    public static void chu(int a , int b) { // throws RuntimeException{
        System.out.println(a);
        System.out.println(b);
        int c = a / b;
        System.out.println(c);
    }
}

七、异常处理使代码更稳健的案例

【案例】异常处理使代码更稳健的案例

需求:

键盘录入一个合理的价格为止(必须是数值,值必须大于0).

分析:

定义一个死循环,让用户不断的输入价格。

【代码示例】

/**
    需求:需要输入一个合法的价格为止 要求价格大于 0
 */
public class Test2 {
    public static void main(String[] args) {
        Scanner sc  = new Scanner(System.in);
        while (true) {
            try {
                System.out.println("请您输入合法的价格:");
                String priceStr = sc.nextLine();
                // 转换成double类型的价格
                double price = Double.valueOf(priceStr);

                // 判断价格是否大于 0
                if(price > 0) {
                    System.out.println("定价:" + price);
                    break;
                }else {
                    System.out.println("价格必须是正数~~~");
                }
            } catch (Exception e) {
                System.out.println("用户输入的数据有毛病,请您输入合法的数值,建议为正数~~");
            }
        }
    }
}

八、自定义异常

自定义异常的必要?

● Java无法为这个世界上全部的问题提供异常类。 

● 如果企业想通过异常的方式来管理自己的某个业务问题, 就需要自定义异常类了 

 一定义异常的好处:

● 可以使用异常的机制管理业务问题, 如提醒程序员注意。 

● 同时一旦出现bug, 可以用异常的形式清晰的指出出错的地方。 

 自定义异常的分类

1、自定义编译时异常

● 定义一个异常类继承Exception. 

● 重写构造器。 

● 出现异常的地方用throw new 自定义对象抛出, 

作用: 编译时异常是编译阶段就报错, 提醒更加强烈, 一定需要处理! ! 

2、自定义运行时异常

● 定义一个异常类继承RuntimeException. 

● 重写构造器。 

● 在出现异常的地方用throw new 自定义对象抛出! 

作用: 提醒不强烈, 编译阶段不报错! ! 运行时才可能出现! ! 

【代码示例1】自定义编译时异常

/**
    自定义的编译时异常
      1、继承Exception
      2、重写构造器
 */
public class ItheimaAgeIlleagalException extends Exception{
    public ItheimaAgeIlleagalException() {
    }

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

【代码示例2】自定义的运行时异常

/**
    自定义的运行时异常
      1、继承RuntimeException
      2、重写构造器
 */
public class ItheimaAgeIlleagalRuntimeException extends RuntimeException{
    public ItheimaAgeIlleagalRuntimeException() {
    }

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

 【代码示例3】

/**
    目标:自定义异常(了解)

    引入:Java已经为开发中可能出现的异常都设计了一个类来代表.
        但是实际开发中,异常可能有无数种情况,Java无法为
        这个世界上所有的异常都定义一个代表类。
        假如一个企业如果想为自己认为的某种业务问题定义成一个异常
        就需要自己来自定义异常类.

    需求:认为年龄小于0岁,大于200岁就是一个异常。

    自定义异常:
        自定义编译时异常.
            a.定义一个异常类继承Exception.
            b.重写构造器。
            c.在出现异常的地方用throw new 自定义对象抛出!
            编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!

        自定义运行时异常.
            a.定义一个异常类继承RuntimeException.
            b.重写构造器。
            c.在出现异常的地方用throw new 自定义对象抛出!
            提醒不强烈,编译阶段不报错!!运行时才可能出现!!

 */
public class ExceptionDemo {
    public static void main(String[] args) {
//        try {
//            checkAge(-34);
//        } catch (ItheimaAgeIlleagalException e) {
//            e.printStackTrace();
//        }

        try {
            checkAge2(-23);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void checkAge2(int age)  {
        if(age < 0 || age > 200){
            // 抛出去一个异常对象给调用者
            // throw :在方法内部直接创建一个异常对象,并从此点抛出
            // throws : 用在方法申明上的,抛出方法内部的异常
            throw new ItheimaAgeIlleagalRuntimeException(age + " is illeagal!");
        }else {
            System.out.println("年龄合法:推荐商品给其购买~~");
        }
    }

    public static void checkAge(int age) throws ItheimaAgeIlleagalException {
        if(age < 0 || age > 200){
            // 抛出去一个异常对象给调用者
            // throw :在方法内部直接创建一个异常对象,并从此点抛出
            // throws : 用在方法申明上的,抛出方法内部的异常
            throw new ItheimaAgeIlleagalException(age + " is illeagal!");
        }else {
            System.out.println("年龄合法:推荐商品给其购买~~");
        }
    }
}