JAVA 异常

JAVA 异常


【目标】

  1. 异常的概念及体系
  2. 异常的处理
  3. 异常的处理流程
  4. 自定义异常

1. 异常的概念及体系

1.1 异常的概念

异常:在Java中,将程序执行过程中发生的不正常行为称为异常。
在我们实际编写代码中,程序运行过程时,难免会出现一些奇奇怪怪的问题,比如:数据格式不对、网络不通畅、内存报警等。这些就是异常,有些异常,通过代码难以解决。
因此,了解并熟悉异常的知识,对于我们未来的编程中出现异常的时候,如何辨别异常,处理异常,会有很大的帮助。

1.2 常见的几个异常

下面是一段代码,分别可以触发三种不同的异常。

注意:main方法里的三段小代码,运行时,必须同时屏蔽掉其他两段,因为异常一次只能抛出一个不能同时抛出多个异常抛出异常后,后面的代码立刻停止运行。

public class Main {
    public static void main(String[] args) {
        //算术异常
//        System.out.println(10/0);

        //空指针异常
//        String s = null;
//        System.out.println(s.length()); // 求取字符串的长度

        //数组访问越界异常
        int[] array = new int[]{1,2,3,4,5}; 
        System.out.println(array[10]);

    }
}

这三种异常,相信在java代码编写的时候,许多人都会遇见这三个经典的异常:

  • 算术异常(ArithmeticException)
    在这里插入图片描述

  • 数组访问越界异常(ArrayIndexOutOfBoundsException)
    在这里插入图片描述

  • 空指针异常(NullPointerException)
    在这里插入图片描述

1.3 异常的体系结构

想要了解异常的体系结构,我们必须先知道,异常的本质到底是什么?
答:异常的本质,其实就是 类 !
怎么知道的呢? —> 当出现异常的时候,我们按住键盘最左下角的 crtl ,鼠标点击异常,就可以看到了。
如图:
在这里插入图片描述
当我们了解了异常是一个一个的类以后,我们就可以看这么一张图:
在这里插入图片描述
我们主要介绍 Throwable,Error,Exception,这三个

  • Throwable :这是是异常体系的顶层类,正如所有类的父类是Object类一样。其诞生了两个比较重要的子类:Error 和 Exception
  • Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError(栈溢出错误)OutOfMemoryError(内存溢出错误),一旦发生回力乏术。比如:癌症晚期,人体器官衰竭。
  • Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。
    我们平时所说的异常就是Exception,所有异常,都继承于Exception。Exception又可分出两个异常:
  • Checked Exceptions(编译时异常,也称为受检查异常)
  • RuntimeException(运行时异常,也称非受检查异常)

1.4 异常的分类

异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:

  1. 编译时异常(Checked Exceptions)
    程序编译期间发生的异常,称为编译时异常,也称为受检查异常(Checked Exception)

如图所示,这就是一个编译时异常。
在这里插入图片描述
2. 运行时异常(RuntimeException)
程序执行期间发生的异常,称为运行时异常,也称为非受检查异常(Unchecked Exception)

RunTimeException以及其子类对应的异常,都称为运行时异常。比如:NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组访问越界异常)、ArithmeticException(算术异常)。
注意:编译时出现的语法性错误,不能称之为异常。例如将 System.out.println 拼写错了, 写成了system.out.println. 此时编译过程中就会出错, 这是 “编译期” 出错。而运行时指的是程序已经编译通过得到class 文件了, 再由 JVM 执行过程中出现的错误。

第一点总结:

  1. 明白异常到底是什么? —> 异常就是类
  2. 知道异常的体系 / 知道异常的分类

2. 异常的处理

2.1 防御式编程

错误在代码中是客观存在的. 因此我们要让程序出现问题的时候及时通知程序猿。主要的方式有以下两种:

  1. LBYL: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
  return;
}
ret = 开始匹配();
if (!ret) {
处理匹配错误;
  return;
}
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
  return;
}
ret = 选择英雄();
if (!ret) {
  处理选择英雄错误;
  return;
  }
ret = 载入游戏画面();
if (!ret) {
处理载入游戏错误;
  return;
}

缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱

  1. EAFP: It’s Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到问题再处理. 即:事后认错型
    优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码
