当前位置:   article > 正文

程序异常退出的原因_Java核心技术笔记分享------ 异常

程序异常退出是什么原因

在Java中,如果某个方法不能采用正常的途径完成它的任务,可以通过另一个途径退出方法。在这种情况下,方法并不会返回任何值,而是抛出(throw)了一个封装了错误信息的对象。需要注意的是,这个方法会立刻退出,并不返回正常值(或任何值)。此外,也不会从调用这个方法的代码继续执行,取而代之的是,异常处理机制开始搜索能够处理这种异常状况的异常处理器

异常有自己的语法和特定的继承层次结构。下面我们先介绍语法,然后再给出有效地使用这种语言特性地技巧。

一、异常的分类

在Java语言中,异常对象都是派生于Throwable类的一个类实例

我们来看一个大概的简化图:

cd29672a86350e8aee65e9f729815579.png

可以看出,所有异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception

1> Error类

Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。这种错误是java虚拟机jvm报出的。如果出现了这种内部错误,无法解决,只能终止程序。

2> Exception类

在设计Java程序时,要重点关注Exception层次结构。

2.1 RunTimeException

派生于RunTimeException:一般是由编译错误导致的异常(但是编译可以通过,既不用捕获也不用抛出,只是在运行时程序检测出,然后停止),常包括以下问题:

  • 错误的强制类型转换
  • 数组访问越界
  • 访问null指针

如果出现RunTimeException异常,那么一定就是你的问题

2.2 其他异常

不是派生于RunTimeException的异常属于其他异常,常包括以下问题:

  • 试图超越文件末尾来读取文件
  • 试图打开一个不存在的文件
  • 试图根据给定的字符串查找Class对象,而这个字符串表示的类并存在

2.3 检查型异常与非检查型异常

Java语言将派生于Error类或RunTimeException类的所有异常称为非检查型异常,所有的其他的异常称为检查型异常

编译器将检查你是否为所有的检查型异常提供了异常处理器

二 、声明检查型异常

1> 声明异常的规则

如果遇到了无法处理的情况,Java方法可以跑出一个异常。这个道理很简单:方法不仅需要告诉编译器将要返回什么值, 还要告诉编译器有可能发生什么错误

要在方法的首部指出这个方法可能抛出一个异常,所以要修改方法的首部,以反映这个方法可能抛出的检查型异常,例如:

public FileInputStream(String name) throws FileNotFoundException

当然也可以声明多个异常,例如:

  1. class MyAnimation{
  2. ...
  3. public Image loadImage(String s) throws FileNotFoundException, EOFException{
  4. ...
  5. }
  6. }

2> 继承关系中抛异常的规则

  1. 如果子类中重写了父类的中的一个方法,子类中声明的检查型异常不能比父类中声明的异常更通用(即,子类中声明的异常不能时父类中声明的异常的父类),由此可得,如果父类的方法没有声明异常,那么子类重写这个方法时,不能声明任何异常。

自己编写方法时,不必声明这个方法可能抛出的所有异常。至于什么时候需要在方法中使用throws子句声明异常,以及声明出哪些异常,我们需要记住下面的几点:

  • 调用了一个抛出检查型异常的方法。(一般,编译器如IDEA,eclipse等编译器)
  • 检测到一个错误,并利用throw语句抛出一个检查型异常
  • 如果类中的一个方法声明它会抛出一个异常,而这个异常是某个特定类的实例,那么这个方法抛出的异常可能属于这个类,也可能属于这个类的任意一个子类。

三、如何抛出异常

现在我们假设一个名为readDate( )的方法正在读取一个文件,文件首部包含以下信息,承诺文件长度为1024哥字符。(这里使用的例子是有关IO流的内容,不懂得可以先可以自己查API文档)

然而,读到了733个字符之后文件就结束了。这时候并不是一个正常的情况,需要抛出一个异常。阅读API文档以后,我们发现EOFException很合适,它的描述是:指示输入过程中意外遇到了EOF。

来看示例代码:

  1. String readDate(Scanner in) throws EOFException{
  2. ...
  3. while(...){
  4. if(!in.hasNext()){
  5. if(n < len)
  6. throw new EOFException();
  7. }
  8. }
  9. return s;
  10. }

我们来总结一下

  1. 抛出异常的标准规范语言:用关键词throw
throw new Exception();
  1. 如果一个已有的异常类能够满足你的要求,抛出这个异常非常容易。

在这个情况下:

  • 找到一个合适的异常类
  • 创建这个类的一个对象
  • 将对象抛出

四、创建异常类与描述异常

1> 创建异常类

有时候我们可能会遇到任何标准异常类都无法描述的问题。这种情况下,我们就需要创建自己的异常类了。

以下为创建异常类的标准步骤:

  • 自定义一个继承于Exception或者Exception任何子类的一个类
  • 创建一个默认的构造器
  • 创建一个包含描述信息的构造器(超类Throwable的toString( )方法会返回一个字符串,其中包含这个详细信息,这在调试中非常有用)

以下为示例代码:

  1. class FileFormatException extends IOException{
  2. public FileFormatException(){}
  3. public FileFormatException(String gripe){
  4. super(gripe);
  5. }
  6. }

2> 描述异常

