Java: 理解异常/错误(Error和Exception)_java 错误和异常-程序员宅基地

技术标签: java  

查漏补缺,持续学习

参考:Java中finally关键字的几个坑Java:详解Java中的异常(Error与Exception)​​​​​​​

一、Throwable结构

在Java中,Throwable是所有错误与异常的超类。Throwable包含两个子类:Error(错误)和Exception(异常)

异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理

Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常

Error: 是程序无法处理的错误,表示运行应用程序中较严重问题. 大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。(如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等)这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。在 Java中,错误通过Error的子类描述。

当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError,Java虚拟机(JVM)一般会选择线程终止

Exception: 是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。

  • 不可查异常定义【包括RuntimeException和错误】:执行方法期间抛出的RuntimeException的任何子类都无需在throws子句中进行声明,因为它是uncheckedExcepiton。常见五种RuntimeException如下,RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException

    • java.lang.ArithmeticException(算术异常)
    • java.lang.ClassCastException(类型转换异常)
    • java.lang.IllegalArgumentException(不合法的参数异常)
    • java.lang.IndexOutOfBoundsException(数组下标越界异常)
    • java.lang.NullPointerException(空指针异常) 
  • 受检异常定义: Exception 中除 RuntimeException 及其子类之外的异常。特点: Java编译器要求程序必须try-cache捕获或声明throws子句抛出这种异常

    • java.io.IOException(IO流异常)
    • java.lang.ClassNotFoundException(没找到指定类异常)
    • java.lang.NoSuchFieldException(没找到指定字段异常)
    • java.lang.NoSuchMetodException(没找到指定方法异常)
    • java.lang.IllegalAccessException(非法访问异常)
    • java.lang.InterruptedException(中断异常)

二、异常处理机制

在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。 

2.1 抛出异常

2.1.1 抛出异常的声明

如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。例如汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。

throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。throws语句的语法格式为:

 methodname throws Exception1,Exception2,..,ExceptionN  {  }  

方法名后的throws Exception1,Exception2,…,ExceptionN 为声明要抛出的异常列表。当方法抛出异常列表的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理。

使用throws关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。

2.1.2 Throws抛出异常的规则:

1. 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
2. 如果一个方法可能出现可查异常(checked exception),要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
3. 只有当抛出了异常时,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出。
4. 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类

2.1.3 使用Throws抛出异常

throw总是出现在方法体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块,或者层层上抛,最终JVM会进行处理。

我们知道,异常是异常类的实例对象,我们可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为:

throw new exceptionname;

 例如抛出一个IOException类的异常对象:

throw new IOException;

要注意的是,throw 抛出的只能够是可抛出类Throwable 或者其子类的实例对象。下面的操作是错误的,因为String 不是Throwable 类的子类。

throw new String("exception");

如果抛出了可查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。

如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。

public class TestException {  
    public static void main(String[] args) {  
        int a = 6;  
        int b = 0;  
        
        try { 
            if (b == 0) {
				throw new ArithmeticException(); 
				//"除数为0"等ArithmeticException,是RuntimException的子类。而运行时异常将由运行时系统自动抛出,不需要使用throw语句,这里把throw new ArithmeticException()去掉也是不影响运行结果的。
			}
            System.out.println("a/b的值是:" + a / b);  
        } catch (ArithmeticException e) {
            System.out.println("程序出现异常,变量b不能为0。");  
        }  
        
        System.out.println("程序正常结束。");  
    }  
}

运行结果:

程序出现异常,变量b不能为0。

程序正常结束。

2.1.4 补充

为什么要在声明方法抛出异常?
答:方法是否抛出异常与方法返回值的类型一样重要。假设方法抛出异常却没有声明该方法将抛出异常,那么客户程序员可以调用这个方法而且不用编写处理异常的代码。那么,一旦出现异常,那么这个异常就没有合适的异常控制器来解决。

为什么抛出的异常一定是可检查异常(除了Exception中的RuntimeException及其子类以外,其他的Exception类及其子类)?
答:RuntimeException与Error可以在任何代码中产生,它们不需要由程序员显示的抛出,一旦出现错误,那么相应的异常会被自动抛出。遇到Error,程序员一般是无能为力的;遇到RuntimeException,那么一定是程序存在逻辑错误,要对程序进行修改;只有可检查异常才是程序员所关心的,程序应该且仅应该抛出或处理可检查异常。而可检查异常是由程序员抛出的,这分为两种情况:客户程序员调用会抛出异常的库函数;客户程序员自己使用throw语句抛出异常。

