Java学习日志(九): 异常处理详解,自定义异常

JavaEE学习日志持续更新----> 必看!JavaEE学习路线(文章总汇)

异常介绍

异常的概念

异常 :指的是程序在执行过程中,出现的非正常的情况,终会导致JVM的非正常停止。

在Java等面向对象的编程语言中,异常本身是一个类(存在于java.lang包),产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。

异常的继承体系

异常的继承体系

异常与错误的区别

异常:程序出现的小问题

  1. 编译期异常:写代码的时候,程序出现了异常
  2. 运行期异常:运行程序的时候出现的异常

异常都是可以解决的,解决之后,程序继续运行

错误:程序出现了严重问题
出现了错误,必须修改源代码,让错误不再出现

示例

扫描二维码关注公众号,回复: 9027560 查看本文章
public class Demo01Error {
    public static void main(String[] args) {
        //编译器异常:写代码的时候,程序出现了异常
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = sdf.parse("2018-08-19");
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println(date);

        //运行期异常:运行程序的时候,出现的异常
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);//ArrayIndexOutOfBoundsException
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }

        //错误:程序出现了严重问题
        int[] arr = new int[1024*1024*1024];//OutOfMemoryError  内存溢出,必须修改代码
        System.out.println("后续代码");
    }
}

异常的产生过程

如有以下代码产生了数组索引越界异常ArrayIndexOutOfBoundsException

public class Demo02 {
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        int element = getElement(3, arr);
        System.out.println(element);//ArrayIndexOutOfBoundsException
    }
    /*
    * 定义一个方法,根据数组的索引查找并返回对应元素
    * 参数:
    *   int index
    *   int[] arr
    * */
    public static int getElement(int index,int[] arr){
        int element = arr[index];
        return element;
    }
}

访问了数组的3索引,而数组没有3索引,这时JVM就会检查出程序会出现异常,
JVM会做三件事情:

  1. 会根据异常产生的原因,创建一个异常相关的对象,这个对象包含了异常产生的原因和位置

     new ArrayIndexOutOfBoundsException(3);
    
  2. JVM发现getElement方法中没有解决异常相关的逻辑,所以就会把异常对象抛出给方法的调用者main方法处理,main方法接收到了异常对象,JVM发现main方法也没有解决异常的逻辑,就会把异常对象继续抛出给main方法的调用者JVM处理

  3. JVM接收到了异常对象,JVM会把异常相关的原因和位置以红色的字体打印在控制台,并且终止当前正在执行的程序(中断处理)

异常处理

throw关键字

作用:使用throw关键字,可以在指定的位置抛出指定的异常对象

格式: throw new xxxException("异常产生的原因");

注意事项:

  1. throw关键字必须写在方法的内部
  2. throw关键字后边创建的异常对象,必须是Exception或Exception的子类对象
  3. throw关键字后边创建的异常对象,如果是运行期异常,那么不用处理这个异常,交给JVM处理,默认处理方式就是中断处理。如果是编译期异常,那么就必须处理这个异常,要么throws抛出给别人处理,要么try…catch自己处理

示例:运行期异常

public class Demo03throw {
    public static void main(String[] args) {
        //int[] arr = null;//java.lang.NullPointerException: 数组为空
        int[] arr = {1,2,3};//java.lang.ArrayIndexOutOfBoundsException: 参数index3超出数组索引范围
        //int element = getElement(0, arr);
        int element = getElement(3, arr);
        System.out.println(element);
    }

    /*
     * 定义一个方法,根据数组的索引查找并返回对应元素
     * 参数:
     *   int index
     *   int[] arr
     * 在工作中,会对方法进行合法性判断
     * 如果传递的参数不合法,就可以使用抛出异常的方式告知方法的调用者,
     * 传递参数不合法
     * */
    public static int getElement(int index, int[] arr) {
        /*
              对传递的参数数组arr进行判断,判断数组是否为null
              如果传递的数组为null
              那么就抛出一个异常对象,告知方法的调用者,数组的值为null
         */
        if(arr == null){
            throw new NullPointerException("数组为空");
        }
        /*
            对传递的参数index进行判断,判断index是否在索引范围内
            如果不在索引范围内
            那么就抛出一个异常对象,告知方法的调用者,index超出数组索引范围
         */
        if (index<0||index>arr.length-1){
            throw new ArrayIndexOutOfBoundsException("参数index"+index+"超出数组索引范围");
        }
        int element = arr[index];
        return element;
    }
}

requireNonNull方法

