Java异常处理机制深入理解

目录
  • 1.初识异常
  • 2.异常的基本用法
    • 异常处理流程
  • 3.为什么要使用异常?
    • 异常应只用于异常的情况
  • 4. 异常的种类
    • 4.1 受查异常
      • 解决方案:
    • 4.2非受查异常
  • 5.如何使用异常
    • 避免不必要的使用受查异常
  • 6.自定义异常

1.初识异常

我们在写代码的时候都或多或少碰到了大大小小的异常,例如:

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

当我们数组越界时,编译器会给我们报数组越界,并提示哪行出了错。

再比如:

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

当我们尝试用使用空对象时,编译器也会报空指针异常:

那么究竟什么是异常?

所谓异常指的就是程序在 运行时 出现错误时通知调用者的一种机制 .

关键字 "运行时" ,有些错误是这样的, 例如将 System.out.println 拼写错了, 写成了

system.out.println. 此时编译过程中就会出 错, 这是 "编译期" 出错.

而运行时指的是程序已经编译通过得到 class 文件了 , 再由 JVM 执行过程中出现的错误 .

2.异常的基本用法

Java异常处理依赖于5个关键字:try、catch、finally、throws、throw。下面来逐一介绍下。

①try:try块中主要放置可能会产生异常的代码块。如果执行try块里的业务逻辑代码时出现异

常,系统会自动生成一个异常对象,该异常对象被提交给运行环境,这个过程被称为抛出

(throw)异常。Java环境收到异常对象时,会寻找合适的catch块(在本方法或是调用方

法)。

②catch: catch 代码块中放的是出现异常后的处理行为,也可以写此异常出错的原因或者打

印栈上的错误信息。但catch语句不能为空,因为一旦将catch语句写为空,就代表忽略了此

异常。如:

空的catch块会使异常达不到应有的目的,即强迫你处理异常的情况。忽略异常就如同忽略

火警信号一样——若把火警信号关掉了,当真正的火灾发生时,就没有人能看到火警信号

了。或许你会侥幸逃过一劫,或许结果将是灾难性的。每当见到空的catch块时,我们都应该

警钟长鸣。

当然也有一种情况可以忽略异常,即关闭fileinputstream(读写本地文件)的时候。因为你还

没有改变文件的状态,因此不必执行任何恢复动作,并且已经从文件中读取到所需要的信

息,因此不必终止正在进行的操作。

③finally:finally 代码块中的代码用于处理善后工作, 会在最后执行,也一定会被执行。当遇

到try或catch中return或throw之类可以终止当前方法的代码时,jvm会先去执行finally中的语

句,当finally中的语句执行完毕后才会返回来执行try/catch中的return,throw语句。如果

finally中有return或throw,那么将执行这些语句,不会在执行try/catch中的return或throw语

句。finally块中一般写的是关闭资源之类的代码。但是我们一般不在finally语句中加入return

语句,因为他会覆盖掉try中执行的return语句。例如:

finally将最后try执行的return 10覆盖了,最后结果返回了20.

④throws:在方法的签名中,用于抛出此方法中的异常给调用者,调用者可以选择捕获或者

抛出,如果所有方法(包括main)都选择抛出(或者没有合适的处理异常的方式,即异常类

型不匹配)那么最终将会抛给JVM,就会像我们之前没使用try、catch语句一样。JVM打印出

栈轨迹(异常链)。

⑤throw:用于抛出一个具体的异常对象。常用于自定义异常类中。

ps:

关于 "调用栈",方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述.

在 JVM 中有一块内存空间称为 "虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常

的时候, 我们就可以使用 e.printStackTrace() 的方式查看出现异常代码的调用栈,一般写在catch语句中。

异常处理流程

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

3.为什么要使用异常?

存在即合理,举个例子

             //不使用异常
        int[] arr = {1, 2, 3};

        System.out.println("before");

        System.out.println(arr[100]);

        System.out.println("after");

当我们不使用异常时,发现出现异常程序直接崩溃,后面的after也没有打印。

               //使用异常
        int[] arr = {1, 2, 3};

        try {

            System.out.println("before");

            System.out.println(arr[100]);

            System.out.println("after");

        } catch (ArrayIndexOutOfBoundsException e) {
            //	打印出现异常的调用栈

            e.printStackTrace();

        }

        System.out.println("after try catch");

当我们使用了异常,虽然after也没有执行,但程序并没有直接崩溃,后面的sout语句还是执行了

这不就是异常的作用所在吗?

再举个例子,当玩王者荣耀时,突然断网,他不会让你直接程序崩溃吧,而是给你断线重连的机会吧:

我们再用伪代码演示一把王者荣耀的对局过程:

不使用异常处理
boolean ret = false;