try {
  登陆游戏();
  开始匹配();
  游戏确认();
  选择英雄();
  载入游戏画面();
 ...
} catch (登陆游戏异常) {
  处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}

异常处理的核心思想就是 EAFP。(在JAVA语言当中,就是使用这种思想,来处理异常。)
在Java中,异常处理主要的5个关键字throw、try、catch、final、throws

这5个关键字,下面都会一一介绍到。

2.2 异常的抛出(throw的介绍)

在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者,比如:参数检测。
在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:

throw new XXXException(“异常产生的原因”);

代码演示:

    public static int getElement(int[] array,int pos) {
        if(array == null) {
            throw new NullPointerException("传递的数组为null");
        }

        if(pos < 0 || pos > array.length) {
            throw new ArrayIndexOutOfBoundsException("数组越界访问!");
        }

         return array[pos];
    }

    public static void main(String[] args) {
        // 实现一个获取数组中任意位置元素的方法。
        int[] array = new int[]{1,2,3,4,5};
        int ret = getElement(array,3);
        System.out.println(ret);
    }

这个代码具体意思是:在getElement(array,3)方法里面,如果传入的数组 array 是空的,就抛出空指针异常(NullPointerException),如果传入的下标 pos,超过了数组的长度或者为负数时,抛出数组越界访问异常(ArrayIndexOutOfBoundsException)

【注意事项】

  1. throw必须写在方法体内部
  2. 抛出的对象必须是Exception 或者 Exception 的子类对象
  3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理
  4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
  5. 异常一旦抛出,其后的代码就不会执行

第一点和第五点注意事项可以用这么一张图表示:
在这里插入图片描述

这就是抛出异常,throw 关键字的一些具体用法了。
throw关键字,是程序员手动去抛出异常,而我们平时代码运行时出现的一些异常,是由JVM抛出的异常。

大多数情况,由throw抛出的异常,是自定义的异常,什么是自定义异常?后面有介绍。

现在我们知道了,java有两种抛出异常的方式
1.你写的代码,触发了JVM的异常
2.通过throw,手动的抛出异常。

抛出了异常,我们又该如何处理异常呢?

2.3异常的捕获(throws的介绍)

异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理

2.3.1 异常声明throws

处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常
简单来说:throws就是声明当前方法可能会抛出异常,如果抛出异常,就会交给JVM处理一旦交给JVM处理,程序就会立即终止!(本质上:throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者(JVM)处理)。

语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2…{
}
如:public void func(int a) throws ArrayIndexOutOfBoundsException,NullPointerException{
方法的内容…
}

代码演示:克隆一个对象的例子

class Person implements Cloneable{
    public String name;
    public int age;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
//
public class Main{
    public static void main(String[] args) throws CloneNotSupportedException{
        Person person1 = new Person("小明",10);
        Person person2 = (Person) person1.clone();
        System.out.println(person1.name);
        System.out.println(person2.name);
    }
}

【throws的注意事项】

  1. throws必须跟在方法的参数列表之后
  2. 声明的异常必须是 Exception 或者 Exception 的子类
  3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
  4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出。
    如上面的克隆对象的代码:
    clone()方法,后面加了 throws CloneNotSupportedException,说明当调用这个方法的时候,可能会抛出异常。
    main方法,如果你不写 throws CloneNotSupportedException,代码编译的时候就会报错。此时,你需要在main方法的参数列表后,声明异常,如:
    public static void main(String[] args) throws CloneNotSupportedException,
    或者 使用 try-catch 捕获并处理异常。

2.3.2 try-catch捕获并处理异常(处理异常最主要的知识点)

throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行处理,就需要try-catch
我们先来看看 try-catch 的语法形式:

try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
//
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
//
} [catch(异常类型 e){
// 对异常进行处理
}finally{
// 此处代码一定会被执行到
} ]
// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行
注意:

  1. []中表示可选项,需要根据自己的程序,来选择添加。([ ] ,在第二个catch到finally的最后)
  2. try中的代码可能会抛出异常,也可能不会