Objects工具类中的静态方法
static <T> T requireNonNull​(T obj) 检查指定的对象引用是否不是 null 。
static <T> T requireNonNull​(T obj, String message) 检查指定的对象引用是否不是 null 。

底层源码:

public static <T> T requireNonNull(T obj, String message) {
    if (obj == null) {
        throw new NullPointerException(message);
    } else {
        return obj;
    }
}

使用示例:

public class Demo04Objects {
    public static void main(String[] args) {
        method(null);
    }

    private static void method(Object obj) {
        /*if(obj == null){
            throw new NullPointerException("对象为空");
        }*/
        //简便写法
        //Objects.requireNonNull(obj);//java.lang.NullPointerException
        Objects.requireNonNull(obj,"对象为空");//java.lang.NullPointerException: 对象为空
    }
}

throws关键字

异常处理的第一种方式(声明抛出异常对象)

作用
当方法内部抛出异常对象时,可以使用throws关键字处理这个异常,会继续把异常对象声明抛出给方法的调用者处理
格式

修饰符 返回值类型 方法名(参数) throws xxxException,yyyException...{
        throw new xxxException();
        throw new yyyException();
}

注意事项:

  1. throws关键字必须写在方法声明处
  2. throws关键字后边声明的异常类名必须是Exception或者是throws关键字的子类
    一般啊方法内部抛出什么异常对象,就声明什么异常对象
  3. 如果方法内部抛出多个异常对象,那么throws后边就必须声明多个异常类
    抛出的多个异常对象有子父类关系,那么声明父类异常即可
  4. 如果调用了一个声明抛出异常对象的方法,那么就必须处理这个异常对象
    a.可以使用throws继续声明抛出这个异常对象,最终给JVM处理—>异常处理
    b.可以使用try…catch自己处理这个异常对象

示例:

public class Demo05throws {
    public static void main(String[] args) throws Exception{
        checkFilePath("c:\\a.java");//java.io.FileNotFoundException: 文件路径错误
        System.out.println("后续代码");//若发生异常,后续代码不执行
    }
    /*
    *   定义一个方法,对传递的文件路径进行合法性判断
    * */
    //FileNotFoundException extends IOException extend Exception
    public static void checkFilePath(String fileName) throws Exception{
        /*
            对文件路径进行判断,判断路径是否是d:\\a.txt
            如果路径不是d:\\a.txt,那么就抛出异常对象,告知方法的调用者,
            传递路径不正确
         */
        if(!"d:\\a.txt".equals(fileName)){
            throw new FileNotFoundException("文件路径错误");
        }
        /*
            对文件名后缀进行判断,判断是否以".txt"结尾
            不是".txt"结尾,那么就抛出异常对象,告知方法的调用者,
            文件名后缀不正确
         */
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件名后缀不正确");
        }
        System.out.println("读取文件");
    }
}

try…catch关键字

异常处理的第二种方式(自己捕获处理异常)

作用:当方法内部抛出异常对象时,可以使用try…catch关键字处理这个异常

好处:程序如果有后续代码,会把异常处理完毕之后,继续执行

格式

try{
    可能会产生异常的代码(没有异常的代码也可以放进来)
}catch(异常类的数据类型 变量名){
    异常的处理逻辑(随意编写)
}
...
catch(异常类的数据类型 变量名){
    异常的处理逻辑(随意编写)
}

注意事项:

  1. catch中定义的异常变量是根据try中抛出的异常来定义,一般抛出什么异常,就定义什么异常变量来接收异常对象
  2. try中可能会抛出多个异常对象,就可以让他定义多个catch来分别处理这些异常对象
  3. try代码中如果出现了异常,那么就不会继续执行try中的代码,会执行catch中异常处理逻辑,处理完毕;继续执行try…catch之后的代码
    try代码中如果没有出现异常,正常执行try中的代码,不会执行catch中的代码,执行完毕,继续执行try…catch之后的代码

示例

public class Demo06tryCatch {
    public static void main(String[] args) {
        try {
            checkFilePath("c:\\a.java");
            System.out.println("读取文件");
        } catch (FileNotFoundException e) {
            //异常处理逻辑,随笔编写
            System.out.println("发生了FileNotFoundException异常");//发生了FileNotFoundException异常
        }
        System.out.println("后续代码");
    }
    /*
     *   定义一个方法,对传递的文件路径进行合法性判断
     * */
    public static void checkFilePath(String fileName) throws FileNotFoundException {
        /*
            对文件路径进行判断,判断路径是否是d:\\a.txt
            如果路径不是d:\\a.txt,那么就抛出异常对象,告知方法的调用者,
            传递路径不正确
         */
        if(!"d:\\a.txt".equals(fileName)){
            throw new FileNotFoundException("文件路径错误");
        }
        /*
            对文件名后缀进行判断,判断是否以".txt"结尾
            不是".txt"结尾,那么就抛出异常对象,告知方法的调用者,
            文件名后缀不正确
         */
        System.out.println("读取文件");
    }
}

