java语言对异常处理运行的初步探索(try-catch-finally)

简介: java语言对异常处理运行的初步探索(try-catch-finally)

异常处理机制

java中的异常处理机制使得即使程序出现异常,代码也能够继续执行下去而不是直接退出程序。下面我们先来简单的了解一下异常处理是怎么使用。

在引用异常处理之前,代码运行中存在异常会导致JVM直接中断该程序并输出异常信息,如下:

public class ExceptionTry {
    public static void main(String[] args) {
        int i = 0;
        System.out.println(9 / i);
        System.out.println("程序继续执行");
    }
}

而在使用异常处理机制之后,我们可以对异常进行简单的处理,应用场景有很多,假如淘宝商品的筛选中价格筛选区间实现是使用如下的代码(目前学习还不够深入,淘宝具体功能的实现可能不是这么简单,只是我是这么理解的):

Scanner scanner = new Scanner(System.in);

double a = scanner.nextDouble();

假如用户玩心大起,或者输入时有误触,输入字符串,这会产生InputMismatchException异常,也就是输入类型无法转换为double型,数据输入类型不匹配,从而导致程序直接中断退出,用户再次使用就只能重新启动,极度影响软件的使用体验。同时,在debug代码中也会带来许多不必要的麻烦。这个时候使用异常处理可以很好的优化使用体验,同时极大的提高了代码的健壮性。还是使用第一个例子:

 

public class ExceptionTry {
    public static void main(String[] args) {
        try {
            int i = 0;
            System.out.println(9 / i);
        } catch (Exception e) {
            System.out.println("代码出现异常:" + e.getMessage());
        }
        System.out.println("程序继续执行");
    }
}

这个时候代码是正常运行结束的:

异常和错误的区分

异常(Exception)和错误(Error)是两个完全不同的概念,在我的理解中前者是代码在偶然性或者编程错误等因素导致的一般性问题,可以使用针对性的代码来进行处理的,比如空指针使用异常,算数错误异常(除零,开负数根等等),类型转换错误,数组越界等等;而错误则是更加严重的JVM系统内部错误或者资源耗尽导致程序完全不能执行,比如栈溢出,内存不足等等。通俗来讲就是前者是肚子疼,后者是癌症。

异常中又可以细分为运行时异常和编译时异常,前者是编译器无法检查出的逻辑错误,如上面那个例子中的by zero的除零错误就是运行时异常,而后者是编译器要求必须处理的异常,不然编译过不了,比如在开文件流的时候使用空文件出现的FileNotFundException。

 

try-catch-finally的使用

try-catch-finally语法了解

了解了异常处理机制的作用接下来我们可以来先看看这个语句是怎么使用的

整体上是很好理解的,try后面的代码是我们认为的可能出现异常的代码,在这个代码块中如果出现异常,JVM就会把出现的异常捕获到catch模块中,然后我们可以在其中进行针对性的操作,比如还是第一个例子,我们可以在catch中输出报错然后要求进行重新输入:

 

public class ExceptionTry {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int i = 0;
        try {
            System.out.println(9 / i);
        } catch (Exception e) {
            System.out.println("代码出现异常:" + e.getMessage());
            System.out.println("请重新输入i");
            i = scanner.nextInt();
        }
        System.out.println("程序继续执行");
    }
}

我们还可以在外层套循环来保证输入的数据不会出现异常,出现异常则会一直在循环中重复进行输入,甚至之后我们可以自定义异常来使得输入的数据符合我们的要求(异常除了系统自己规定的类以外还可以自定义,毕竟异常本质上也还是一种类)。

在出现异常的情况下,代码是按照try-catch-finally的执行顺序来的,finally可以省略,finally是语句必须执行的内容,一般是在执行一段代码中不管是否发生异常都必须执行其中的业务逻辑的应用场景中,比如不管是否发生异常都需要关闭一些文件流,关闭一些资源等等。catch其实也可以省略,不过这样的话程序异常会和之前一样由JVM来处理:中断程序,输出异常信息。

 

try-catch-finally的具体使用实例
import java.util.Scanner;
public class Try {
    public static void main(String[] args) {
        while(true) {
            try {
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入一个整数");
                int a = scanner.nextInt();
                System.out.println("a的值为:" + a);
                break;
            } catch (Exception e) {
                System.out.println("输入错误,请重新输入");
            }
        }
    }
}

以上就实现了数据输入时限定只能得到整数,输入异常时还不会退出程序的数据输入。我们还可以在其中添加我们想要具体限定的条件,比如大小,位长等等。