接下来看一段代码:

    public static void functest(String str) throws NullPointerException,CloneNotSupportedException {
        // 声明了异常以后,一定要抛出异常,才能真正捕捉到异常
        if(str == null) {
            //如果 str 为 null,会抛出 NullPointerException 异常
            throw new NullPointerException();
        }

        // 后续代码......
    }
    public static void main(String[] args) {
        try {
            // 放 可能会出现异常的代码 如:
            functest(null);
//            System.out.println("哈哈哈");  
// 如果前面的语句抛出了异常,这条语句,永远不会被执行。
        }catch (NullPointerException e) { 
            //什么时候执行 catch 语句呢? 一定是你这个 try 里面的语句,抛出了相对应的异常。
            System.out.println("捕获了NullPointerException异常,并处理了异常,程序继续执行!");
        }catch (CloneNotSupportedException e) {
            System.out.println("捕获了CloneNotSupportedException异常,并处理了异常,程序继续执行!");
        }
        System.out.println("程序正常继续进行...");
        
        // 后续代码....
        
    }

这个代码,并不是实现了完整功能的代码,只是简单的去告诉你,异常是怎么去处理的而已。
结合try-catch语法和这段代码,总结的几个注意点:

  1. try 里面,放的是,可能会抛出异常的代码
  2. 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的子类时,就会被捕获到,并执行catch里面的语句,不然catch就不会执行。
  3. 异常只会抛出一个,所以,处理完异常以后,就会跳出try-catch,执行后续代码。
  4. 当异常被捕获到时,异常就被处理了,后序代码一定会执行,
    如果捕获了,由于捕获时类型不对,那就没有捕获到,后续代码就不会被执行。

关于异常的处理方式:
异常的种类有很多, 我们要根据不同的业务场景来决定.
1.对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
2.对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
3.对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息.

以错误日志的方式来处理异常:

  1. 只打印异常信息
System.out.println(e.getMessage());  //只打印异常信息

效果演示:
在这里插入图片描述

  1. 打印异常类型:异常信息
System.out.println(e); //打印异常类型:异常信息

效果演示:
在这里插入图片描述

  1. 全面的打印所有信息
e.printStackTrace();  //全面的打印所有信息

效果演示:
在这里插入图片描述
更推荐使用第三种,e.printStackTrace();,因为这样输出,不仅可以更直观看到错误信息,还可以知道错误信息具体出现在哪个地方。

三种打印信息的原代码:

    public static void main(String[] args) {
        int[] array = new int[]{1,2,3};

        try{
            int ret = array[4];
        }catch (ArrayIndexOutOfBoundsException e) {
//            System.out.println(e.getMessage()); //只打印异常信息
//            System.out.println(e); //打印异常类型:异常信息
            e.printStackTrace(); //全面的打印所有信息
        }
    }

当然,catch,不只能写一个,还能写多个,捕捉不同的异常,比如:

    public static void main(String[] args) {
        int[] array = new int[]{1,2,3};
        array = null;
        try{
            int ret = array[2];
        }catch (ArrayIndexOutOfBoundsException e) {
//            System.out.println(e.getMessage()); //只打印异常信息
//            System.out.println(e); //打印异常类型:异常信息
            e.printStackTrace(); //全面的打印所有信息
        }catch (NullPointerException e) {
            //System.out.println(e.getMessage()); //只打印异常信息
//            System.out.println(e); //打印异常类型:异常信息
            e.printStackTrace(); //全面的打印所有信息
        }
    }

在这里插入图片描述
此时,问题:会不会同时抛出 数组访问越界 和 空指针异常?
答:不会,不会同时抛出多个异常只要有异常,就停止下来处理异常,处理完以后,跳出try-catch,执行try-catch 外面的代码(后续代码)。

上面那个代码,可以简化,使用 ’ | ’ 来组成一个catch语句。

    public static void main(String[] args) {
        int[] array = new int[]{1,2,3};
        array = null;
        try{
            int ret = array[2];
        }catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
//            System.out.println(e.getMessage()); //只打印异常信息
//            System.out.println(e); //打印异常类型:异常信息
            e.printStackTrace(); //全面的打印所有信息
        }
    }

这样的代码,有一个很大的缺点:不够明确

在介绍异常的体系结构的时候,我们说:所有异常都继承于Exception,等于说,Exception是所有异常的父类,那这样的代码,又有什么问题呢?

    public static void main(String[] args) {
        int[] array = new int[]{1,2,3};
        array = null;
        try{
            int ret = array[2];
        }catch (Exception e) {
//            System.out.println(e.getMessage()); //只打印异常信息
//            System.out.println(e); //打印异常类型:异常信息
            e.printStackTrace(); //全面的打印所有信息
        }
    }