ret = 登陆游戏();

if (!ret) {

处理登陆游戏错误;

return;

}

ret = 开始匹配();

if (!ret) {

处理匹配错误;

return;

}
ret = 游戏确认();

if (!ret) {

处理游戏确认错误;

return;

}
ret = 选择英雄();

if (!ret) {

处理选择英雄错误;

return;

}

ret = 载入游戏画面();

if (!ret) {

处理载入游戏错误;

return;

}

......
使用异常处理
try {

登陆游戏();

开始匹配();

游戏确认();

选择英雄();

载入游戏画面();

...

} catch (登陆游戏异常) {

处理登陆游戏异常;

} catch (开始匹配异常) {

处理开始匹配异常;

} catch (游戏确认异常) {

处理游戏确认异常;

} catch (选择英雄异常) {

处理选择英雄异常;

} catch (载入游戏画面异常) {

处理载入游戏画面异常;

}
......

我们能明显的看到不使用异常时,正确流程和错误处理代码混在一起,不易于分辨,而用了

异常后,能更易于理解代码。

当然使用异常的好处还远不止于此,我们可以在try、catch语句中加入信息提醒功能,比如你

开发了一个软件,当那个软件出现异常时,发个信息提醒你及时去修复。博主就做了一个小

小的qq邮箱信息提醒功能,源码在码云,有兴趣的可以去看看呀!需要配置qq邮箱pop3服

务,友友们可以去查查怎么开启呀,我们主旨不是这个所以不教怎么开启了。演示一下:

别群发消息哦,不然可能会被封号???

异常应只用于异常的情况

try{
   int i = 0;
   while(true)
       System.out.println(a[i++]);
}catch(ArrayIndexOutOfBoundsException e){
 }

这段代码有什么用?看起来根本不明显,这正是它没有真正被使用的原因。事实证明,作为

一个要对数组元素进行遍历的实现方式,它的构想是非常拙劣的。当这个循环企图访问数组

边界之外的第一个数组元素时,用抛出(throw)、捕获(catch)、

忽略(ArrayIndexOutOfBoundsException)的手段来达到终止无限循环的目的。假定它与数

组循环是等价的,对于任何一个Java程序员来讲,下面的标准模式一看就会明白:

for(int m : a)
   System.out.println(m);

为什么优先异常的模式,而不是用行之有效标准模式呢?

可能是被误导了,企图利用异常机制提高性能,因为jvm每次访问数组都需要判断下标是否越

界,他们认为循环终止被隐藏了,但是在foreach循环中仍然可见,这无疑是多余的,应该避

免。

上面想法有三个错误:

1.异常机制设计的初衷是用来处理不正常的情况,所以JVM很少对它们进行优化。

2.代码放在try…catch中反而阻止jvm本身要执行的某些特定优化。

3.对数组进行遍历的标准模式并不会导致冗余的检查。

这个例子的教训很简单:顾名思义,异常应只用于异常的情况下,它们永远不应该用于正常

的控制流。

总结:异常是为了在异常情况下使用而设计的,不要用于一般的控制语句。

4. 异常的种类

在Java中提供了三种可抛出结构:受查异常(checked exception)、运行时异常(run-time exception)和错误(error)。

(补充)

4.1 受查异常

什么是受查异常?只要不是派生于error或runtime的异常类都是受查异常。举个例子:

我们自定义两个异常类和一个接口,以及一个测试类

interface IUser {
    void changePwd() throws SafeException,RejectException;
}

class SafeException extends Exception {//因为继承的是execption,所以是受查异常类

    public SafeException() {

    }

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

}

class RejectException extends Exception {//因为继承的是execption,所以是受查异常类

