Java 基础语法 异常处理

目录
  • 1. 异常的背景
    • 1.1 邂逅异常
    • 1.2 异常和错误
    • 1.3 Java 异常的体系(含体系图)
    • 1.4 异常的核心思想
    • 1.5 异常的好处
  • 2. 异常的基本用法
    • 2.1 捕获异常
      • 2.1.1 基本语法
      • 2.1.2 示例一
      • 2.1.3 示例二(含使用调用栈)
      • 2.1.4 示例三(可以使用多个 catch 捕获不同的异常)
      • 2.1.5 示例四(可以使用一个 catch 捕获所有异常,不推荐)
      • 2.1.6 示例五(使用 finally,它之间的代码将在 try 语句后执行)
      • 2.1.7 示例六(finally 引申的思考题)
      • 2.1.8 示例七(使用 try 负责回收资源)
      • 2.1.9 示例八(本方法中没有合适的处理异常方式,就会沿着调用栈向上传递)
      • 2.1.10 异常处理流程总结
    • 2.2 抛出异常
    • 2.3 异常说明
  • 3. 自定义异常类

前些章节的知识点有时会涉及到异常的知识,如果没有专门学习过异常的小伙伴可能看的有点疑惑。今天这节就是为了讲解异常,让我们来了解什么是异常,它的作用是啥,怎么使用异常。

1. 异常的背景

1.1 邂逅异常

大家在学习 Java 时,应该也遇见过一些异常了,例如

算术异常:

System.out.println(10 / 0);

结果为:Exception in thread "main" java.lang.ArithmeticException: / by zero

数组越界异常:

int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr[100]);

结果为Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

空指针异常:

int[] arr = null;
System.out.println(arr.length);

结果为:Exception in thread "main" java.lang.NullPointerException

那么什么是异常呢?

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

1.2 异常和错误

  • 异常被分为下面两种

运行时异常(非受查异常):

在程序运行(通过编译已经得到了字节码文件,再由 JVM 执行)的过程当中发生的异常,是可能被大家避免的异常,这些异常在编译时可以被忽略。

例如:算数异常、空指针异常、数组越界异常等等

编译时异常(受查异常):

编译时发生的异常,这个异常是大家难以预见的,这些异常在编译时不能被简单的忽略。

例如:要打开一个不存在的文件时,一个异常就发生了

除了异常我们我们也要了解下错误

错误:

错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略。

例如:当栈溢出时,一个错误就发生了,这是编译检查不到的

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

结果为:Exception in thread "main" java.lang.StackOverflowError

那么异常和错误的区别是什么呢?

出现错误必须由我们程序员去处理它的逻辑错误,而出现异常我们只要去处理异常就好了

如果有疑惑的伙伴通过后面的介绍你会逐渐了解它们的区别

1.3 Java 异常的体系(含体系图)

Java 中异常的种类是很多的,我将一些异常收集并归类如下

其中 Error 是错误,Exception 是异常。而异常中又分为了两种,黄色的是编译时异常,橙色的是运行时异常。

但是这张图不仅仅说明了上述的关系,我们还要知道

每个异常其实都是一个类,并且箭头代表了继承的关系

我们可以通过一个代码来理解

int[] arr = null;
System.out.println(arr.length);
// 结果为:Exception in thread "main" java.lang.NullPointerException

此时我们点击这个异常 NullPointerException 就转到了它的定义,我们会看到

我们可以得到以下结论:

  • NullPointerException 是一个类
  • 这个类继承了 RuntimeException 这个类

为了刨根究底,我们继续转到 RuntimeException 这个类看看

我们又得到了以下结论:

  • RuntimeException 是一个类
  • 这个类继承了 Exception 这个类

继续刨根究底,我们又可以看到

诶,此时我们再对照着体系图我们就可以理解清除这张图的所有意思,并且此时对异常又有了个全面对认识

而今天我们的主角是异常,即 Exception,接下来我将会对它进行解析。

1.4 异常的核心思想

作为一个程序员,我们经常都面对着

错误在代码中的存在我们不言而喻,因此就产生了两种主要针对错误的方式

方式一(LBYL):在操作之前就做充分的检查
方式二(EAFP):直接操作,有错误再解决

而异常的核心思想就是 EAFP

1.5 异常的好处

那么核心思想为 EAFP 的异常有什么好处呢?

我们可以随便举一个例子,比如你打一把王者,我们要进行登录、匹配、确认游戏、选择英雄等等的操作。

如果使用 LBYL 风格的代码,我们就要对每一步都做好充分的检查之后,再进行下一步,简单写个代码如下