我们可以看到,catch里面,是用Exception作为参数类型的,而Exception能处理所有的异常,所以这样的捕捉异常并处理异常的写法,是有很大的问题的!!!!
因为你如果不使用e.printStackTrace();,你根本无法知道它处理了什么异常,十分不明确!!!!

我们再来看看这么一段代码:
在这里插入图片描述

这样的代码,为什么会报错呢?
答:你所有的异常,全都被Exception处理了,那要这段代码,有什么意义呢?

catch (NullPointerException e) {
            e.printStackTrace(); //全面的打印所有信息
        }

还有一个例子,NullPointerException 写在 Exception 之前,用图片给你演示并说明:
在这里插入图片描述

总结-注意事项

  1. try块内抛出异常位置之后的代码将不会被执行。
    在这里插入图片描述

  2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的

public static void main(String[] args) {
  try {
    int[] array = {1,2,3};
    System.out.println(array[3]);  // 此处会抛出数组越界异常
 }catch (NullPointerException e){  
 // 捕获时候捕获的是空指针异常--真正的异常无法被捕获到
    e.printStackTrace();
 }
  System.out.println("后序代码");
}
  1. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获。(上面也有讲到。)
public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    try {
      System.out.println("before");
      // arr = null;
      System.out.println(arr[100]);
      System.out.println("after");
   } catch (ArrayIndexOutOfBoundsException e) {
      System.out.println("这是个数组下标越界异常");
      e.printStackTrace();
   } catch (NullPointerException e) {
      System.out.println("这是个空指针异常");
      e.printStackTrace();
   }
    System.out.println("after try catch");
 }
  1. 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐) (上面有介绍过)。
    public static void main(String[] args) {
        int[] array = new int[]{1,2,3};
        array = null;
        try{
            int ret = array[2];
        }catch (Exception e) {
        //这样子会捕获所有的异常,即多个异常,一次捕获(不推荐)
            e.printStackTrace(); //全面的打印所有信息
        }
    }
  1. 由于Exception是所有异常的父类,如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误(上面也有提到):
public static void main(String[] args) {
  int[] arr = {1, 2, 3};
  try {
    System.out.println("before");
    arr = null;
    System.out.println(arr[100]);
    System.out.println("after");
 } catch (Exception e) {  // Exception可以捕获到所有异常
    e.printStackTrace();
 }catch (NullPointerException e){  // 永远都不会执行到这条语句
    e.printStackTrace();
 }
 
  System.out.println("after try catch");
}

//Error:(33, 10) java: 已捕获到异常错误java.lang.NullPointerException

总结 try-catch :这是处理异常里,最主要的方式,也是在异常里,非常重要的知识点,一定要学会用 try-catch。

2.3.3 finally

在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进行回收。另外,因为异常会引发程序的跳转,可能导致有些语句执行不到finally就是用来解决这个问题的。

首先,我们看看finally的语法:

语法格式:
try{
// 可能会发生异常的代码
}catch(异常类型 e){
// 对捕获到的异常进行处理
}finally{
// 此处的语句无论是否发生异常,都会被执行到
}
// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行

然后,看一段带代码演示:

    public static void main(String[] args) {
        int[] array = new int[]{1,2,3};
        try{
            int ret = array[2];
            array = null;
            System.out.println(array.length);
        }catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("处理了ArrayIndexOutOfBoundsException异常");
            e.printStackTrace();
        }catch (NullPointerException e) {
            System.out.println("处理了NullPointerException异常");
            e.printStackTrace();
        }finally {
            System.out.println("finally被执行了。[无论有没有异常,finally都会被执行]");
        }

        System.out.println("after");
    }

不同的代码,看运行结果:

  1. 有异常发生的代码运行结果:
    在这里插入图片描述
  2. 没有异常发生的代码的运行结果:
    在这里插入图片描述
    看两个运行结果就可以知道:无论有没有异常,finally语句里的内容都会被执行!!!
    这是finally语句的一个特性。

问题:既然 finally 和 try-catch-finally 后的代码都会执行,那为什么还要有finally呢
看这样一段代码,你可能就明白了:

public class TestFinally {
  public static int getData(){
    Scanner sc = null;
    try{
      sc = new Scanner(System.in);
      int data = sc.nextInt();
      return data;
   }catch (InputMismatchException e){
      e.printStackTrace();
   }finally {
      System.out.println("finally中代码");
   }
    System.out.println("try-catch-finally之后代码");
    if(null != sc){
      sc.close();
       return 0;
 }
  public static void main(String[] args) {
    int data = getData();
    System.out.println(data);
 }
}

上述程序,如果正常输入一个整数,成功接收输入后程序返回了,try-catch-finally之后的代码根本就没有执行,即输入流就没有被释放,造成资源泄漏
所以,我们此时就要必要把 sc.close();这条语句,放到finally语句里面,运用"无论有没有异常,finally语句里的内容都会被执行"这个特性,做一些资源清理的扫尾工作。

总结:finally中的代码是一定会执行的,一般在finally中进行一些资源清理的扫尾工作。

仍需注意的一个小点:一般我们不建议在 finally 中写 return。

    public static int func() {
        try {
            return 10;
        }finally {
            return 20;
        }
    }
    public static void main(String[] args) {
        System.out.println(func());
    }

我们运行程序:
在这里插入图片描述
方法里不是返回了10吗?但是运行的结果是20;为什么呢?
答:
finally 执行的时机是在方法返回(return)之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
这里记不住,没关系,你可以简单粗暴的理解为:finally一定是最后被执行的!

所以,一般我们不建议在 finally 中写 return (被编译器当做一个警告)

3. 异常的处理流程

关于 “调用栈”
方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述.
在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系.
当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.

如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递.

比如这样一个例子:
在这里插入图片描述
在方法里处理 和 在 main方法里处理 演示图片:
在这里插入图片描述
一般,我们建议:在 抛出异常的那个方法里 处理异常。
比如现在是在test方法里抛出的异常,就建议在test方法里处理异常

如果你不处理的话呢,程序就会向上一直传递,如果都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始未使用 trycatch 时是一样的)。

public static void main(String[] args) {
  func();
  System.out.println("after try catch");
}
public static void func() {
  int[] arr = {1, 2, 3};
  System.out.println(arr[100]);
}
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 3
	at Main.test(Main.java:28)
	at Main.main(Main.java:31)

异常处理流程总结

  • 程序先执行 try 中的代码
  • 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
  • 如果找到匹配的异常类型, 就会执行 catch 中的代码
  • 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
  • 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
  • 如果上层调用者也没有处理的了异常, 就继续向上传递.
  • 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.

4.自定义异常

Java 中虽然已经内置了丰富的异常类, 但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我们实际情况的异常结构.

4.1 自定义异常的引出

例如:实现一个类,完成用户登陆功能

class Login {
    private String useName;
    private String passWord;

    public Login(String useName, String passWord) {
        this.useName = useName;
        this.passWord = passWord;
    }

    public String getUseName() {
        return useName;
    }

    public void setUseName(String useName) {
        this.useName = useName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public void loginInfo(String useName, String passWord) {
        if(!this.useName.equals(useName)) {
            System.out.println("用户名错误");
            return;
        }
        if (!this.passWord.equals(passWord)) {
            System.out.println("密码错误");
            return;
        }

        System.out.println("登陆成功");
    }
}
/
public class Test {
    public static void main(String[] args) {
        Login login = new Login("abc","1234");

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String useName = scanner.nextLine();
        System.out.println("请输入密码:");
        String passWord = scanner.nextLine();

        login.loginInfo(useName,passWord);
    }
}

此时这个代码,如果输入用户名或者密码,成功与否,输出结果只会是个错误提示信息文本,我现在想实现这样的功能: 让他输入用户名或者密码,错误的时候,抛出异常。这时候,我们该怎么去写呢?

这时候,因为要输出我们想要的异常,所以就需要用到,自定义的异常了!

4.2 自定义异常的语法

  1. 第一步:新建一个类,类名自己定
    在这里插入图片描述

  2. 第二步: 新建的这个类,需要继承 RuntimeException 或 Exception
    在这里插入图片描述

  3. 第三步:按住键盘左下角 Ctrl键,鼠标点击 RuntimeException ,选中以下两个构造方法
    在这里插入图片描述