多说一句,如果尝试在循环外部创建Scanner对象,然后内部依旧使用nextInt方法,并在调试时第一次输入异常的话,那么之后就不会再有输入的机会了,会陷入一个死循环,无法实现我们想要的功能。有兴趣的朋友可以看一下。

 

我们在出现异常的时候是直接跳出代码的执行的,通过断点追踪进去Scanner具体的原码后发现,本来退出被调用方法时为了不影响下一次操作需要对一些参数进行重置的操作,现在因为异常直接跳出导致重置操作无法进行,这样就会对下一次数据的读取造成影响。关于上面那个问题,我发现的是Scanner里面的skipped属性(用来判断分隔符是否被跳过了,应该就是字段是否已经被读取完毕)没有被改回false,所以导致之后的循环中scanner对象的每一次使用skipped属性都无法被修改。(因为skipped每次都是true,就代表字段已经被读取完毕了;被读取的字段token是空,无法进行重置参数的操作;needInput初始就是false,而且只有第一次正常的读取中readInput后也被修改为false,不需要更多的输入,跳过了输入阶段;所以又抛出异常throwFor()退出方法的调用到nextInt(),但是退出时又没有修改skipped属性,所以下一次还是true,就这样进入了一个死循环)具体有问题的源码内容如下:

我们可以通过在循环体内创建对象,这样scanner会在每次调用的时候重新创建从而来进行更新;或者我们可以通过使用Integer.paseInt(scanner.next())来改变数据类型,这样就不会让scanner的属性没有重新初始化遭受异常强制中断了,因为这个时候异常时发生在外部的paseInt中的。

当然严谨来说我其实应该还要具体进去看看getCompleteTokenInBuffer方法的,并且我的解释在自己看来都不是很好,但是这对于我这个小垃圾来说源码看着属实是有点头晕了,看着getCompleteTokenInBuffer的注释大概应该可能是这么回事吧,但是又想具体搞清楚,属于是又菜又爱玩了。之后要是实力有所精进会更加具体详实的搞清楚这个问题。诚心欢迎各位大佬朋友、兄弟姐妹指正我说法里面的错误和不足,拜谢!

 

异常处理的整体机制

知道try-catch-finally的用法之后我们可以来看看异常处理的整体的处理机制了。我们知道,在使用try-catch-finally之前,异常都是由我们的JVM系统来处理的,JVM在异常处理中有着我们Object类一样的顶级地位,Object是所有类的最终父类,JVM也是所有类的异常处理的最终处理单位。具体是像下图中的结构:

我们的main调用了f1方法,f1方法又调用了f2方法。这里用到了我们的throws了,throws就像是摆烂摸鱼,如果f2中没有显式地处理这个异常,那么f2就会把这个异常抛给调用他的f1,这个异常我不想管,不管了。所以在没有使用try处理前,异常会被一层一层的被抛给最高级的JVM系统,并最终由他来处理这个异常。同时我们可以修改异常类型来选择性的抛弃指定的异常。(将Exception修改为具体的异常类型)

 

try-catch-finally的使用细节

public class Try {
   public static void main(String[] args) {
       System.out.println(a.method());
   }
}
class a{
   public static int method() {
       int i = 0;
       try {
           return 9 / i;
       } catch (Exception e) {
           System.out.println("catch");
           return ++i;
       } finally {
           System.out.println("finally");
           return ++i;
       }
   }
}

关于这一段代码的执行顺序,我们只需要明确一个概念就可以迎刃而解了——finally中的代码是必须执行的业务逻辑。在这个语句中try中发生了异常被捕获到了catch中,执行完之后到return ++i;当然是先执行完  ++i 之后发现finally没有被执行,所以就在return之前先去执行finally 的语句,并最终在finally中return。所以最后的结果是2;

 

如果将catch中的 ++i 换成 i - 8,我们的过程其实没有变化,只不过 i - 8 和 ++i有很大的区别,后者是可以作为一个独立的语句来执行的,而前者没有接收者,所以return i - 8;这里其实在底层先用一个临时变量temp储存了 i - 8 的值,然后再去执行finally的代码。如果finally中的代码没有出口return,就算finally中修改了 i ,最终还是会返回temp的值;如果有,那么会在finally中直接return。