我们来看示例代码:

  1. String readDate(BufferedReader in) throws FileFormatException{
  2. ...
  3. while(...){
  4. if(ch = -1){
  5. if(n < len){
  6. String description = "String not accepted long enough";
  7. throw new FileFromatException(description);
  8. }
  9. }
  10. }
  11. }

其中:

  1. String description = "String not accepted long enough";
  2. throw new FileFromatException(description);

就是在描述这个异常的信息

五、捕获异常

1> 捕获异常

5.1.1 trt/catch语句块

如果发生了某个异常,但没有在任何地方捕获这个异常,程序就会终止。所以我们就需要捕获异常。

要想捕获异常,我们就要使用try/catch语句块,如下所示:

  1. try{
  2. ...
  3. }catch(ExceptionType e){
  4. //handler for this type exception
  5. }

当然我们也可以捕获多个异常:

  1. try{
  2. ...
  3. }catch(ExceptionType e){
  4. //handler for this type exception
  5. }catch(ExceptionType e){
  6. //handler for this type exception
  7. }
  8. ...
  1. 如果try语句块中的任何代码抛出了catch子句中指定的一个异常类,那么
  2. 程序将跳过try语句块的其余代码
  3. 程序将执行catch子句中的处理器代码
  4. 如果try语句中的代码没有抛出任何异常,那么程序将跳过catch子句
  5. 如果方法中的任何代码抛出了catch子句中声明的一个异常类型,那么这个方法就会立刻退出。

5.1.2 抛异常与捕获异常

抛异常:即前面提的声明异常:throws

捕获异常:try/catch

那该什么时候抛异常,什么时候捕获异常呢?

请记住:

如果你知道该怎样处理这个异常,就去捕获他。如果你不知道怎样该处理这个异常,那就去先抛出他,让他的调用者来捕获

我们也得记住,最后在main( )方法中,我们必须捕获抛出的异常,如果继续抛异常,那么就会把这个异常交给虚拟机,就相当于了Error

3> 再次抛出异常与异常链(了解)

可以在catch子句中抛出一个异常。通常,希望改变异常的类型时会这样做。

这样很有道理,如果开发了一个供其他程序员使用的子系统,可以使用一个指示子系统故障的异常类型。

来看示例代码:

  1. try{
  2. //access the database
  3. }catch(SQLException e){
  4. throw new ServletException("database error" + e.getMessage());
  5. }

我们也可以把原始异常设置为新异常的“原因”:

  1. try{
  2. //access database
  3. }catch(SQLException original){
  4. var e = new ServletException("database Error");
  5. e.initCause(original);
  6. throw e;
  7. }

捕获到这个异常时,可以使用下面的这条语句获取原始异常:

Throwable original = caughtException.getCause();

4> finally 子句

代码在抛出一个异常时,就会停止处理这个方法剩余的代码,并退出这个方法。

但是,如果这个方法已经获得了只有它自己知道的一些本地资源,而且这些资源必须清理,这就会有问题。

finally子句可以解决这些问题:不管是否有异常被捕获,finally子句中的代码都会被执行。

我们来分析一个代码

如下所示:

  1. var in = new FileInputStream(...);
  2. try{
  3. //1
  4. //2
  5. }catch{
  6. //3
  7. //4
  8. }finally{
  9. //5
  10. in.close();
  11. }
  12. //6
  1. 当代码没有抛出异常。在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句中的代码。随后,继续执行finally子句之后的第一条语句。也就是说,执行的顺序是 1、2、5、6
  2. 代码抛出一个异常,并在一个catch子句中被捕获。在这种情况下,程序将执行try语句中的所有代码,直到抛出异常为止。此时,将跳过try语句中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally中的子句。
  3. 如果catch子句没有抛出异常,程序将执行finally子句之后的第一条子句。这种情况下执行的顺序是:1、3、4、5、6
  4. 如果catch子句抛出了一个异常,异常将被炮灰这个方法的调用者。执行的顺序只是:1、3、5
  5. 代码抛出了一个异常,但没有任何catch子句捕获这个异常。在这种情况下,程序将执行try语句中的所有语句,直到抛出异常为止。此时,将跳过try语句中的剩余代码,然后执行finally子句中的语句,并将异常抛回给这个方法的调用者。在这里执行的顺序是:1、5

5> try-with-Resources语句

try-with-Resources语句(带资源的try语句)的最简形式为:

  1. try(Resources res =...){
  2. //work with res
  3. }

try块退出时,会自动调用res.close( )

我们来看一个典型的例子:

这里要读取一个文件的所有单词

  1. try(var in = new Scanner(new FileInputStream("/xx/xxx/xx/xx"),StandardCharsets.UTF_8);
  2. var out = new PrintWriter("xxx.xxx",StandardCgarsets.UTF_8)
  3. ){
  4. while(in.hasNext())
  5. System,out.println(in.next());
  6. }

不论这个块如何退出,in和out都会关闭。就好像finally子句中调用了in 和 out 的关闭方法

如果try块抛出一个异常,而且close( )方法也抛出一个异常,这就会带来一个难题。try-with-Resources语句可以很好地处理这种情况。原来的异常会重新抛出,而close( )方法抛出的异常则会“被抑制”。这些异常将自动的捕获,并由addSuppressed( )方法,它会生成从close( )方法抛出,并被抑制的异常数组

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/108145
推荐阅读
相关标签
  

闽ICP备14008679号