对Java中异常的认识

一、异常的概念与体系结构

1.异常的概念

在生活中,当我们发现朋友表现出不舒服的情况,出于关心,我们都会问是不是病了,要不要去看医生。
程序也是如此,程序员是一群办事比较严谨的人,在日常开发之中,绞尽脑汁将代码尽可能写的尽善尽美,但是在运行过程中,常常会出现各种奇奇怪怪的问题。 例如:数据格式不对、网络不通畅,内存警报等。

在 Java 中,程序在执行过程中出现的不正常行为称之为异常。

通常遇到的情况如下:

  1. 算数异常
        System.out.println(10/0);

在这里插入图片描述

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

在这里插入图片描述
从上述的部分异常中不难看出,java中的不同类型的异常,都有与之对应的类来进行描述。

2.异常的体系结构

异常的种类繁多,为了对不同的异常或者错误进行更好的分类管理,Java 内部维护了一个异常的体系结构:

  1. Throwable:是异常体系的顶层类,其派生出两个重要的子类,Error 和 Exception
  2. Error:指的是 Java 虚拟机无法解决的严重问题,比如:**JVM的内部错误、资源耗尽等,**典型代表:Stack OverflowErrorOutOfMemoryError,一旦发生回力乏术。
  3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。就是我们平时说的异常。

3.异常的分类

  1. 编译时异常
    在程序编译时发生的异常,称之为编译时的异常,也称之为受检查异常。
    private  String name;
    private  String gender;
    int age;

    public abnormal clone(){
        return (abnormal)super.clone();
    }

在这里插入图片描述

  1. 运行时异常
    在程序执行期间发生的异常,称之为运行时异常,也称之为非受检查异常

注:编译时出现的语法性错误,不能称之为异常。

二、异常的处理

1.防御式编程

  1. LBYL: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型

代码示例:

boolean ret = false;
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 (游戏确认异常) {
处理游戏确认异常;
} 

**优势:**正常流程和错误流程是分离开来的,程序员更关注正常的流程,代码更加清晰,更加容易理解代码。

在 Java 中,异常处理的5各主要关键字:throw、try、catch、final、throws.

2. 异常的抛出

在 Java 中,可以借助 throw 关键字,抛出一个指定的异常对象,并将错误信息告知调用者。
语法如下:

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

代码示例:

    public static int getElement(int[] array,int index){
        if(array==null){
            throw new NullPointerException("传递的数组为null");
        }
        if(index<0 || index >= array.length){
            throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
        }
        return array[index];
    }

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

在这里插入图片描述

注:

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

3.异常的捕获

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

  1. 异常声明 throws

    当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助 throws 将异常抛给方法的调用者来处理。即,当前方法不处理异常,提醒方法的调用者处理异常。

代码实现:
需求,加载指定的文件 config.ini.

class Config {
    File file;
    public void OpenConfig(String filename) throws IOException{
        if(!filename.endsWith(".ini")){
        //判断如果是ini文件返回 true
            throw new IOException("文件不是.ini文件");
        }
        if(!filename.equals("config.ini")){
        //判断文件夹名称是否一致,一致返回 true
            throw new FileNotFoundException("配置文件名字不对");
        }
// 打开文件
    }
    public void readConfig(){
    }
}

main方法:
情况1:文件准确无误

    public static void main(String[] args) throws IOException {
        Config config = new Config();
        config.OpenConfig("config.ini");
    }

在这里插入图片描述
情况 2:文件后缀有误

        config.OpenConfig("config.zip");

在这里插入图片描述
情况 3:文件名称有误

        config.OpenConfig("another.ini");

在这里插入图片描述
注:

  1. throws必须跟在方法的参数列表之后
  2. 声明的异常必须是 Exception 或者 Exception的子类。
  3. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用 throws 抛出。

2.try-catch 捕获并处理

代码实现

需求:读取配置文件,如果配置文件不是指定名字,抛出异常,调用者进行异常处理。

