推荐阅读文章

  • JavaSE系列1️⃣《JavaSE系列教程》
  • MySQL系列2️⃣《MySQL系列教程》
  • JavaWeb系列3️⃣《JavaWeb系列教程》
  • SSM框架系列4️⃣《SSM框架系列教程》

本博客知识点收录于《JavaSE系列教程》—>✈️11【泛型、Map、异常】✈️

文章目录

  • 异常
    • 1.1 异常概述
      • 1.1.1 什么是异常
      • 1.1.2 异常体系
      • 1.1.3 异常分类
    • 1.2 异常的处理
      • 1.2.1 异常的捕获
      • 1.2.2 异常的常用方法
      • 1.2.3 异常的抛出
      • 1.2.4 声明异常
        • 1)运行时异常
        • 2)编译时异常
    • 1.3 自定义异常
    • 1.4 方法的重写与异常

异常

1.1 异常概述

1.1.1 什么是异常

程序运行过程中出现的问题在Java中被称为异常,异常本身也是一个Java类,封装着异常信息;我们可以通过异常信息来快速定位问题所在;我们也可以针对性的定制异常,如用户找不到异常、密码错误异常、页面找不到异常、支付失败异常、文件找不到异常等等…
当程序出现异常时,我们可以提取异常信息,然后进行封装优化等操作,提示用户;

注意:语法错误并不是异常,语法错了编译都不能通过(但Java有提供编译时异常),不会生成字节码文件,根本不能运行;

默认情况下,出现异常时JVM默认的处理方式是中断程序执行,因此我们需要控制异常,当出现异常后进行相应修改,提供其他方案等操作,不要让程序中断执行;
我们之前有见到过很多的异常:

  • 空指针异常:java.lang.NullPointerException
String str=null;str.toString();
  • 数字下标越界异常:java.lang.ArrayIndexOutOfBoundsException
int[] arr = {1, 3, 4};System.out.println(arr[3]);
  • 类型转换异常:java.lang.ClassCastException
class A {}class B extends A {}class C extends A {}public static void main(String[] args) {    A a = new B();    C c = (C) a;}
  • 算数异常:java.lang.ArithmeticException
int i=1/0;
  • 日期格式化异常:java.text.ParseException
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date parse = sdf.parse("2000a10-24");

1.1.2 异常体系

Java程序运行过程中所发生的异常事件可分为两类:

  • Error:表示严重错误,一般是JVM系统内部错误、资源耗尽等严重情况,无法通过代码来处理;
  • Exception:表示异常,一般是由于编程不当导致的问题,可以通过Java代码来处理,使得程序依旧正常运行;

Tips:我们平常说的异常指的就是Exception;因为Exception可以通过代码来控制,而Error一般是系统内部问题,代码处理不了;

1.1.3 异常分类

异常的分类是根据在编译器检查异常还是在运行时检查异常;

  • 编译时期异常:在编译时期就会检查该异常,如果没有处理异常,则编译失败;
  • 运行时期异常:在运行时才出发异常,编译时不检测异常;

Tips:在Java中如果一个类直接继承与Exception,那么这个异常将是编译时异常;如果继承与RuntimeException,那么这个类是运行时异常。即使RuntimeException也继承与Exception;

  • 编译时异常举例:
public class Demo {    public static void main(String[] args) {        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");        Date date = sdf.parse("2000-10-24");    }}

  • 运行时异常:
public class Demo {    public static void main(String[] args) {        int i = 1 / 0;    }}

1.2 异常的处理

Java程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统(JVM),这个过程称为抛出(throw)异常。
如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
流程如下:

1.2.1 异常的捕获

异常的捕获和处理需要采用 try 和 catch 来处理,具体格式如下:

  • 1)try...catch(){}
try {// 可能会出现异常的代码} catch (Exception1 e) {// 处理异常1} catch (Exception2 e) {// 处理异常2} catch (ExceptionN e) {// 处理异常N}

Tips:后处理的异常必须是前面处理异常的父类异常;

  • 2)try...catch(){}...finally{}
try {// 可能会出现异常的代码} catch (Exception1 e) {// 处理异常1} catch (Exception2 e) {// 处理异常2} catch (ExceptionN e) {// 处理异常N} finally {// 不管是否出现异常都会执行的代码}
  • 3)try...finally{}
try {// 可能会出现异常的代码}  finally {// 不管是否出现异常都会执行的代码}

示例代码:

package com.dfbz.demo01;public class Demo01 {    public static void main(String[] args) {        method();        System.out.println("程序终止我就不能执行了~");    }    public static void method() {        try {            String str = null;            System.out.println(str.toString());        } catch (Exception e) {            System.out.println("执行代码出现异常了!");        } finally {            System.out.println("释放一些资源...");        }    }}

运行结果:

tips:try…catch语句是可以单独使用的;即:不要finally代码块;

需要注意的是:如果finally有return语句,则永远返回finally中的结果。我们在开发过程中应该避免该情况;

  • 示例代码:
package com.dfbz.demo02;public class Demo02 {    public static void main(String[] args) {        int result = method();        System.out.println("method方法的返回值: " + result);    }    public static int method() {        try {            int i = 10;            return 1;        } catch (Exception e) {            e.printStackTrace();            return 2;        } finally {            return 3;           // 不管是否出现异常都是返回3        }    }}

1.2.2 异常的常用方法

在Throwable类中具备如下几个常用异常信息提示方法:

  • public void printStackTrace():获取异常的追踪信息;
    包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
  • public String getMessage():异常的错误信息;

异常触发被抓捕时,异常的错误信息都被封装到了catch代码块中的Exception类中了,我可以通过该对象获取异常错误信息;
示例代码:

package com.dfbz.demo01;public class Demo02 {    public static void main(String[] args) {        method();        System.out.println("我继续执行~");    }    public static void method() {        try {            int i=1/0;        } catch (Exception e) {            System.out.println("异常的错误信息: " + e.getMessage());            // 打印异常的追踪信息            e.printStackTrace();        }    }}

运行结果如下:

异常的追踪信息可以帮助我们追踪异常的调用链路,一步一步找出异常所涉及到的方法,在实际开发非常常用;

1.2.3 异常的抛出

我们已经学习过出现异常该怎么抓捕了,有时候异常就当做提示信息一样,在调用者调用某个方法出现异常后及时针对性的进行处理,目前为止异常都是由JVM自行抛出,当然我们可以选择性的自己手动抛出某个异常;
Java提供了一个throw关键字,它用来抛出一个指定的异常对象;抛给上一级;

Tips:自己抛出的异常和JVM抛出的异常是一样的效果,都要进行处理,如果是自身抛出的异常一直未处理,最终抛给JVM时程序一样会终止执行;

语法格式:

throw new 异常类名(参数);

示例:

throw new NullPointerException("调用方法的对象是空的!");throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

示例代码:

package com.dfbz.demo01;public class Demo03 {    public static void main(String[] args) {        method(null);        System.out.println("我还会执行吗?");    }    public static void method(Object object) {        if (object == null) {            // 手动抛出异常(抛出异常后,后面的代码将不会被执行)            throw new NullPointerException("这个对象是空的!不能调用方法!");        }        System.out.println(object.toString());    }}

运行结果:

手动抛出的异常和JVM抛出的异常是一个效果,也需要我来处理抛出的异常;
修改代码:

package com.dfbz.demo01;public class Demo03 {    public static void main(String[] args) {        try {            method(null);        } catch (Exception e) {            System.out.println("调用method方法出现异常了");        }        System.out.println("我还会执行吗?");    }    public static void method(Object object) {        if (object == null) {            // 手动抛出异常(抛出异常后,后面的代码将不会被执行)            throw new NullPointerException("这个对象是空的!不能调用方法!");        }        System.out.println("如果出现了异常我是不会执行了,你能执行到这里说明没有异常");        System.out.println(object.toString());    }}

运行结果:

1.2.4 声明异常

1)运行时异常

在定义方法时,可以在方法上声明异常,用于提示调用者;
Java提供throws关键字来声明异常;关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常);
语法格式:

... 方法名(参数) throws 异常类名1,异常类名2{   }

代码示例:

package com.dfbz.demo01;import java.text.ParseException;public class Demo04 {    public static void main(String[] args) {        // 可以处理,也可以不处理        method("你好~");        // 编译时异常必须处理        try {            method2("hello~");        } catch (ParseException e) {            System.out.println("出现异常啦!");        }    }    // 调用此方法可能会出现空指针异常,提示调用者处理异常    public static void method(Object obj) throws NullPointerException {        System.out.println(obj.toString());    }    // 抛出的不是运行时异常,调用者调用该方法时必须处理    public static void method2(Object obj) throws ParseException {        System.out.println(obj.toString());    }    // 也可以同时抛出多个异常    public static void method3(Object obj) throws ClassCastException,ArithmeticException {        System.out.println(obj.toString());    }}

2)编译时异常

在声明和抛出编译时异常时需要注意如下几点:

  • 1)如果是抛出(throw)编译是异常,那么必须要处理,可以选择在方法上声明,或者try…catch处理
  • 2)如果调用的方法上声明(throws)了编译时异常,那么在调用方法时就一定要处理这个异常,可以选择继续网上抛,也可以选择try…catch处理

  • 示例代码:
package com.dfbz.demo04;/** * @author lscl * @version 1.0 * @intro: */public class Demo02 {    public static void main(String[] args) throws Exception{        // 如果调用的方法上声明了编译时异常,那么在调用方法时就一定要处理这个异常,可以选择继续往上抛,也可以选择try..catch处理        test(500);    }    // 如果方法声明了编译时异常,那么在调用方法时就一定要处理这个异常    public static void test(Integer num) throws MyException{        if (num == 100) {            // 如果是抛出编译是异常,那么必须要处理,可以选择在方法上声明,或者try...catch处理            throw new MyException();                        /*try {                throw new MyException();            } catch (MyException e) {                e.printStackTrace();            }*/        }    }}// 直接继承与Exception,那么这个异常是一个编译时异常class MyException extends Exception {}

1.3 自定义异常

我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是Java中没有定义好的,此时我们根据自己业务的异常情况来定义异常类。
我们前面提到过异常分类编译时异常运行时异常

  • 1)继承于java.lang.Exception的类为编译时异常,编译时必须处理;
  • 2)继承于java.lang.RuntimeException的类为运行时异常,编译时可不处理;
  • 自定义用户名不存在异常:
package com.dfbz.demo04;public class UsernameNotFoundException extends RuntimeException {    /**     * 空参构造     */    public UsernameNotFoundException() {    }    /**     * @param message 表示异常提示     */    public UsernameNotFoundException(String message) {        // 调用父类的构造方法        super(message);    }}
  • 测试类:
package com.dfbz.demo04;public class Demo01 {    // 定义内置账户    public static String[] users = {"xiaohui", "xiaolan", "xiaoliu"};    public static void main(String[] args) {        findByUsername("abc");    }    public static void findByUsername(String username) throws UsernameNotFoundException {        for (String user : users) {            if (username.equals(user)) {                System.out.println("找到了: " + user);                return;            }        }        // 用户名没找到        throw new UsernameNotFoundException("没有用户: " + username);    }}

运行结果:

1.4 方法的重写与异常

  • 1)子类在重写方法时,父类方法没有声明编译时异常,则子类方法也不能声明编译时异常

需要注意的是:运行时异常没有这个规定;也就是子类在重写父类方法时,不管父类方法是否有声明异常,子类方法都可以声明异常;

package com.dfbz.demo05;/** * @author lscl * @version 1.0 * @intro: */public class Demo01 {    public static void main(String[] args) throws Exception {    }}// 定义一个编译时异常class MyException extends Exception {}class Fu {    public void method() {    }    public void method2() {    }}class Zi extends Fu {    // 语法通过,运行时异常随意    public void method() throws NullPointerException {    }    // 语法报错,父类方法没有声明编译时异常,那么子类重写方法时也不能声明编译时异常    public void method2() throws MyException {    }}
  • 2)同样是在编译时异常中,在子类重写父类方法时,子类不可以声明比父类方法大的编译时异常
package com.dfbz.demo06;/** * @author lscl * @version 1.0 * @intro: */public class Demo01 {    public static void main(String[] args) throws Exception {    }}// 定义一个编译时异常class MyException extends Exception {}class Fu {    public void method() throws NullPointerException{    }    public void method2()throws MyException{    }}class Zi extends Fu {        // 运行时异常没有问题    public void method() throws RuntimeException {    }    // 语法报错,如果是编译时异常,子类在重写父类方法时,不可以抛出比父类大的编译时异常    public void method2() throws Exception {    }}