throwable三种处理异常的方法:

在java.lang.Throwable中有异常处理的方法:

  • String getMessage() 返回此throwable的简短描述。
  • String toString() 返回此throwable的详细消息字符串。
  • void printStackTrace() 打印的异常信息是最全面的,JVM默认调用此方法打印异常信息

示例:

public class Demo06tryCatch2 {
    public static void main(String[] args) {
        try {
            checkFilePath("c:\\a.java");
            System.out.println("读取文件");
        } catch (FileNotFoundException e) {//接收抛出的异常对象
            //异常处理逻辑,随笔编写
            //System.out.println("发生了FileNotFoundException异常");//发生了FileNotFoundException异常
            System.out.println(e.getMessage());//文件路径错误
            System.out.println(e.toString());//System.out.println(e);  //java.io.FileNotFoundException: 文件路径错误
            e.printStackTrace();
            /*java.io.FileNotFoundException: 文件路径错误
           at cn.itcast.demo01.Exception.Demo06tryCatch2.checkFilePath(Demo06tryCatch2.java:36)
           at cn.itcast.demo01.Exception.Demo06tryCatch2.main(Demo06tryCatch2.java:15)*/
        }
        System.out.println("后续代码");
    }
    /*
     *   定义一个方法,对传递的文件路径进行合法性判断
     * */
    public static void checkFilePath(String fileName) throws FileNotFoundException {
        /*
            对文件路径进行判断,判断路径是否是d:\\a.txt
            如果路径不是d:\\a.txt,那么就抛出异常对象,告知方法的调用者,
            传递路径不正确
         */
        if(!"d:\\a.txt".equals(fileName)){
            throw new FileNotFoundException("文件路径错误");
        }
        /*
            对文件名后缀进行判断,判断是否以".txt"结尾
            不是".txt"结尾,那么就抛出异常对象,告知方法的调用者,
            文件名后缀不正确
         */
        System.out.println("读取文件");
    }
}

finally关键字

作用:无论程序是否出现异常,finally中的代码都会执行

格式

try{
    可能会产生异常的代码(没有异常的代码也可以放进来)
}catch(异常类的数据类型 变量名){
    异常的处理逻辑(随意编写)
}
...
catch(异常类的数据类型 变量名){
    异常的处理逻辑(随意编写)
}finally{
    无论是否异常都会执行的代码,一般用于资源释放(IO)
}

注意:可以没有catch,直接使用try…finally

示例

public class Demo07 {
    public static void main(String[] args) {
        try {
            //可能会产生异常的代码
            throw new Exception("编译器异常");
        } catch (Exception e) {
            //异常的处理逻辑
            e.printStackTrace();
        }finally {
            System.out.println("无论是否异常都会执行的代码");
        }
    }
}

异常的注意事项

异常注意事项:

多个异常使用捕获的处理方式

  1. 多个异常分别处理
  2. 多个异常一次捕获,多次处理(最常用)
  3. 多个异常一次捕获一次处理

1:多个异常分别处理

public class Demo01 {
    public static void main(String[] args) {
        //1.多个异常分别处理
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }

        try {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            System.out.println(list.get(10));
        }catch (IndexOutOfBoundsException e){
            e.printStackTrace();
        }

        System.out.println("后续代码");
    }
}

2:多个异常一次捕获,多次处理

注意:
若下面的程序中两个catch调换位置,则在编译的时候就会报错
当一个try有多个catch时,子类异常必须定义在父类异常的上面

public class Demo01 {
    public static void main(String[] args) {
    //2.多个异常一次捕获,多次处理
        try{
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);

            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            System.out.println(list.get(10));
        }catch (ArrayIndexOutOfBoundsException e){//子类异常
            e.printStackTrace();
        }catch (IndexOutOfBoundsException e){//父类异常
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}

3:多个异常一次捕获一次处理
好处:简单
弊端:多个异常只能有一种处理方式

public class Demo01 {
    public static void main(String[] args) {
    try{
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);

            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            System.out.println(list.get(10));
        } catch (IndexOutOfBoundsException e){
            System.out.println(e);
        }
        System.out.println("后续代码");
    }
}