  4. 将其中的构造方法复制过来,放在类内,并修改其方法名 为自己定义的方法名,此时,自定义类就创建成功了
    在这里插入图片描述
    由于是根据登陆功能,所以我的自定义的异常名字是 useNameException(用户名异常)。
    还要再创建一个异常,是密码输入错误的异常。
    这里给大家自己练习:根据上面的步骤,再创建一个名为passWordException(密码异常)的异常。

4.3 自定义异常的使用

自定义好 useNameException 和 passWordException 这两个异常以后,我们就把这两个应用到登陆功能里面。
代码如下(注意:用 ‘ / ’ 这样分开的代码,表示一个类,建议一个java文件一个类):

public class useNameException extends RuntimeException{

    public useNameException() {
        super();
    }

    public useNameException(String message) {
        super(message);
    }
}
/
public class passWordException extends RuntimeException{

    public passWordException() {
        super();
    }

    public passWordException(String message) {
        super(message);
    }
}
//
class Login {
    private String useName;
    private String passWord;

    public Login(String useName, String passWord) {
        this.useName = useName;
        this.passWord = passWord;
    }

    public String getUseName() {
        return useName;
    }

    public void setUseName(String useName) {
        this.useName = useName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public void loginInfo(String useName, String passWord) throws useNameException,passWordException{
        if(!this.useName.equals(useName)) {
            throw new useNameException("用户名错误异常...");
        }
        if (!this.passWord.equals(passWord)) {
            throw new passWordException("密码错误异常...");
        }

        System.out.println("登陆成功");
    }
}
///
public class Test {
    public static void main(String[] args) {
        Login login = new Login("abc","1234");

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String useName = scanner.nextLine();
        System.out.println("请输入密码:");
        String passWord = scanner.nextLine();

        try {
            login.loginInfo(useName,passWord);
        }catch (useNameException e) {
            e.printStackTrace();
        }catch (passWordException e) {
            e.printStackTrace();
        }finally {

        }
    }
}

我们可以看到,两个自定义的异常 useNameException ,passWordException ,都是继承于RuntimeException,如果将他们改成 继承于 Exception呢?并且,我不在loginInfo方法后不声明异常了,会出现什么情况呢?

public class useNameException extends Exception{

    public useNameException() {
        super();
    }

    public useNameException(String message) {
        super(message);
    }
}
/
public class passWordException extends Exception{

    public passWordException() {
        super();
    }

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

在这里插入图片描述

可以看到,当我们通过 throw 抛出自定义异常的时候,代码报错了,为什么?

此时,我们就需要知道,继承 RuntimeException 和 Exception的区别了。

4.4 继承 RuntimeException 和 Exception 的区别

  • 继承 Exception的异常,表明该异常为 编译时异常 或者 受查异常
    在这里插入图片描述
  • 继承 RuntimeException的异常,表明该异常为 运行时异常 或者 非受查异常
    在这里插入图片描述

现在我们知道了,继承 RuntimeException 和 Exception 的区别了,那么 【改成 继承于 Exception呢?并且,我不在loginInfo方法后不声明异常了,然后throw 抛出自定义异常的时候,代码报错】这个问题,怎么解决呢?

两种方法:

  1. 使用try-catch 自己解决这个 编译时异常
    在这里插入图片描述

  2. 在方法后面,声明异常
    在这里插入图片描述

所以,以后都建议:在方法后面声明你代码里面可能会出现的自定义异常或java自带的异常,无论继承于哪一个,都不会出现这种问题了。

4.5 自定义异常的注意事项

  • 自定义异常通常会继承自 Exception 或者 RuntimeException
  • 继承自 Exception 的异常默认是受查异常(编译时异常)
  • 继承自 RuntimeException 的异常默认是非受查异常(运行时异常).

5. 总结

到这里,异常的介绍就全部结束了!异常的部分,其实没有 类和对象 那部分那么抽象,在程序开发过程中,java自带的异常,我们需要学习这些异常的工作原理,如:异常的抛出异常的捕捉和处理异常的处理流程,同时,java的异常可能不足以支撑我们所有java程序出现的异常,所以,我们要学会如何自定义异常和使用自定义异常,抛出我们想要的异常。

感谢你能看到这里,希望这篇文章能帮到屏幕前正在学习Java语法的你,如果文章有表达错误,或者有不同意见的,欢迎评论区指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值