boolean ret = false;
ret = log();
if(!=ret){
 // 处理登录游戏错误
 return;
}
ret = matching();
if(!=ret){
 // 处理匹配游戏错误
 return;
}
ret = confirm();
if(!=ret){
 // 处理确认游戏错误
 return;
}
ret = choose();
if(!=ret){
 // 处理选择游戏错误
 return;
}

而使用 EAFP 的风格,代码则是这样的

try{
    log();
    matching();
    confirm();
    choose();
}catch(登录游戏异常){
 // 处理登录游戏错误
}catch(匹配游戏异常){
 // 处理匹配游戏错误
}catch(确认游戏异常){
 // 处理确认游戏错误
}catch(选择游戏异常){
 // 处理选择游戏错误
}

两种方式的代码一对比,大家也可以看得出哪一种更好。EAFP 风格的就可以将流程和处理异常的代码分开,看起来更加舒服。而这也就是使用异常的好处之一。上述代码运用了异常的基本用法,后续会介绍。

2. 异常的基本用法

2.1 捕获异常

2.1.1 基本语法

try{
    // 有可能出现异常的语句
}[catch(异常类型 异常对象){
    // 出现异常后的处理行为
}...]
[finally{
    // 异常的出口
}]
  • try 代码块中放的是可能出现异常的代码
  • catch 代码块中放的是出现异常后的处理行为
  • finally 代码块中的代码用于处理善后工作,会在最后执行
  • 其中 catch finally 都可以根据情况选择加或者不加

2.1.2 示例一

首先我们看一个不处理异常的代码

int[] arr = {1, 2, 3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");

结果是:

我们分析一下这个结果,首先它告诉我们在 main 方法中出现了数组越界的异常,原因就是100这个数字。下面它又告诉我们了这个异常的具体位置。

并且通过这个结果我们知道,当代码出现异常之后,程序就中止了,异常代码后面的代码就不会执行了。

那么为什么这里抛出异常之后,后面的代码就不再执行了呢?

因为当没有处理异常的时候,一旦程序发生异常,这个异常就会交给 JVM 来处理。
而一旦交给了 JVM 处理异常,程序就会立即终止执行!

这也就是为什么我们会有自己处理异常这个行为

我们如果加上 try catch 自己处理异常

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("数组越界!");
}
System.out.println("after try catch");

结果是:

我们发现 try 中出现了异常的语句,并且我们针对这个异常做出了处理的行为。而 try catch 后面的程序依然可以继续执行

我们在上述代码中处理异常时 catch 里面用的语句就是直接告诉它出现了什么问题,但是如果我们想要知道这是什么异常,在代码的第几行有问题的话,就可以再加一个调用栈。

什么是调用栈呢?

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

2.1.3 示例二(含使用调用栈)

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();
}
System.out.println("after try catch");

结果是:

2.1.4 示例三(可以使用多个 catch 捕获不同的异常)

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");

这个代码里面有多个 catch,他会捕获到第一个出现异常的位置

2.1.5 示例四(可以使用一个 catch 捕获所有异常,不推荐)

int[] arr = {1, 2, 3};
try {
    System.out.println("before");
    System.out.println(arr[100]);
    System.out.println("after");
}catch(Exception e){
    e.printStackTrace();
}
System.out.println("after try catch");

其中我们使用了 Exception 这个类,我们知道它是所有异常的父类,因此可以用来捕获所有异常。但是这个方法是不推荐的,因为异常太多了,我们不容易定位问题

并且我们能得到一个结论

catch 进行类型匹配的时候,不光会匹配相同类型的异常,也能捕获目标异常类型的子类对象

2.1.6 示例五(使用 finally,它之间的代码将在 try 语句后执行)

int[] arr = {1, 2, 3};
try {
    arr = null;
    System.out.println(arr.length);
}catch(NullPointerException e){
    e.printStackTrace();
}finally{
    System.out.println("finally 执行啦!");
}
System.out.println("after try catch");

结果为:

我们紧接着再看一个代码,我将异常给改正确

int[] arr = {1, 2, 3};
try {
    System.out.println(arr.length);
}catch(NullPointerException e){
    e.printStackTrace();
}finally{
    System.out.println("finally 执行啦!");
}
System.out.println("after try catch");

上述代码就没有错误了,但是结果是

我们就得出了这个结论

无论 catch 是否捕获到异常,都要执行 finally 语句

finally 是用来处理善后工作的,例如释放资源是可以被做到的。如果大家对于使用 finally 释放资源有疑惑,可以先看示例八,因为在 finally 中加入 Scanner close 方法就是释放资源的一种例子

2.1.7 示例六(finally 引申的思考题)