class Config {
    File file;
    public void OpenConfig(String filename) throws IOException{
        if(!filename.endsWith(".ini")){
            throw new IOException("文件不是.ini文件");
        }
// 打开文件
    }
    public void readConfig(){
    }
}
    public static void main(String[] args) {
        Config config = new Config();
        try{
            config.OpenConfig("config.txt");
            System.out.println("文件打开成功");
        }catch(IOException e){
            e.printStackTrace();    //打印全面的异常信息
        }

        //一旦异常被捕获处理,后续代码会执行
        System.out.println("后续代码");
    }

在这里插入图片描述

注意事项:

  1. try 块内的异常抛出后未被处理,之后的代码就不会被执行。
  2. 如果抛出的异常类型与 catch 时的异常类型不匹配,那么异常将不会被成功捕获。
    public static void main(String[] args) {
        try{
            int[] array = {0,1,2};
            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");
            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,父类异常在后 catch,否则语法错误!
    public static void main(String[] args) {
        int[] arr = {1,2,3};

        try{
            System.out.println("before");
            arr = null;
            System.out.println("after");
        }catch (Exception e){   //捕获所有异常
            e.printStackTrace();
        }catch (NullPointerException e){    //已经被前面捕获
            e.printStackTrace();
        }
    }

在这里插入图片描述

总结:由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.

  1. finally关键字

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

语法格式:

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

    public static void main(String[] args) {
        try{
            int[] arr = {1,2,3};
            arr[0] = 10;
            System.out.println(arr[100]);
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("finally语句后的代码实现");
        }
        System.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执行");
    }
}

在这里插入图片描述

这里会有一个问题:既然 finally 和 try-catch-finally 后的代码都会执行,呢为啥还要有 finally 呢?

举例解释,需求:实现 getData 方法,内部输入一个整形数字,然后将改数字返回,并且在 main 方法中的进行打印。

代码实现:

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) {
        TestFinally testFinally = new TestFinally();
        int data = testFinally.getData();
        System.out.println(data);
    }

正常输入时:
在这里插入图片描述
非正常输入:
在这里插入图片描述
从上面的程序我们不难看出,如果输入正确,成功接收程序后就会返回,try-catch-finally之后的代码根本就没有执行,即,sc.close() 就没有执行,输入流没有被释放,会造成资源的泄露。

注意: finally 中的代码一定会执行,一般情况下 finally 中的代码会用来做一些资源清理的工作。

4.异常处理流程总结

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

三、自定义异常类

在 Java 中虽然已经内置了丰富的异常类,但是,在我们的日常开发中所遇到的一些异常,并不能被完全覆盖到,此时我们就需要创建异常类来维护我们实际情况的代码。

举例:实现一个用户登录系统。

  1. 首先,先定义两个异常类
// 密码错误时的异常类
public class PasswordException extends Exception{
    public PasswordException(String massage){
        super(massage);		//这里的super类是用来改变异常输出语句
    }
}
//	用户名错误是的异常类
public class UserNameException extends Exception{
    public UserNameException(String massage){
        super(massage);
    }
}

class Login{
	// 内部设定好用户名、密码
    private String userName = "admin";
    private String password = "123456";

    public  void logins(String UserName,String Password) throws UserNameException,PasswordException{
        if (!UserName.equals(userName)){
        // 修改捕获错误时的报错原因
            throw new UserNameException("用户名错误");
        }
        if (!Password.equals(password)){
            throw new PasswordException("密码错误");
        }
        System.out.println("登录成功");
    }
}
    public static void main(String[] args) {
        Login login = new Login();
        try{
            login.logins("admin","123456");
        }catch(UserNameException e){
            e.printStackTrace();
        }catch (PasswordException e){
            e.printStackTrace();
        }
    }

情况展现:

  1. 正常登录

在这里插入图片描述

  1. 用户名错误

在这里插入图片描述
在这里插入图片描述

  1. 密码错误

在这里插入图片描述
在这里插入图片描述
注意事项

  • 自定义异常通常会继承自 Exception 或者 RuntimeException
  • 继承自 Exception 的异常默认是受查异常
  • 继承自 RuntimeException 的异常默认是非受查异常

到此, 文章结束, 如有不足, 欢迎提出. 如有错误, 欢迎指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值