注意: 在finally块中不能抛出异常。JAVA异常处理机制保证无论在任何情况下必须先执行finally块然后再离开try块,因此在try块中发生异常的时候,JAVA虚拟机先转到finally块执行finally块中的代码,finally块执行完毕后,再向外抛出异常。如果在finally块中抛出异常,try块捕捉的异常就不能抛出,外部捕捉到的异常就是finally块中的异常信息,而try块中发生的真正的异常堆栈信息则丢失了。

2.2 捕获异常

2.2.1 try-cache语句

try {  
	// 可能会发生异常的程序代码  
} catch (Type1 id1){  
	// 捕获并处置try抛出的异常类型Type1  
} catch (Type2 id2){  
	 //捕获并处置try抛出的异常类型Type2  
}

      关键词try后的一对大括号将一块可能发生异常的代码包起来,称为监控区域

      Java方法在运行过程中出现异常,则创建异常对象。将异常抛出监控区域之外,由Java运行时系统试图寻找匹配的catch子句以捕获异常。若有匹配的catch子句,则运行其异常处理代码,try-catch语句结束。

      匹配的原则是:如果抛出的异常对象属于catch子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与catch块捕获的异常类型相匹配。

      注意:一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。

      Java通过异常类描述异常类型,对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的catch子句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。


2.2.2 try-cache-finally语句

 try-catch语句还可以包括第三部分,就是finally子句。它表示无论是否出现异常,都应当执行的内容。try-catch-finally语句的一般语法形式为:

try {  
	// 可能会发生异常的程序代码  
} catch (Type1 id1){  
	// 捕获并处置try抛出的异常类型Type1  
} catch (Type2 id2){  
	 //捕获并处置try抛出的异常类型Type2  
}finally {  
	// 无论是否发生异常,都将执行的语句块  
} 

try、catch、finally语句块的执行顺序:
     1)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
     2)当try捕获到异常,catch语句块里没有处理此异常的情况:此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
     3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句。

finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:
     1)在finally语句块中发生了异常。
     2)在前面的代码中用了System.exit()退出程序。
     3)程序所在的线程死亡。
     4)关闭CPU。

 2.2.3 如果执行了finally,返回值的问题?

public static void main(String[] args) {
	System.out.println(test());
}
	
public static int test(){
	try {
		System.out.println("try block");
	
		int i = 1 / 0; 
		return 0;
	} catch (Exception e) {
		System.out.println("catch block");
		return 1;
	} finally {
		System.out.println("finally block");
		return 2;
	}
}

运行结果输出:

try block
catch block
finally block

对于上面的代码,相信大部分人都能知道输出值是 2,打印结果也确实是 2,就算把 int i = 1 / 0这一行注释掉,打印结果也是 2。

所以在这里我们可以下结论 : finally里的 return语句会把 try/catch块里的 return语句效果给覆盖掉。

假如我们不在 finally中 return,结果会怎样?我们再看看下面的例子 :

public static void main(String[] args) {
	System.out.println(test());
}
	
public static int test(){
	int i = 999;
	try {
		System.out.println("try block");
		
		i = 1 / 0;
		return i;
	} catch (Exception e) {
		System.out.println("catch block");
		
		i = 100;
		return i;
	} finally {
		System.out.println("finally block");
		
		i = 200;
	}
}

打印结果:

try block
catch block
finally block
100 

虽然调用了 finllay改变了i的值,但是最后输出还是 100,为什么呢?我们可以通过分析字节码文件得到结果 :

 public static int test();
    Code:
       0: sipush        999
       3: istore_0
。。。
      29: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      32: ldc           #14                 // String catch block
      34: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      37: bipush        100
      39: istore_0
      40: iload_0
      41: istore_2
      42: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      45: ldc           #12                 // String finally block
      47: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      50: sipush        200
      53: istore_0
      54: iload_2
      55: ireturn
。。。

