【JavaSE】异常的初步认识

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

1、初步认识异常

1、算数异常

 2、空指针异常

 3、数组越界异常

2、异常的结构体系

3、异常的分类

1、编译时异常/受查异常 

2、运行时异常/非受查异常 

4、异常的处理 

1、处理异常的编程方式(防御式编程

1、事前防御性(LBYL

2、事后认错行 (EAFP

2、异常的抛出 

3、异常的捕获

1、异常声明(关键字throws

2、try和catch关键字(捕获并处理异常  

3、finally关键字 

4、异常的处理流程 

 5、自定义异常类


1、初步认识异常

在Java中将程序执行过程中发生的不正常行为成为异常比如下面常见的例子

1、算数异常

public class Test {
    public static void main(String[] args) {
        System.out.println(10/0);
    }
}

 来看上面的提示的异常java.lang.ArithmeticException.他表示的是包当中的类所以这里我们可以认为ArithmeticException(算数异常是一个类。

 2、空指针异常

public class Test{ 
    public static void main(String[] args) {
        int[] array = null;
        System.out.println(array.length);
    }
}

 NullPointerException(空指针异常表示的也是一个类存在于java.lang包当中。

 3、数组越界异常

public class Test{
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        System.out.println(array[10]);
    }
}

 像上述两个异常一样ArrayIndexOutOfBoundsException(数组越界异常也属于java.lang包当中。

 从上述过程中可以看到Java中不同类型的异常都有与其对应的类来进行描述。

❗❗❗【注意事项】

1、当发生异常之后没有人为处理异常的时候那么后面的内容不会被执行。

2、在以后的学习当中我们会遇到各种各样的异常当我们不知道某种异常产生的原因或者不知道什么错误可以导致这种异常产生的时候

🔆第一种方式我们可以看这个异常的注释

第二种方式可以查看这个技术网站stackoverflow但是这个网站是国外的网站查询的时候会非常的慢。

2、异常的结构体系

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

从上图可以看到

  1. Throwable:是异常体系的顶层派生两个重要的子类Error (错误和 Exception(异常。
  2. Error指的是Java虚拟机无法解决的严重问题比如JVM的内部错误、资源耗尽等。
  3. Exception异常产生后程序员可以通过代码进行处理使程序继续执行。我们平时所说的一场就是Exception。

3、异常的分类

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

1、编译时异常/受查异常 

在程序编译期间发生的异常称为编译时异常也称为受查异常(Checked Exception。

此时这种异常要处理掉不然编译不会通过。

举例当我们创建一个student类让他实现Cloneable接口并在Student类当中重写clone(方法在Test类当中创建Student类的对象并调用clone方法。

像上述的算数异常、空指针异常、数组越界异常他们在编译时并没有报出异常都正常编译通过但上图在编译时就出现了红色波浪线表示出现异常那为什么会出现这种情况呢?

像算数异常、空指针异常、数组越界异常都是运行时异常(非受查异常 ;像上图出现的克隆异常被称为编译时异常(受查异常

【注意事项】

下面的这种情况不属于编译时异常编译时出现的语法错误不能称之为异常  

 以上这两种都不属于编译时异常这只是语法错误。所以在写代码的时候一定要分辨清楚是语法错误还是编译时异常。

2、运行时异常/非受查异常 

  • 在程序执行期间发生的异常称为运行时异常也成为非受查异常(Unchecked Exception
  • RuntimeException以及其子类对应的异常都称为运行时异常比如之前的算数异常(ArithmeticException、空指针异常(NullPointerException、数组越界异常(ArrayIndexOutOfBoundsException

 

 注意运行时异常指的是程序已经编译通过得到class文件再由JVM执行过程中出现的错误。

4、异常的处理 

1、处理异常的编程方式(防御式编程

1、事前防御性(LBYL

在操作之前做充分的检查。每一步操作都进行检查。

boolean ret = false;
ret = 登陆游戏();
if (!ret) {
    处理登陆游戏错误;
    return;
}
ret = 开始匹配();
if (!ret) {
    处理匹配错误;
    return;
}
ret = 游戏确认();
if (!ret) {
    处理游戏确认错误;
    return;
}
ret = 选择英雄();
if (!ret) {
    处理选择英雄错误;
    return;
}
ret = 载入游戏画面();
if (!ret) {
    处理载入游戏错误;
    return;
}

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

2、事后认错行 (EAFP

先操作遇到问题再处理在Java当中异常处理选择使用这种方式

try {
    登陆游戏();
    开始匹配();
    游戏确认();
    选择英雄();
    载入游戏画面();
    ...
} catch (登陆游戏异常) {
    处理登陆游戏异常;
} catch (开始匹配异常) {
    处理开始匹配异常;
} catch (游戏确认异常) {
    处理游戏确认异常;
} catch (选择英雄异常) {
    处理选择英雄异常;
} catch (载入游戏画面异常) {
    处理载入游戏画面异常;
}
    ......

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

 在Java当中异常处理主要的5个关键字throw、try、catch、final、throws。

关键字功能
throw抛出异常
try将可能出现的异常代码放在try里
catch捕获异常
finallyfinally中的代码一定会被执行
throws声明异常

要处理异常首先得有异常怎样才能有异常?代码在编译或者运行的时候触发异常或者抛出异常 。

2、异常的抛出 

我们在编写程序的时候大多数是编译器抛出异常但是我们可不可以自己抛出异常。

答案是可以在Java当中我们可以借助throw关键字抛出一个指定的异常对象将错误信息告知给调用者。

语法格式throw  new  XXXException("异常产生的原因"); 

1、这里抛出的是运行时异常的子类空指针异常 所以可以不用处理。

public class Test{
    public static void test1(int[] a){
        if(a == null){
            throw new NullPointerException("空指针异常");
            //根据自己的需求抛出自己想要的异常
            //这个语法一般用在自己自定义的异常
        }
    }

    public static void main(String[] args) {
        test1(null);
    }
}

2、这里抛出的是编译时异常用户必须自己处理不然编译无法通过。

 

 如何解决在下面的内容中。

❗❗❗【注意事项】

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

3、异常的捕获

异常的捕获也就是异常的具体处理方式只要有两种

异常声明throws
try - catch捕获处理

1、异常声明(关键字throws

语法格式

修饰符 返回值类型 方法名(参数列表) throws 异常类型1异常类型2....{
}
public static void test1(int[] a) throws CloneNotSupportedException{
}

如上述的编译时异常CloneNotSupportedException异常如何解决这里就说明第一种解决方式

声明过后的意思大概就是站在test1方法的角度来说我没有责任了 这个方法内部会抛出什么异常我已经告诉你了就需要方法的调用者自己来解决了

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

那么给main方法也进行声明

 可以看见编译器没有报错那么没有报错就说明这个异常被解决了吗?在运行时就不会报错了吗?

可以看见异常依旧存在 这里的处理异常只不过是让代码在编译的时候不报错但是代码的逻辑问题还是没有解决这时候这个异常被交给了JVM进行处理异常交给JVM处理代码运行就会直接终止。

【注意事项】

  • throws必须跟在方法的参数列表之后
  • 声明的异常必须是Exception或者Exception的子类
  • 方法内部如果抛出多个异常throws之后必须跟多个异常类型之间用逗号隔开。
public class Test{
    public static void test1(int[] a) throws CloneNotSupportedException,
            NullPointerException,ArrayIndexOutOfBoundsException {
        if(a == null){
            throw new CloneNotSupportedException("");
        }
    }

当然如果觉得上述的方法太麻烦可以直接声明多个异常的父类异常这样的写法虽然写的时候方便的但是这样的代码可读性非常低Exception异常有很多的子类这样让方法的调用者不知道调用的方法会抛出什么异常。

    public static void test1(int[] a) throws Exception{}
  • 调用声明会抛出异常的方法的时候调用者必须对该异常进行处理或者继续使用throws抛出。

【面试题】

throw和throws的区别
throw用来抛出一个异常
throws用来声明一个异常

2、try和catch关键字(捕获并处理异常  

上面说到的throws并没有对异常进行真正的处理而是将异常声明了方法的调用者需要对异常进行处理这里就需要try - catch。

语法格式

try{
   //将可能出现异常的代码放在这里
}catch(要捕获的异常类型  e){
   //如果try中的代码抛出异常了此时catch捕获时异常类型于try 中抛出的异常类型一致时
   //或者时try中抛出异常的基类时就会被捕获到
   //对异常就可以处理处理完成之后跳出try-catch结构继续执行后序代码
}catch(要捕获的异常类型  e){
    //第一个catch没有捕获到异常下一个catch捕获异常捕获到了对异常进行处理
    //处理完成之后跳出try-catch结构继续执行后序代码


   //后序代码
   // 当异常被捕获到时异常就被处理了这里的后序代码一定会执行
   // 如果捕获了由于捕获时类型不对那就没有捕获到这里的代码就不会被执行

举例

public class Test{
    public static void main(String[] args) {
        try{
            test1(null);
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
            System.out.println("捕捉到了 CloneNotSupperException 异常进行处理异常的逻辑");
        }catch(NullPointerException e){
            e.printStackTrace();
            System.out.println("捕捉到了 NullPointerException 异常");
        }
        System.out.println("正常的逻辑......");
    }

}

 可以看出当使用了try-catch 结构处理异常可以将异常解决。

【注意事项】

  • 对于受查异常来说当try当中没有抛出catch这个受查异常的时候catch检测不到就会报错。

  •  运行异常(非受查异常检测没有那么严格try当中的代码抛出异常和catch检测的异常不相同编译器在编译阶段并不会报错

  • 有的人想到将catch要检测的异常写成Exception,这样就可以一劳永逸了什么异常都可以捕Exception放在第一个catch中那么后续的catch都就没有作用了这样写没有错但是不推荐这样写的代码可读性不高

  •  但是可以用Exception来兜底。将Exception放在最后的catch中。
  public static void main(String[] args) {
        try {
            //test1(null);
            int[] array = null;
            System.out.println(array.length);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.out.println("捕捉到了 Exception 异常进行处理异常的逻辑");
        } catch (NullPointerException e) {
            e.printStackTrace();
            System.out.println("捕捉到了 ArithmeticException 异常进行处理异常的逻辑");
        }catch(Exception e){
            e.printStackTrace();
            System.out.println("捕捉到了 ArithmeticException 异常进行处理异常的逻辑");
        }
        System.out.println("正常的逻辑......");
    }
}
  • 当try当中存在多个异常的时候从上往下执行谁先抛出异常就捕获那个异常;catch当中程序的书写顺序不影响异常的捕获。

  • 当try当中有多个异常时在同一时间只会抛出一个异常就上面图片中显示的一样。
  • 当try块内抛出异常的位置之后的代码不会被执行
  • 如果try当中抛出的异常catch没有捕获到那么就会交给JVM将异常交给JVM之后就会被中端程序

❗❗补充printStackTrace ()方法的意思是在命令行打印异常信息在程序中出错的位置及原因。 

3、finally关键字 

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

语法格式

try{
    // 可能会发生异常的代码
}catch(异常类型 e){
    // 对捕获到的异常进行处理
}finally{
    // 此处的语句无论是否发生异常都会被执行到
}

//正常代码
// 如果没有抛出异常或者异常被捕获处理了这里的代码也会执行

finally当中的代码一定会被执行

 public static void main(String[] args) {
        try {
            int[] array = null;
            System.out.println(array.length);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.out.println("捕捉到了 ArrayIndexOutOfBoundsException 异常进行处理异常的逻辑");
        }catch(ArithmeticException e) {
            e.printStackTrace();
            System.out.println("捕捉到了 ArithmeticException 异常进行处理异常的逻辑");
        }finally{
            System.out.println("finally 一般被用于资源的释放....");
        }
        System.out.println("正常的逻辑......");
    }
}

即使try当中抛出的异常没有被捕获处理但是finally块内的代码还是会被运行

  • 当然你也可以尝试当try中抛出的异常被捕获并解决了之后finally块内的代码会不会被执行。
  • 也可以尝试当try当中的代码没有异常抛出finally块内的代码会不会执行。

 finally被用来进行资源的释放

第一种写法

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        try {
            int a = scanner.nextInt();
            int[] array ={1,2,3};
            System.out.println(array[1]);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.out.println("捕捉到了 ArrayIndexOutOfBoundsException 异常进行处理异常的逻辑");
        }finally{
            scanner.close();//关闭Scanner资源
            System.out.println("finally 一般被用于资源的释放....");
        }
        System.out.println("正常的逻辑......");
    }

第二种写法

public static void main(String[] args) {
       //写成这种格式在finally中就可以不用写scanner.close();
        try (Scanner scanner = new Scanner(System.in)) {
            int a = scanner.nextInt();
            int[] array ={1,2,3};
            System.out.println(array[1]);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.out.println("捕捉到了 ArrayIndexOutOfBoundsException 异常进行处理异常的逻辑");
        }finally{
            System.out.println("finally 一般被用于资源的释放....");
        }
        System.out.println("正常的逻辑......");
    }

注意finally中的代码一定会执行的一般在finally中进行一些资源清理的扫尾工作。 

下面我们来看一道题

 这个代码你觉得输出的结果会是几?

  •  finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果 finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
  • 所以我们一般不建议finally中写return

4、异常的处理流程 

我们先来了解一个知识点调用栈

方法之间是存在相互调用关系的这种调用关系我们可以应用"调用栈"来描述在JVM中有一块内存空间称为"虚拟机栈"专门存储方法之间的调用关系。

 

当func方法中存在异常的时候没有解决这个异常会随着调用栈的顺序进入main方法main方法中没有解决这个异常那么这个异常就会交给JVM那么程序就会直接终止掉。 

 

​​​​​​​【异常处理的流程】

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

 5、自定义异常类

Java当中虽然已经内置了丰富的异常类但是并没有完全包含实际开发中所遇到的一些异常此时就需要我们自己定义一些异常类来供我们自己使用。

来看一个登录程序

public class Test {
    public String name = "abcde";
    public String password = "12345678";
    public void login(String name,String password){
        if(!this.name.equals(name)){
            System.out.println("用户名错误");
            return;
        }
        if(!this.password.equals(password)){
            System.out.println("密码错误");
            return;
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.login("wertd","23455");
    }
}

可以看到当按上述代码格式来运行当输入的密码于写死的密码不相同时程序就会结束但是在一个程序中我们不能因为某个验证错误让整个程序结束运行我们是要程序是跑起来的当遇到异常时将异常抛出但是程序还在运行。 

这里我们来写一个自定义的异常类来解决这个问题

定义自定义的异常类

UserNameException类继承Exception类表示受查异常/编译时异常 

 UserNameException类继承RuntimeException类表示非受查异常/运行时异常

 下面通过自定义好的异常类来修改上述的代码

来看两个自定义的异常类

public class UserNameException extends RuntimeException {
    public UserNameException(){
        super();
    }
    //写一个有参构造方法可以在抛出异常的时候编辑出现异常的原因
    public UserNameException(String s){
        super(s);
    }
}
public class PasswordException extends RuntimeException{
    public PasswordException(){
        super();
    }
    public PasswordException(String s){
        super(s);
    }
}

用户登录功能的代码

public class Test {
    public String name = "abcde";
    public String password = "12345678";
    public void login(String name,String password) throws UserNameException,PasswordException{
        if(!this.name.equals(name)){
            System.out.println("用户名错误");
            throw new UserNameException("你的用户名错了");//这里“”内的内容是在运行期间编译器抛出异常后用作提醒错误的原因
        }
        if(!this.password.equals(password)){
            System.out.println("密码错误");
            throw new PasswordException("你的密码错了");
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        try{
            test.login("wertd","23455");
        }catch(UserNameException e){
            e.printStackTrace();
            System.out.println("用户名异常");
        }catch(PasswordException e){
            e.printStackTrace();
            System.out.println("密码异常");
        }finally{

        }
    }
}

💥总结

  • 自定义异常类然后继承自Exception或者RuntimeException
  • 实现一个带有String类型参数的构造方法参数含义出现异常的原因
  • 自定义的异常类继承自Exception的异常默认是受查异常 / 编译时异常
  • 继承自RuntimeException的异常默认是非受查异常 / 运行时异常。
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java