    public RejectException() {

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

public class Test {
    public static void main(String[] args) {
        IUser user = null;
        user.changePwd();
    }
}

我们发现test测试类中user使用方法报错了,因为java认为checked异常都是可以再编译阶

段被处理的异常,所以它强制程序处理所有的checked异常,java程序必须显式处checked

异常,如果程序没有处理,则在编译时会发生错误,无法通过编译。

解决方案:

①try、catch包裹

 IUser user = null;
        try {
            user.changePwd();
        }catch (SafeException e){
            e.printStackTrace();
        }
        catch (RejectException e){
            e.printStackTrace();
        }

②抛出异常,将处理动作交给上级调用者,调用者在调用这个方法时还是要写一遍try、catch

包裹语句的,所以这个其实是相当于声明,让调用者知道这个函数需要抛出异常

public static void main(String[] args) throws SafeException, RejectException {
        IUser user = null;
        user.changePwd();
    }

4.2非受查异常

派生于error或runtime类的所有异常类就是非受查异常。

可以这么说,我们现在写程序遇到的异常大部分都是非受查异常,程序直接崩溃,后面的也

不执行。

像空指针异常、数组越界异常、算术异常等,都是非受查异常。由编译器运行时给你检查出

来的,所以也叫作运行时异常。

5.如何使用异常

避免不必要的使用受查异常

如果不能阻止异常条件的产生,并且一旦产生异常,程序员可以立即采取有用的动作,这种

受查异常才是可取的。否则,更适合用非受查异常。这种例子就是

CloneNotSuppportedException(受查异常)。它是被Object.clone抛出来的,Object.clone

只有在实现了Cloneable的对象上才可以被调用。

被一个方法单独抛出的受查异常,会给程序员带来非常高的额外负担,如果这个方法还有其

他的受查异常,那么它被调用是一定已经出现在一个try块中,所以这个异常只需要另外一个

catch块。但当只抛出一个受查异常时,仅仅一个异常就会导致该方法不得不处于try块中,也

就导致了使用这个方法的类都不得不使用try、catch语句,使代码可读性也变低了。

受查异常使接口声明脆弱,比如一开始一个接口只有一个声明异常

interfaceUser{
    //修改用户名,抛出安全异常
    publicvoid changePassword() throws MySecurityExcepiton;
} 

但随着系统开发,实现接口的类越来越多,突然发现changePassword还需要抛出另一个异

常,那么实现这个接口的所有类也都要追加对这个新异常的处理,这个工程量就很大了。

总结:如果不是非用不可,尽量使用非受查异常,或将受查异常转为非受查异常。

6.自定义异常

我们用自定义异常来实现一个登录报错的小应用

class NameException extends RuntimeException{//用户名错误异常
    public NameException(String message){
        super(message);
    }
}
class PasswordException extends RuntimeException{//密码错误异常
    public PasswordException(String message){
        super(message);
    }
}

test类来测试运行

public class Test {
    private static final String name = "bit";
    private static final String password ="123";

    public static void Login(String name,String password) throws NameException,PasswordException{
        try{
            if(!Test.name.equals(name)){
                throw new NameException("用户名错误!");
            }
        }catch (NameException e){
            e.printStackTrace();
        }
        try {
            if(!Test.password.equals(password)){
                throw new PasswordException("密码错误");
            }
        }catch (PasswordException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();
        String password = scanner.nextLine();
        Login(name,password);
    }
}

关于异常就到此为止了,怎么感觉还有点意犹未尽呢?

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

(0)

相关推荐

  • JAVA异常处理机制之throws/throw使用情况的区别

    JAVA中throw和throws的区别:https://www.cnblogs.com/xiohao/p/3547443.html 区别:(摘自上面的博客) 1.throws出现在方法函数头:而throw出现在函数体. 2.throws表示出现异常的一种可能性,并不一定会发生这些异常:throw则是抛出了异常,执行throw则一定抛出了某种异常. 3.两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用

  • 深入理解java异常处理机制的原理和开发应用

    Java异常处理机制其最主要的几个关键字:try.catch.finally.throw.throws,以及各种各样的Exception.本篇文章主要在基础的使用方法上,介绍了如何更加合理的使用异常机制. try-catch-finally try-catch-finally块的用法比较简单,使用频次也最高.try块中包含可能出现异常的语句(当然这是人为决定的,try理论上可以包含任何代码),catch块负责捕获可能出现的异常,finally负责执行必须执行的语句,这里的代码不论是否发生了异常,

  • java异常处理机制示例(java抛出异常、捕获、断言)

    这是一个介绍基本异常处理的小例子,包括抛出,捕获,断言,日志. Java异常处理通过5个关键字try.catch.throw.throws.finally进行管理.基本过程是用try语句块包住要监视的语句,如果在try语句块内出现异常,则异常会被抛出,你的代码在catch语句块中可以捕获到这个异常并做处理;还有以部分系统生成的异常在Java运行时自动抛出.你也可以通过throws关键字在方法上声明该方法要抛出异常,然后在方法内部通过throw抛出异常对象. 复制代码 代码如下: package

  • Java异常处理机制try catch流程详解

    在项目中遇到try...catch...语句,因为对Java异常处理机制的流程不是很清楚,导致对相关逻辑代码不理解.所以现在来总结Java异常处理机制的处理流程: 1.异常处理的机制如下:在方法中用 try... catch... 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个不同类型的异常.对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 声明异常,通过throw语句拋出异常,即由上层的调用方法来处理该异常. try { 逻辑程序块 } catch(Excep

  • 深入理解java异常处理机制及应用

    1. 引子 try-catch-finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解.不过,我亲自体验的"教训"告诉我,这个东西可不是想象中的那么简单.听话.不信?那你看看下面的代码,"猜猜"它执行后的结果会是什么?不要往后看答案.也不许执行代码看真正答案哦.如果你的答案是正确,那么这篇文章你就不用浪费时间看啦. package Test; public class TestException { public TestEx

  • 全面理解java中的异常处理机制

    一.java异常总结: 异常就是程序运行时出现不正常运行情况 1.异常由来: 通过java的类的形式对现实事物中问题的描述,并封住成了对象 其实就是java对不正常情况描述后的对象体现 2.对于问题的划分有两种:一种是严重的问题,一种是非严重的问题 对于严重的,java通过Error类来描述 对于Error一般不编写针对性的代码对其进行处理 对于非严重的,java通过Exception类来描述 对于Exception可以使用针对性的处理方式进行处理 3.常见的异常有:数组角标越界异常,空指针异常

  • 剖析Java中的事件处理与异常处理机制

    一.事件处理 其实,由事件处理这个名字自然就想到MFC中的消息响应机制,就我的体会,它们应该算是南桔北枳的情形吧,我怀疑Java中的事件处理这个"新瓶"应是装的MFC中的消息响应这个"旧酒".     所谓的"事件"即如键盘按键.鼠标点击等这类由动作或什么导致某个状态改变并需要对这个改变作相应响应的这类改变.我们可以将Java中的事件分为按钮.鼠标.键盘.窗口.其它事件这几大类.     事件处理模型  1.   基于继承的事件处理模型(JDK1

  • java多线程中的异常处理机制简析

    在java多线程程序中,所有线程都不允许抛出未捕获的checked exception,也就是说各个线程需要自己把自己的checked exception处理掉.这一点是通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw exception部分)进行了约束.但是线程依然有可能抛出unchecked exception,当此类异常跑抛出时,线程就会终结,而对于主线程和其他线程完全不受影响,且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常

  • java-流的使用完结与异常处理机制(详解)

    1.1 java.io.objectInputStream 对象输入流:用于将一组字节(通过对象输出流写出对象而转换的一组字节)读取并转换为对应的对象.对象输出流将对象写出时转换为一组字节的过程,称为:对象序列化对象输入流将这组字节读取并还原会对象的过程,称为:对象反序列化 1.2 java.io.Serializable Serializable序列化接口 当一个类实现了Serializable接口后,应当在当前类中添加一个常量: 序列化版本号serialVersionUID 序列化版本号若不

  • Java异常处理机制深入理解

    目录 1.初识异常 2.异常的基本用法 异常处理流程 3.为什么要使用异常? 异常应只用于异常的情况 4. 异常的种类 4.1 受查异常 解决方案: 4.2非受查异常 5.如何使用异常 避免不必要的使用受查异常 6.自定义异常 1.初识异常 我们在写代码的时候都或多或少碰到了大大小小的异常,例如: public class Test { public static void main(String[] args) { int[] arr = {1,2,3}; System.out.println

  • Java反射机制深入理解

    Java反射机制深入理解 一.概念 反射就是把Java的各种成分映射成相应的Java类. Class类的构造方法是private,由JVM创建. 反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来.Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性.例如,Pascal.C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息.

  • JAVA异常处理机制之throws/throw使用情况

    JAVA中throw和throws的区别:https://www.jb51.net/article/191065.htm 区别:(摘自上面的博客) 1.throws出现在方法函数头:而throw出现在函数体. 2.throws表示出现异常的一种可能性,并不一定会发生这些异常:throw则是抛出了异常,执行throw则一定抛出了某种异常. 3.两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理. 也就

  • 浅析Java中的异常处理机制

    异常处理机制 1.抛出异常 2.捕获异常 3.异常处理五个关键字: try.catch.finally.throw.throws 注意:假设要捕获多个异常:需要按照层级关系(异常体系结构) 从小到大! package exception; /** * Java 捕获和抛出异常: * 异常处理机制 * 1.抛出异常 * 2.捕获异常 * 3.异常处理五个关键字 * try.catch.finally.throw.throws * 注意:假设要捕获多个异常:需要按照层级关系(异常体系结构) 从小到大

  • Java十分钟精通异常处理机制

    目录 异常处理机制的底层原理 异常的继承关系图 异常的处理 一.try-catch-finally结构 二.多catch处理不同的异常: 三.throws声明异常/throw抛出异常: 四.自定义异常: 五.常见的异常 异常处理机制的底层原理 抛出异常,在执行一个方法时,如果发送了异常,则这个方法生成代表该异常的一个对象,停止当前执行的 路径,并把异常提交给jre. 捕获异常:jre得到该异常后,虚招相应的代码来处理该异常.jre在方法的调用栈中查找,从生成异常的 方法开始回溯,直到找到相应的异

随机推荐