从字节码文件中可以看出,在 37:39中把 100存入 index 0的位置,就是设置 i的值为 100,在 40:41中把 index 0的值赋值给了 index 2的位置,在 50:53中把 200存入 index 0的位置,就是设置 i的值为 200,最后在 54:55中把 index 2的值加载出来并返回,即最后返回的是 index 2的值 100

对于这种情况我的理解就是在 return的的时候会把返回值压入栈,并把返回值赋值给栈中的局部变量, 最后把栈顶的变量值作为函数返回值。所以在 finally中的返回值就会覆盖 try/catch中的返回值,如果 finally中不执行 return语句,在 finally中修改返回变量的值,不会影响返回结果。

 3、常见异常

RuntimeException子类

IOException

​​​​​​​

 其他    

四、相关问题

1. 为什么要创建自己的异常

答:当Java内置的异常都不能明确的说明异常情况的时候,需要创建自己的异常。

2. 应该在声明方法抛出异常还是在方法中捕获异常?

答:捕捉并处理知道如何处理的异常,而抛出不知道如何处理的异常。

3. throw和throws的区别是什么?

答:throw抛出异常,方法的调用者必须处理该异常,就是必须try catch处理这个异常,或者继续throws继续网上抛; throw抛出异常,程序就终止了,调用该方法的地方报错,不继续向后执行

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_35821291/article/details/120652656

智能推荐

ncs v2.6.0安装教程 开发环境搭建_nrf connect sdk 2.6-程序员宅基地

文章浏览阅读720次,点赞20次,收藏27次。最简单的ncs v2.6.0开发环境搭建教程最实用的ncs v2.6.0开发环境搭建教程_nrf connect sdk 2.6

Linux平台(Ubuntu或者树莓派)上下载磁力链接;使用Deluge下载_树莓派下载 ed2k文件-程序员宅基地

文章浏览阅读1.2w次。最近又想在Linux平台上下载磁力资源了,因为我之前都是Windows下载,处理好的视频再通过scp拷贝到我的树莓派上就很麻烦了。现在直接在Linux上下载,就不用拷贝了。以前用过一些下载器,Linux上的其实还是很折腾的,Aria之类的,许多要各种配置,并不能像浏览器那样,起码打开就能用。今天稍微试了试,发现树莓派有个自带的Deluge,很不错。我下载权力的游戏,一开始是没速度的。以前类似的情况..._树莓派下载 ed2k文件

测验六python编程题_Python编程的最新测试问题,语言,程序设计,测验,题目,汇总,嵩天,老师...-程序员宅基地

文章浏览阅读83次。测验1:Python基本语法元素Hello World的条件输出获得用户输入的一个整数,参考该整数值,打印输出"Hello World",要求:‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬如果输入值是0,直接输出"Hello World"‪‬‪‬‪‬‪..._测试编程题python