public static int func(){
    try{
        return 10;
    }catch(NullPointerException e){
        e.printStackTrace();
    }finally{
        return 1;
    }
}
public static void main(String[] args) {
    int num = func();
    System.out.println(num);
}

结果为:1

因为 finally 块永远是最后执行的。并且你也无法在这个代码之后执行其他语句,因为不管有没有捕获到异常都要执行 finally 中的 return 语句去终止代码

2.1.8 示例七(使用 try 负责回收资源)

在演示代码前要先补充一个关于 Scanner 的知识

我们知道使用 Scanner 类可以帮助我们进行控制台输入语句,但是 Scanner 还是一种资源,而资源使用完之后是需要回收的,就像是我们打开了一瓶水喝了点还要盖上它。故用完后我们可以加上 close 方法来进行回收,如

Scanner reader = new Scanner(System.in);
int a = reader.nextInt();
reader.close();

而 try 有一种写法可以在它执行完毕后自动调用 Scanner close 方法

try(Scanner sc = new Scanner(System.in){
 int num = sc.nextInt();
}catch(InputMismatchException e){
 e.printStackTrace();
}

而这种方式的代码风格要比使用 finally 中含有 close 方法要好些

2.1.9 示例八(本方法中没有合适的处理异常方式,就会沿着调用栈向上传递)

public static void func(){
    int[] arr = {1, 2, 3};
    System.out.println(arr[100]);
}
public static void main(String[] args){
    try{
        func();
    }catch(ArrayIndexOutOfBoundsException e){
        e.printStackTrace();
    }
}

结果为:

由于我们写 func 方法时出现了异常没有及时处理,但我们在 main 方法中调用它了,所以就经过方法之间互相的调用关系,我们一直到了 main 方法被调用的位置,并且此时有合适的处理异常的方法

若最终没有找到合适的异常处理方法,最终该异常就会交给 JVM 处理,即程序就会终止

2.1.10 异常处理流程总结

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

2.2 抛出异常

以上我们介绍的都是 Java 内置的类抛出的一些异常,除此之外我们也可以使用关键字 throw 手动抛出一个异常,如

public static int divide(int x, int y) {
    if (y == 0) {
        throw new ArithmeticException("抛出除 0 异常");
    }
}
public static void main(String[] args) {
 System.out.println(divide(10, 0));
}

该代码就是我们手动抛出的异常,并且手动抛出的异常还可以使用自定义的异常,后面将会介绍到

2.3 异常说明

我们在处理异常时,如果有一个方法,里面很长一大段,我们其实是希望很简单的就知道这段代码有可能会出现哪些异常。故我们可以使用关键字 throws,把可能抛出的异常显示的标注在方法定义的位置,从而提醒使用者要注意捕获这些异常,如

public static int divide(int x, int y) throws ArithmeticException{
    if (y == 0) {
        throw new ArithmeticException("抛出除 0 异常");
    }
}

注意:

如果我们将 main 方法抛出一个异常说明,而 main 方法的调用者是 JVM,所以如果在 main 函数上抛出异常的话,就相当于 JVM 来处理这个异常了

3. 自定义异常类

Java 中虽然有丰富的异常类,但是实际上肯定还要一些情况需要我们对这些异常进行扩展,创建新的符合情景的异常。

那怎么创建自定义异常呢?首先我们就可以去看看原有的那些异常是怎么做的

两异常

我们发现这两个异常都是继承在 RuntimeException 这个类的,并且都构造了两个构造方法,分别是不带参数和带参数

而我模拟了一个登录账号的代码

public class TestDemo {
    private static String userName = "root";
    private static String password = "123456";
    public static void main(String[] args) {
        login("admin", "123456");
    }
    public static void login(String userName, String password) {
        if (!TestDemo.userName.equals(userName)) {
            // 处理用户名错误
        }
        if (!TestDemo.password.equals(password)) {
            // 处理密码错误
        }
        System.out.println("登陆成功");
    }
}

通过这个模拟的场景,我们可以针对运行时账号和密码是否正确写一个异常

class UserException extends RuntimeException{
    public UserException(){
        super();
    }
    public UserException(String s){
        super(s);
    }
}
class PasswordException extends RuntimeException{
    public PasswordException(){
        super();
    }
    public PasswordException(String s){
        super(s);
    }
}

紧接着我们再手动抛出异常

public class TestDemo {
    private static String userName = "root";
    private static String password = "123456";
    public static void main(String[] args) {
        login("admin", "123456");
    }
    public static void login(String userName, String password) {
        if (!TestDemo.userName.equals(userName)) {
            throws new UserException("用户名错误");
        }
        if (!TestDemo.password.equals(password)) {
            throws new PasswordException("密码错误");
        }
        System.out.println("登陆成功");
    }
}

所以我们创建新的异常时,就是先思考这是哪种类型的异常,再照猫画虎。但是可能有疑惑,如果我们新建异常时统一继承 Exception 不就行吗?

No!由于 Exception 分为编译时异常和运行时异常,使用 Exception 的话默认是编译时异常(即受查异常),而一段代码可能抛出受查异常则必须显示进行处理。

故如果我们将上述新建的异常继承 Exception 的话,就要再对代码中的异常进行处理,否则会直接报错

到此这篇关于Java 基础语法 异常处理的文章就介绍到这了,更多相关Java 异常处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java异常处理throws完成异常抛出详解

    已检查异常抛出 对于已检查异常(checked exceptions),编译器强制要求捕获并处理可能发生的异常,不处理就不能通过编译.但调用的方法没有能力处理这种异常,对于这种情况,可以在方法声明处使用throws子句来声明抛出异常,而是调用层次向上传递,谁调用这个方法,这个异常就由谁来处理.如:在service层读取文件,如果文件不存在,则需要将文件不存在的这条信息反馈给用户.要求在service层将此异常向上抛,用户层调用services层获取此条信息,反馈给用户.示例代码如下: 创建类Re

  • Java异常(Exception)处理以及常见异常总结

    目录 前言 异常简介 异常类型 总结 前言 很多事件并非总是按照人们自己设计意愿顺利发展的,经常出现这样那样的异常情况.例如: 你计划周末郊游,计划从家里出发→到达目的→游泳→烧烤→回家.但天有不测风云,当你准备烧烤时候突然天降大雨,只能终止郊游提前回家."天降大雨"是一种异常情况,你的计划应该考虑到这样的情况,并且应该有处理这种异常的预案. 计算机程序的编写也需要考虑处理这些异常情况.异常(exception)是在运行程序时产生的一种异常情况,已经成为了衡量一门语言是否成熟的标准之一

  • java异常处理执行顺序详解try catch finally

    目录 不含return的执行顺序 finally子句 含return的执行顺序 返回类型是对象类型时值的变化 结论 不含return的执行顺序 执行顺序为执行try中代码,如果没有异常,然后执行try catch后续的代码.如: public static void main(String[] args) { try { int j = 10 / 2; System.out.println("执行try中代码!"); } catch (Exception e) { e.printSta

  • java异常处理拦截器详情

    使用异常处理拦截器,可以不用写那么多try-catch-. 我有个功能,就是前端提交短信内容到后端,后端进行一系列的处理,其中是要将短信内容提交到公共短信接口.微服务框架,公共短信接口是另外一个服务.这样子就会有一个隐患,假如这个服务不开,或者因为网络原因无法访问,怎么办? 可以原子操作,也可以重新提交啥的,不管怎么搞,错误提示是避免不了的.问题是,系统赤裸裸的贴代码的错误提示方式,极不友好.应当捕获这个错误,然后改用较为友好的内容进行提示. 最原始的办法,可以层层try-catch-,从ser

  • Java开发过程中关于异常处理的详解

    1.运行java时,出现了异常: 我这里是因为:arr[3]不存在: java.lang.ArrayIndexOutOfBoundsException: 3 public class btyf { public static void main(String[] args){ int[] arr={1,2,3}; System.out.println(arr[0]); System.out.println(arr[3]); System.out.println(arr[1]); //1 异常 A

  • Java 基础语法 异常处理

    目录 1. 异常的背景 1.1 邂逅异常 1.2 异常和错误 1.3 Java 异常的体系(含体系图) 1.4 异常的核心思想 1.5 异常的好处 2. 异常的基本用法 2.1 捕获异常 2.1.1 基本语法 2.1.2 示例一 2.1.3 示例二(含使用调用栈) 2.1.4 示例三(可以使用多个 catch 捕获不同的异常) 2.1.5 示例四(可以使用一个 catch 捕获所有异常,不推荐) 2.1.6 示例五(使用 finally,它之间的代码将在 try 语句后执行) 2.1.7 示例六

  • Java 基础语法中的逻辑控制

    目录 Java 基础语法中的逻辑控制 一.逻辑控制语句 1. 顺序结构 2. 分支结构 3. 循环结构 二.输入输出方式 1. 输出到控制台 2. 从键盘输入 三.猜数字游戏 四.总结 Java 基础语法中的逻辑控制 一.逻辑控制语句 1. 顺序结构 像我们写的代码,执行时会按照从上到下一行一行的执行.这z就是顺序结构,不同的顺序,结果可能就不一样.如 System.out.println(1) System.out.println(2) System.out.println(3) 该代码结果为

  • Java基础之异常处理操作示例

    本文实例讲述了Java基础之异常处理操作.分享给大家供大家参考,具体如下: 示例代码: public class ExecDemo { public static void main(String[] args) { int[] nums = new int[4]; System.out.println("before the exception:"); try { //try代码块 try catch代码块可以嵌套 try{ nums[7] = 10; //数组越界 System.o

  • Java基础语法之二维数组详解

    一.二维数组 进入正题之前.首先为了便于大家理解,我画了一个图: xx枪战游戏中, 我是一个刚刚注册账号的小白,系统送了我两把枪,此时,我的武器库只有这么一层(可以理解为一位数组,枪就是对应的数组中对应的元素) 经过艰苦卓绝的战斗,终于有了一笔钱,现在我打算配置好的游戏装备,我现在有了一个枪柜,它可以存放三层的枪械,每一层都可以放多把武器(这个就是二维数组,有多层,每层都是一个一维数组) 随着游戏时长和我的高超技术,获取游戏装备的效率越来越高了,一个枪柜已经存不下了,于是,我就有了多个枪柜(这个

  • Java 基础语法让你弄懂类和对象

    目录 Java 基础语法 一.类与对象的初步认知 二.类和类的实例化 三.类的成员 1. 字段/属性/成员变量 2. 方法 3. static 关键字 四.封装 1. private 实现封装 2. getter 和 setter 方法 五.构造方法 1. 基本语法 2. this 关键字 六.认识代码块 1. 什么是代码块 2. 本地代码块 3. 实例代码块 4. 静态代码块 七.补充说明 1. toString 方法 2. 匿名对象 八.总结 Java 基础语法 其实在学习 C 语言时就一直

  • Java 基础语法之解析 Java 的包和继承

    目录 一.包 1. 概念 2. 使用方式 3. 静态导入 4. 创建包 5. 包的访问权限 6. 常见的系统包 二.继承 1. 概念 2. 语法规则(含 super 使用) 3. protected 关键字 4. 更复杂的继承关系 5. final 关键字 三.组合 四.总结(含谜底) 一.包 1. 概念 根据定义:包是组织类的一种方式 那么为什么要组织类呢? 简单来讲就是保证类的唯一性,就比如在以后的工作中,如果大家一起开发一个项目,大家可能在自己的代码中都写到了一个 Test 类,而如果出现

  • Java 基础语法

    目录 1.基本语法 2.标识符(合法的变量名) 3.关键字(保留字) 4.注释 5.Java 包 5.1 package 语句 5.2 import 语句 6.源文件声明规则 前言: Java 是一门面向对象的解释型编程语言. 面向对象 意味着我们应该把一个 Java 程序看作一系列对象的集合,我们的工作就是构建这些对象,并通过调用彼此的方法来让各种对象协同工作,解决实际的问题. 解释型 意味着: Java编译程序生成字节码(byte-code),而不是通常的机器码. 相同的代码设计成可有效地传

  • Java基础语法:逻辑控制

    目录 一.逻辑控制语句 1. 顺序结构 2. 分支结构 2.1 if 语句 2.2 switch 语句 3. 循环结构 3.1 while 循环 3.2 break 3.3 continue 3.4 for 循环 3.5 do while 循环 二.输入输出方式 1. 输出到控制台 1.基本语法 2.代码示例 3.格式化输出表(随用随查) 2. 从键盘输入 1.读入一个字符(不推荐使用) 2.使用 Scanner 读取字符/字符串/整数/浮点数/布尔值 三.猜数字游戏 1.首先我们要知道在 Ja

  • java 学习笔记(入门篇)_java的基础语法

    前言 学习完了第一个java程序,之后就来系统的学习java.先从基础语法开始,这个语法你也可以理解为英语或是汉语里面的语法,只不过大家各有各的特点和区别.学习编程其实也是一个编程语言的学习过程.我们在学习英语的时候都说,要想学习好英语一定要动口说,那么在学习编程的时候你一定要动手写.编程无非就是一个写代码的过程,首要就是你心中要有基础,没有基础怎么去写呢,就像一篇好文章,没有好文笔是写不出来的.好文笔不仅靠积累,更要靠创造,编程亦是如此.java是一个面向对象的语言,在写代码的过程中,接触最多

  • Java基础异常处理代码及原理解析

    这篇文章主要介绍了java基础异常处理代码及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 异常的定义:中断了正常指令流的事件. try..catch..finally结构: class Test{ public static void main(String[] args){ System.out.println(1); try{ System.out.println(2); int i = 1 / 0; System.out.pri

随机推荐