Java异常是用于表示和处理程序故障的库类型和语言功能。 如果您想了解失败在源代码中是如何表示的,那么您来对地方了。 除了对Java异常的概述之外,我还将带您开始使用Java的语言功能来抛出对象,尝试可能失败的代码,捕获抛出的对象以及在抛出异常之后清理Java代码。
在本教程的前半部分,您将学习Java 1.0以来的基本语言功能和库类型。 在下半年,您将发现在最新Java版本中引入的高级功能。
请注意,本教程中的代码示例与JDK 12兼容。
什么是Java异常?
当Java程序的正常行为被意外行为中断时,就会发生故障。 这种差异被称为例外 。 例如,程序尝试打开文件以读取其内容,但是该文件不存在。 Java将异常分为几种类型,因此让我们考虑每种类型。
检查异常
Java将由外部因素引起的异常(例如文件丢失)分类为检查异常 。 Java编译器检查此类异常是否在发生的地方进行了处理 (更正),或记录为在其他地方进行了处理。
异常处理程序
异常处理程序是处理异常的代码序列。 它询问上下文(这意味着它从异常发生时范围内的变量中读取保存的值),然后使用所学的知识将Java程序还原为正常行为流程。 例如,异常处理程序可能会读取保存的文件名并提示用户替换丢失的文件。
运行时(未经检查)异常
假设程序尝试将整数除以整数0。这种可能性说明了另一种异常,即运行时异常 。 与检查的异常不同,运行时异常通常源于编写得不好的源代码,因此应由程序员修复。 由于编译器不会检查运行时异常是否已被处理或记录为可在其他地方处理,因此您可以将运行时异常视为未检查的异常 。
关于运行时异常
您可以修改程序以处理运行时异常,但是最好修复源代码。 运行时异常通常是由于将无效参数传递给库的方法而引起的。 越野车的调用代码应该是固定的。
失误
一些异常非常严重,因为它们危及程序继续执行的能力。 例如,一个程序尝试从JVM分配内存,但是没有足够的可用内存来满足请求。 当程序尝试通过Class.forName()
方法调用加载类文件,但该类文件已损坏时,会发生另一种严重情况。 这种异常称为错误 。 您永远不要尝试自己处理错误,因为JVM可能无法从中恢复。
源代码中的异常
异常可以在源代码中表示为错误代码或对象 。 我将同时介绍两者,并向您展示为什么对象是高级的。
错误代码与对象
诸如C之类的编程语言使用基于整数的错误代码来表示故障和故障原因(即异常)。 以下是几个示例:
if (chdir("C:\\temp"))
printf("Unable to change to temp directory: %d\n", errno);
FILE *fp = fopen("C:\\temp\\foo");
if (fp == NULL)
printf("Unable to open foo: %d\n", errno);
C的chdir()
(更改目录)函数返回一个整数:成功时为0或失败时为-1。 同样,C的fopen()
(文件打开)函数在成功时将非空指针 (整数地址)返回到FILE
结构,而在失败时将返回空(0)指针(由常量NULL
表示)。 在这两种情况下,要确定导致故障的异常,必须阅读全局errno
变量的基于整数的错误代码。
错误代码存在一些问题:
- 整数是没有意义的。 他们没有描述他们代表的例外。 例如6是什么意思?
- 将上下文与错误代码关联起来很尴尬。 例如,您可能想输出无法打开的文件的名称,但是要在哪里存储文件的名称?
- 整数是任意的,在读取源代码时可能导致混淆。 例如,指定
if (!chdir("C:\\temp"))
!
不表示),而不是if (chdir("C:\\temp"))
测试失败更清晰。 但是,选择0表示成功,因此必须指定if (chdir("C:\\temp"))
来测试失败。 - 错误代码太容易忽略,可能导致错误代码。 例如,程序员可以指定
chdir("C:\\temp");
并忽略if (fp == NULL)
检查。 此外,程序员无需检查errno
。 通过不测试失败,该程序在任何一个函数返回失败指示符时都会出现异常行为。
为了解决这些问题,Java引入了一种新的异常处理方法。 在Java中,我们将描述异常的对象与基于抛出和捕获这些对象的机制结合在一起。 使用对象与错误代码表示异常的一些优点如下:
- 可以使用具有有意义名称的类创建对象。 例如,
FileNotFoundException
(在java.io
包中)比6更有意义。 - 对象可以将上下文存储在各个字段中。 例如,您可以存储一条消息,无法打开的文件的名称,解析操作失败的最新位置和/或对象字段中的其他项。
- 您不使用
if
语句测试失败。 而是将异常对象抛出给与程序代码分开的处理程序。 结果,源代码更易于阅读,而且不会出现bug。
Throwable及其子类
Java提供了代表不同种类异常的类的层次结构。 这些类以及Exception
, RuntimeException
和Error
子类都植于java.lang
包的Throwable
类中。
Throwable
是涉及异常的最终超类。 仅可以抛出(并随后捕获)从Throwable
及其子类创建的对象。 这样的对象被称为throwables 。
Throwable
对象与描述异常的详细消息关联。 提供了几个构造器,包括下面描述的一对,以创建带有或不带有详细消息的Throwable
对象:
- Throwable()创建一个没有详细消息的
Throwable
。 此构造函数适用于没有上下文的情况。 例如,您只想知道堆栈为空或已满。 - Throwable(String message)创建一个
Throwable
并将message
作为详细消息。 该消息可以输出给用户和/或记录。
Throwable
提供String getMessage()
方法以返回详细消息。 它还提供了其他有用的方法,我将在后面介绍。
异常类
Throwable
具有两个直接子类。 这些子类之一是Exception
,它描述了由外部因素(例如尝试从不存在的文件中读取)引起的异常。 Exception
声明与Throwable
相同的构造函数(具有相同的参数列表),并且每个构造函数均调用其Throwable
对应对象。 Exception
继承了Throwable
的方法; 它没有声明任何新方法。
Java提供了许多直接将Exception
子类化的Exception
类。 这是三个示例:
- CloneNotSupportedException表示尝试克隆其类未实现
Cloneable
接口的对象。 两种类型都在java.lang
包中。 - IOException表示发生了某种I / O故障。 此类型位于
java.io
包中。 - ParseException表示在解析文本时发生了故障。 可以在
java.text
包中找到此类型。
请注意,每个Exception
字子类名结束Exception
。 此约定使确定类的目的变得容易。
通常,您将使用自己的异常类(其名称应以Exception
结尾)对Exception
(或其子类之一)进行子类化。 这是几个自定义子类示例:
public class StackFullException extends Exception
{
}
public class EmptyDirectoryException extends Exception
{
private String directoryName;
public EmptyDirectoryException(String message, String directoryName)
{
super(message);
this.directoryName = directoryName;
}
public String getDirectoryName()
{
return directoryName;
}
}
第一个示例描述了不需要详细消息的异常类。 它是默认的noargument构造函数调用Exception()
,后者调用Throwable()
。
第二个示例描述了一个异常类,该类的构造函数需要一条详细消息以及空目录的名称。 构造函数调用Exception(String message)
,后者调用Throwable(String message)
。
从Exception
或其子类之一( RuntimeException
或其子类之一除外)实例化的对象为检查异常。
RuntimeException类
Exception
是RuntimeException
直接子类, RuntimeException
描述了最有可能是由于编写不良的代码而引起的异常。 RuntimeException
声明与Exception
相同的构造函数(具有相同的参数列表),并且每个构造函数均调用其Exception
。 RuntimeException
继承Throwable
的方法。 它没有声明任何新方法。
Java提供了许多直接将RuntimeException
子类RuntimeException
异常类。 以下示例是java.lang
包的所有成员:
- ArithmeticException发出非法算术运算信号,例如尝试将整数除以0。
- IllegalArgumentException表示已将非法或不适当的参数传递给方法。
- NullPointerException表示尝试通过空引用调用方法或访问实例字段。
从RuntimeException
或其子类之一实例化的对象是未经检查的异常 。
错误类
Throwable
的另一个直接子类是Error
,它描述了一个严重的(甚至是异常的)问题,合理的应用程序不应尝试处理该问题,例如内存不足,JVM堆栈溢出或尝试加载不能被处理的类。找到了。 像Exception
一样, Error
声明与Throwable
相同的构造函数,继承Throwable
的方法,并且不声明其自己的任何方法。
您可以从约定它们的类名以Error
结尾的约定中识别Error
子类。 示例包括OutOfMemoryError
, LinkageError
和StackOverflowError
。 所有这三种类型都属于java.lang
包。
抛出异常
AC库函数通过将全局errno
变量设置为错误代码并返回失败代码来通知调用代码异常。 相反,Java方法抛出一个对象。 知道如何以及何时抛出异常是有效的Java编程的重要方面。 引发异常涉及两个基本步骤:
- 使用
throw
语句引发异常对象。 - 使用
throws
子句通知编译器。
后面的部分将重点介绍捕获异常并在异常发生后进行清理,但首先让我们了解有关可抛出对象的更多信息。
抛出语句
Java提供了throw
语句来引发描述异常的对象。 这是throw
语句的语法:
throw throwable ;
throwable
标识的对象是Throwable
或其任何子类的实例。 但是,通常只抛出从Exception
或RuntimeException
子类实例化的对象。 以下是几个示例:
throw new FileNotFoundException("unable to find file " + filename);
throw new IllegalArgumentException("argument passed to count is less than zero");
throwable从当前方法抛出到JVM,JVM会检查此方法是否合适。 如果未找到,则JVM展开方法调用堆栈,寻找可以处理throwable描述的异常的最近调用方法。 如果找到此方法,则将throwable传递给该方法的处理程序,该处理程序的代码将执行以处理异常。 如果未找到处理异常的方法,则JVM终止并显示一条适当的消息。
throws子句
当您从方法中抛出已检查的异常时,需要通知编译器。 为此,可以在方法的标题后面附加throws
子句。 此子句具有以下语法:
throws checkedExceptionClassName (, checkedExceptionClassName )*
throws
子句由关键字throws
组成,后跟一个逗号分隔的列表,列出了从方法抛出的检查异常的类名。 这是一个例子:
public static void main(String[] args) throws ClassNotFoundException
{
if (args.length != 1)
{
System.err.println("usage: java ... classfile");
return;
}
Class.forName(args[0]);
}
本示例尝试加载由命令行参数标识的类文件。 如果Class.forName()
找不到类文件,则会抛出java.lang.ClassNotFoundException
对象,这是一个已检查的异常。
已检查的异常争议
throws
子句和已检查的异常是有争议的。 许多开发人员讨厌被迫指定throws
或处理已检查的异常。 从我的文章中了解有关此内容的更多信息。 博客文章。
翻译自: https://www.infoworld.com/article/3269036/exceptions-in-java-part-1-exception-handling-basics.html