public class Try {
    public static void main(String[] args) {
        System.out.println(a.method());
    }
}
class a{
    public static int method() {
        int i = 0;
        try {
            return 9 / i;
        } catch (Exception e) {
            System.out.println("catch");
            return i - 8;
        } finally {
            System.out.println("finally");
            i++;
//            return ++i; //返回2
        }
    }
}

结果如下:

catch

finally

-8

目录
相关文章
|
2天前
|
Java 开发者
Java中的异常处理:从基本概念到最佳实践
【4月更文挑战第30天】 在Java编程中,异常处理是确保程序健壮性和稳定性的关键机制。本文将深入探讨Java异常处理的基本概念,包括异常的分类、异常的抛出与捕获,以及如何有效地使用异常来增强代码的可读性和可维护性。此外,我们还将讨论一些关于异常处理的最佳实践,以帮助开发者避免常见的陷阱和误区。
|
3天前
|
小程序 Java 程序员
【Java探索之旅】我与Java的初相识(二):程序结构与运行关系和JDK,JRE,JVM的关系
【Java探索之旅】我与Java的初相识(二):程序结构与运行关系和JDK,JRE,JVM的关系
13 0
|
3天前
|
XML Java 测试技术
Java异常处理神器:Guava Throwables类概念与实战
【4月更文挑战第29天】在Java开发中,异常处理是保证程序稳定性和可靠性的关键。Google的Guava库提供了一个强大的工具类Throwables,用于简化和增强异常处理。本篇博客将探讨Throwables类的核心功能及其在实战中的应用。
11 2
|
3天前
|
敏捷开发 机器学习/深度学习 Java
Java中的异常处理机制深入理解与实践:持续集成在软件测试中的应用探索自动化测试在敏捷开发中的关键作用
【4月更文挑战第29天】在Java编程中,异常处理是一个重要的概念。它允许开发者在程序执行过程中遇到错误或异常情况时,能够捕获并处理这些异常,从而保证程序的稳定运行。本文将详细介绍Java中的异常处理机制,包括异常的分类、异常的处理方式以及自定义异常等内容。 【4月更文挑战第29天】 随着敏捷开发和DevOps文化的兴起,持续集成(CI)已成为现代软件开发周期中不可或缺的一环。本文将探讨持续集成在软件测试领域内的关键作用、实施策略以及面临的挑战。通过对自动化构建、测试用例管理、及时反馈等核心要素的详细分析,揭示持续集成如何提高软件质量和加速交付过程。 【4月更文挑战第29天】 在当今快速发
|
3天前
|
安全 Java 网络安全
深入理解Java异常处理网络安全与信息安全:保护数据的关键策略
【4月更文挑战第29天】本文将深入探讨Java中的异常处理机制,包括异常的概念、分类、捕获和处理等方面。通过详细的解析和实例演示,帮助读者更好地理解Java异常处理的原理和应用。 【4月更文挑战第29天】在数字化时代,数据成为最宝贵的资产之一。随着网络攻击的日益猖獗,了解并实施有效的网络安全和信息保护措施变得至关重要。本文将深入探讨网络安全漏洞的概念、加密技术的重要性以及提升个人和企业的安全意识的必要性。我们将分析当前的安全挑战,并提供一系列实用的防护策略,旨在帮助读者构建一个更加安全的数字环境。
|
3天前
|
Java 程序员
Java 异常处理
4月更文挑战第22天
|
3天前
|
Java
Java语言---面向对象的三大特征之继承
Java语言---面向对象的三大特征之继承
|
3天前
|
存储 Java
Java语言--->数据类型转化以及运算符
本文讲述了Java中的基本数据类型转换和运算符。在转换中,布尔型除外的8种类型可通过默认或强制转换相互转化,如`byte->short->int->long->float->double`。默认转换发生在不同类型运算时,系统会转为更大容量类型。强制转换可能引起精度损失或溢出。运算符包括算术(如+可做加法或字符串拼接)、比较、逻辑和赋值运算符。比较运算符如`==`、`<`等产生`boolean`结果,逻辑运算符`&&`、`||`具有短路效应。赋值运算符如`+=`简化了赋值过程。运算符的优先级也进行了简要说明。
|
3天前
|
Java 编译器 程序员
Java语言基础
Java语言基础概览:涵盖关键字(全小写,如`image.png`所示),保留字(如`goto`、`const`),标识符命名规则(避免数字开头和保留字),注释(单行`//`,多行`/*...*/`,及Javadoc注释)以及变量(按数据类型:byte, short, int, long, float, double, char, boolean,注意声明规则和默认类型)。