hive -e 和hive -f 的注意点 (//和////)-程序员宅基地

文章浏览阅读3.3w次,点赞17次,收藏59次。原谅我张嘴可能就想骂人,当然跟别人无关,想骂的是自己太年轻,也顺便记录下这个注意点。菜鸟一只,多多见谅!! 大家都知道,hive -f 后面指定的是一个文件,然后文件里面直接写sql,就可以运行hive的sql,hive -e 后面是直接用双引号拼接hivesql,然后就可以执行命令。但是,有这么一个东西,我的sql当中有一个split切割,暂且先不管这个分割的业务逻辑是什么,但是..._hive -f

Android Spinner点击选中Item不再调用onItemSelected方法的方案_android:selecteditemposition=绑定之后 手动选择onitemselect-程序员宅基地

文章浏览阅读4.2k次。Android中使用Spinner有时有二级菜单需要操作,这时选中了某一项,想再点击这一项进入二级菜单重新选择时,onItemSelected方法是不会重新调用的,二级菜单出不来,影响功能。看Spinner源码,只有现在选中的position(或rowId)和点击的position(或rowId)不一样,才会触发onItemSelected回调。AdapterView.java:void check_android:selecteditemposition=绑定之后 手动选择onitemselected不触发

单词查找树(信息学奥赛一本通-T1337)_noi 2000 单词查找树 oj-程序员宅基地

文章浏览阅读2.7k次。【题目描述】在进行文法分析的时候,通常需要检测一个单词是否在我们的单词列表里。为了提高查找和定位的速度,通常都画出与单词列表所对应的单词查找树,其特点如下:1.根结点不包含字母,除根结点外每一个结点都仅包含一个大写英文字母;2.从根结点到某一结点,路径上经过的字母依次连起来所构成的字母序列,称为该结点对应的单词。单词列表中的每个单词,都是该单词查找树某个结点所对应的单词;3.在满..._noi 2000 单词查找树 oj

随便推点

正在写pytorch cpp前端的同行博客_py前端-程序员宅基地

文章浏览阅读175次。https://oldpan.me/别人说的深度学习圣经 https://www.jeremyjordan.me/ 找个时间一起填了_py前端

C#与Halcon联合(9)自制多功能halcon窗体_c#联合halcon做模板区域掩膜功能-程序员宅基地

文章浏览阅读5.3k次,点赞4次,收藏68次。目录1.窗体功能2.使用windows窗体控件库,制作该halcon多功能窗体3.效果展示--图像与操作说明4.如何使用自制的halcon多功能窗体5.视频展示链接与测试代码链接1.窗体功能①按比例显示图像,不填充拉伸②可鼠标拖动图像移动,滚轮缩放③可显示图像十字叉辅助显示④可以显示鼠标位于窗体位置的坐标及图像灰度值⑤可以保存当前窗体中显示的图像⑥可以对窗体进行截图⑦可以插入halcon窗体中的obj 与 msg ,并显示2.使用windows窗体控件库,制作该halcon多功能窗体①_c#联合halcon做模板区域掩膜功能

计算机二级选择题考word基础知识吗,计算机二级选择题难吗 ms office考试内容-程序员宅基地

文章浏览阅读150次。计算机二级是不少小伙伴们比较头疼的一门考试,也是一门比较重要的考试。有没有比较好的复习技巧呢?计算机二级选择题难吗?做题技巧是什么?计算机二级选择题难吗计算机二级选择题不难首先需要准备一个有计算机二级知识点的文档或者是书。文档的话一般百度一下就有,要是觉得网上的不太全面也可以通过某宝获取详细的资料。安装一个名为计算机二级宝典的APP。这个APP有点像考驾照必用的驾考宝典,关于里面二级的题很全面并且..._计算机二级选择题难吗

【平衡小车制作】(一)硬件原理图讲解(超详解)-程序员宅基地

文章浏览阅读5.5w次,点赞186次,收藏1.3k次。  大家好,之后的一系列文章我将介绍我玩平衡小车的过程以及遇到的一些问题,将这些内容记录下来分享给大家,也让大家少走一些弯路。接下来我将从硬件框架选择、软件编程、PID算法、PID调参这四个部分向大家讲解平衡小车的制作过程。  本系列平衡小车文章适合于刚刚学习STM32但感觉没有玩透,想找个项目练练手,那么平衡小车绝对适合你。同时也适合于对于平衡小车感兴趣但编程和硬件基础较差,没有关系,跟随作者将让你轻松学会制作一个属于自己的平衡小车。  _硬件原理图

pytorch中加入注意力机制(CBAM)以ResNet为例_cbam源码-程序员宅基地

文章浏览阅读2.3w次,点赞48次,收藏402次。源码位置:初识CV:ResNet_CBAM源码​第一步:找到ResNet源代码在里面添加通道注意力机制和空间注意力机制所需库import torch.nn as nnimport mathtry: from torch.hub import load_state_dict_from_urlexcept ImportError: from torch.ut..._cbam源码

大数据组件之图数据库JanusGraph图文介绍-程序员宅基地

文章浏览阅读1k次,点赞15次,收藏21次。JanusGraph是一个开源的分布式图数据库。2017年,JanusGraph发布0.1.0 版本,目前(截止2024-03)最新版本为1.0.0。JanusGraph是基于Apache基金会下的一个开源的图数据库与图计算框架Tinkerpop来开发的。采用的图数据模型是“属性图模型”,即图数据包含顶点和边;顶点可以有属性和标签;边有标签和方向,并总是有一个开始节点和结束节点。每个JanusGraph图都有一个Schema,该Schema由其中使用的边标签,属性键和顶点标签组成。边标签。_janusgraph

推荐文章

热门文章

相关标签