运行期异常的处理方式

运行期异常被抛出可以不处理,即不捕获也不声明抛出—>交给JVM处理(中断)

运行期异常没有必要处理,一般修改代码,让程序不再抛出运行期异常

public class Demo02 {
    public static void main(String[] args) {
        try{
            System.out.println(0/0);//加trycatch没有意义
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

对于0/0这样的代码,加上trycatch没有意义。

子父类异常的处理方式

如果父类方法抛出了多个异常,子类重写父类方法时
只能抛出

  1. 和父类相同的异常
  2. 或者是父类异常的子类
  3. 或者不抛出异常

父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

示例:父类

public class Fu {
    public void show01() throws ClassCastException,IndexOutOfBoundsException{}
    public void show02() throws IndexOutOfBoundsException{}
    public void show03() throws IndexOutOfBoundsException{}
    public void show04() {}
}

子类

public class Zi extends Fu {
    //抛出和父类相同的异常
    @Override
    public void show01() throws ClassCastException, IndexOutOfBoundsException {}
    //抛出父类异常的子类
    @Override
    public void show02() throws ArrayIndexOutOfBoundsException {}
    //不抛出异常
    @Override
    public void show03() throws IndexOutOfBoundsException {}
    //父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常
    @Override
    public void show04() {
        //此时子类产生该异常,只能捕获处理,不能声明抛出
        try {
            throw new Exception("编译期异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定义异常

java给我们提供的异常不够时,就需要自己定义一些异常类

格式

public class 自定义异常类的名称 extends Exception /RuntimeException{
	定义一个空参数构造方法
	public 自定义异常类的名称(){
		super();
	}
	定义一个带异常信息的构造方法
	public 自定义异常类的名称(String message){
		super(message);
	}
}

注意事项:

  1. 自定义异常类名称一般都是以Exception结尾。
  2. 如果继承Exception,那么它就是一个编译异常,方法内部抛出了编译异常,就必须处理这个异常(throws/try…catch)
  3. 如果继承RuntimeException,那么它就是一个运行期异常,无需处理,交给JVM处理(中断)

示例:自定义一个注册异常(RegisterException)

public class RegisterException extends Exception{
    //定义一个空参数构造方法
    public RegisterException() {
        super();//调用父类的空参构造
    }
    //定义一个带异常信息的构造方法
    public RegisterException(String message) {
        super(message);//调用父类的带异常信息的构造方法
    }
}

自定义异常的使用:

需求:
模拟登陆操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。

分析:

  1. 定义一个集合,保存用户名已注册的用户名(数据库)
  2. 使用Scanner获取用户输入的注册用户名
  3. 定义一个方法,用于判断用户输入的用户名是否已经被占用(使用Collection集合中的方法contains判断集合中是否包含用户输入的用户名)
    true:抛出一个异常对象,告知注册的人"亲,该用户名已经被注册"
    false:把用户名存储到集合中

示例:使用自定义异常registerException

public class Demo01 {
    public static void main(String[] args) {
        //1.定义一个集合,保存用户名已注册的用户名(数据库)
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        //2.使用Scanner获取用户输入的注册用户名
        System.out.println("请输入要注册的用户名:");
        String regName = new Scanner(System.in).next();
        //调用checkName方法
        checkName(list,regName);
        /*
        请输入要注册的用户名:
        aaa
        cn.itcast.demo03.myException.RegisterException: 亲,该用户名已经被注册
       at cn.itcast.demo03.myException.Demo01.checkName(Demo01.java:39)
       at cn.itcast.demo03.myException.Demo01.main(Demo01.java:30)
        [aaa, bbb, ccc, ddd]
        */
    }
    //3.定义一个方法,用于判断用户输入的用户名是否已经被占用
    public static void checkName(ArrayList<String> list, String regName){
        //使用Collection集合中的方法contains判断集合中是否包含用户输入的用户名
        boolean b = list.contains(regName);
        if(b){
            //true:抛出一个异常对象,告知注册的人"亲,该用户名已经被注册"
            try {
                throw new RegisterException("亲,该用户名已经被注册");
            } catch (RegisterException e) {
                e.printStackTrace();
            }
        }else {
            //false:把用户名存储到集合中
            list.add(regName);
            System.out.println("注册成功!");
        }
        System.out.println(list);
    }
}
发布了14 篇原创文章 · 获赞 15 · 访问量 1618

猜你喜欢

转载自blog.csdn.net/Sakuraaaaaaa/article/details/104193637