本文是我们名为“ 高级Java ”的学院课程的一部分。
本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的过程! 在这里查看 !
目录
- 1.简介 2.异常以及何时使用它们 3.已检查和未检查的异常 4.使用尝试资源 5.异常和lambda 6.标准Java异常 7.定义自己的例外 8.记录例外 9.异常和记录 10.下一步是什么 11.下载源代码
1.简介
Java中的异常是在程序流中发出异常(或异常)情况信号的重要工具,可能会阻止其进一步发展。 从本质上讲,那些特殊情况可能是致命的(程序无法再运行,应该终止)或可恢复(程序可能会继续运行,尽管某些功能可能不可用)。
在本教程的这一部分中,我们将逐步介绍在Java中使用异常的典型场景,讨论已检查和未检查的异常,并探讨一些极端情况和有用的习惯用法。
2.异常以及何时使用它们
简而言之,异常是某种事件(或信号),它们在程序执行期间发生并中断常规执行流程。 导致引入异常的想法是对过去使用的错误代码和状态检查技术的替代。 从那时起,异常被广泛接受为处理包括Java在内的许多编程语言中的错误情况的标准方法。
只有一个与异常处理相关的重要规则(不仅在Java中):永远不要忽略它们! 每个异常都应至少记录一次(请参阅Exceptions和logging ),但永远不要忽略。 尽管如此,在某些罕见的情况下,可以安全地忽略异常,因为实际上可以做很多事情(请参阅示例中的使用try-with-resources部分)。
另外,在本教程的第6部分“ 如何有效地编写方法”中 ,我们讨论了参数验证和健全性检查。 异常是这些实践的关键部分:每种公共方法都应在进行任何实际工作之前验证所有必需的前提条件,如果某些条件未满足,则提出适当的例外条件。
3.已检查和未检查的异常
Java语言中的异常管理与其他编程语言不同。 这主要是因为Java中有两类异常: 选中的异常和未选中的异常。 有趣的是,这两个类在某种程度上是人为的,是由Java语言规则及其编译器强加的(但是JVM在它们之间没有区别)。
根据经验,未经检查的异常用于表示与程序逻辑和所做假设有关的错误条件(无效参数,空指针,不支持的操作,等等)。 任何未经检查的异常都是RuntimeException
的子类,这就是Java编译器如何理解特定异常属于未经检查的异常的类。
未经检查的异常不需要被调用方捕获,也不必被列为方法签名的一部分(使用throws关键字)。 NullPointerException
是未经检查的异常的最著名的成员,这是Java标准库中的声明:
public class NullPointerException extends RuntimeException {public NullPointerException() {super();}public NullPointerException(String s) {super(s);}
}
因此,检查的异常表示程序无法直接控制的区域(如内存,网络,文件系统等)中的无效条件。 任何检查到的异常都是Exception的子类。 与未检查的异常相反,已检查的异常必须被调用者捕获,或者被列为方法签名的一部分(使用throws
关键字)。 IOException
可能是检查异常中最知名的一种:
public class IOException extends Exception {public IOException() {super();}public IOException(String message) {super(message);}public IOException(String message, Throwable cause) {super(message, cause);}public IOException(Throwable cause) {super(cause);}
}
在当时,将已检查和未检查的异常分开听起来是个好主意,但是多年来,事实证明,它引入了更多样板代码,而不是解决实际问题的漂亮代码模式。 Java生态系统中出现的典型(不幸的是,非常麻烦)模式是将未检查的异常隐藏(或包装)在未检查的异常中,例如:
try {// Some I/O operation here
} catch( final IOException ex ) {throw new RuntimeException( "I/O operation failed", ex );
}
这不是最好的选择,但是如果精心设计自己的异常层次结构,可能会减少开发人员需要编写的样板代码数量。
值得一提的是,Java中还有另一类异常扩展了Error类(例如, OutOfMemoryError
或StackOverflowError
)。 这些异常通常表明致命的执行失败,导致无法立即从此类错误情况中恢复,从而导致程序立即终止。
4.使用尝试资源
抛出的任何异常都会导致一些所谓的堆栈展开和程序执行流程的更改。 结果是与未封闭的本机资源(例如文件句柄和网络套接字)有关的可能的资源泄漏。 Java中行为良好的典型I / O操作(直到版本7)需要使用强制性的finally
块来执行清理,并且通常看起来像这样:
public void readFile( final File file ) {InputStream in = null;try {in = new FileInputStream( file );// Some implementation here} catch( IOException ex ) {// Some implementation here} finally {if( in != null ) {try {in.close();} catch( final IOException ex ) {/* do nothing */}}}
}
尽管如此, finally
块看起来确实很丑陋(不幸的是,此处无法做太多事情,因为在输入流上调用close方法也可能导致IOException
异常),无论尝试关闭输入流(并释放背后的操作系统资源)发生了什么情况它)将被执行。 在“ 异常以及何时使用它们 ”一节中,我们强调了一个事实,即永远都不应忽略异常,但是,用close方法抛出的异常可以说是该规则的唯一排除项。
幸运的是,自Java 7以来,该语言引入了一种名为try-with-resources的新结构,该结构大大简化了整体资源管理。 这是上面使用try-with-resources重写的代码片段:
public void readFile( final File file ) {try( InputStream in = new FileInputStream( file ) ) {// Some implementation here} catch( final IOException ex ) {// Some implementation here}
}
为了在try-with-resources块中使用该资源,唯一需要拥有的就是接口AutoCloseable
。 在后台Java编译器将此构造扩展为更复杂的结构,但对开发人员而言,代码看起来非常易读和简洁。 请在适当的地方使用此非常方便的技术。
5.异常和lambda
在本教程的第3部分 , 如何设计类和接口中 ,我们已经讨论了Java 8的最新和最出色的功能,特别是lambda函数。 但是,我们尚未深入研究许多实际用例,并且例外是其中之一。
毫无疑问,未检查的异常可以按预期工作,但是Java的lambda函数语法不允许指定可能抛出的已检查的异常(除非这些异常由@FunctionalInterface
本身定义)。 以下代码段将不会以编译错误“未处理的异常类型IOException”进行编译(可能会在第03
行抛出):
public void readFile() {run( () -> {Files.readAllBytes( new File( "some.txt" ).toPath() );} );}public void run( final Runnable runnable ) {runnable.run();}
现在唯一的解决方案是在lambda函数体内捕获IOException
异常,然后重新抛出适当的RuntimeException
异常(不要忘记将原始异常作为原因传递),例如:
public void readFile() {run( () -> {try {Files.readAllBytes( new File( "some.txt" ).toPath() );} catch( final IOException ex ) {throw new RuntimeException( "Error reading file", ex );}} );
}
声明了许多功能接口,可以从其实现中引发任何异常,但是如果没有(如Runnable),则将检查的异常包装(或捕获)为非检查的异常是唯一的方法。
6.标准Java异常
Java标准库提供了大量关于异常的类,这些异常类被指定为覆盖程序执行期间发生的大多数通用错误。 下表列出了使用最广泛的应用程序,请在定义您自己的应用程序之前考虑它们。
例外类别 | 目的 |
NullPointerException | 在需要对象的情况下尝试使用null 。 |
IllegalArgumentException | 方法已传递了非法或不适当的参数。 |
IllegalStateException | 方法已在非法或不适当的时间被调用。 |
IndexOutOfBoundsException | 某种索引(例如,数组,字符串或向量)的索引超出范围。 |
UnsupportedOperationException | 不支持请求的操作。 |
ArrayIndexOutOfBoundsException | 已使用非法索引访问了数组。 |
ClassCastException | 代码已尝试将对象强制转换为不是实例的子类。 |
EnumConstantNotPresentException | 试图访问一个enum 的名称和常量enum 类型包含具有指定名称的常量( enums 一直在本教程中, 部分5 如何以及何时使用枚举和注解 )。 |
NumberFormatException | 尝试将字符串转换为数字类型之一,但是该字符串没有适当的格式。 |
StringIndexOutOfBoundsException | 索引为负或大于字符串的大小。 |
IOException | 发生了某种I / O异常。 此类是由失败或中断的I / O操作产生的异常的一般类别。 |
表1 –标准Java异常
7.定义自己的例外
Java语言使定义自己的异常类非常容易。 精心设计的异常层次结构允许实施详细且细粒度的错误条件管理和报告。 与往常一样,找到合适的平衡非常重要:太多的异常类会使开发复杂化,并浪费大量的代码来捕获异常或将异常传播到堆栈中。
强烈建议所有用户定义的异常都应继承自RuntimeException
类,并属于未经检查的异常类(但是,规则中始终存在排除项)。 例如,让我们定义异常以进行身份验证拨号:
public class NotAuthenticatedException extends RuntimeException {private static final long serialVersionUID = 2079235381336055509L;public NotAuthenticatedException() {super();}public NotAuthenticatedException( final String message ) {super( message );}public NotAuthenticatedException( final String message, final Throwable cause ) {super( message, cause );}
}
此异常的目的是在信号插入过程中发出有关不存在或无效的用户凭据的信号,例如:
public void signin( final String username, final String password ) {if( !exists( username, password ) ) {throw new NotAuthenticatedException("User / Password combination is not recognized" );}
}
将信息性消息与异常一起传递始终是一个好主意,因为它有助于对生产系统进行故障排除。 同样,如果异常是由于另一个特殊情况导致的,则应使用cause
构造函数参数保留初始异常。 这将有助于找出问题的真正根源。
8.记录例外
在本教程的第6部分“ 如何高效地编写方法”中 ,我们介绍了Java方法的正确文档。 在本节中,我们将花更多的时间讨论如何使异常成为文档的一部分。
如果方法作为其实现的一部分可能引发检查的异常,则它必须成为方法签名的一部分(使用throws
声明)。 Java文档工具分别具有@throws
标记,用于描述这些异常。 例如:
/*** Reads file from the file system.* @throws IOException if an I/O error occurs.*/
public void readFile() throws IOException {// Some implementation here
}
相反,正如我们从Checked和unchecked异常一节中所知道的那样,未经检查的异常通常不声明为方法签名的一部分。 但是,记录它们仍然是一个非常好的主意,因此方法的调用者将意识到可能引发的异常(使用相同的@throws
标记)。 例如:
/*** Parses the string representation of some concept.* @param str String to parse* @throws IllegalArgumentException if the specified string cannot be parsed properly* @throws NullPointerException if the specified string is null*/
public void parse( final String str ) {// Some implementation here
}
请始终记录您的方法可能引发的异常。 它将帮助其他开发人员从一开始就实施适当的异常处理和恢复(后备)逻辑,从而避免他们对生产系统中的问题进行故障排除。
9.异常和记录
日志记录( http://en.wikipedia.org/wiki/Logfile )是或多或少复杂的Java应用程序,库或框架的重要组成部分。 它是应用程序中发生的重要事件的日志,异常是此流程的关键部分。 在本教程的后面,我们可能会介绍Java标准库提供的日志子系统,但是请记住,以后应该正确记录和分析异常,以便发现应用程序中的问题并解决关键问题。
10.下一步是什么
在本教程的这一部分中,我们介绍了异常,这是Java语言的一个非常重要的功能。 我们已经看到异常是Java中错误管理的基础。 异常使处理和发信号通知错误的情况变得非常容易,并且与错误代码,标志和状态相反,一旦发生,则不能忽略异常。 在下一部分中,我们将讨论一个非常热门和复杂的主题:Java中的并发和多线程编程。
11.下载源代码
这是关于如何以及何时使用异常的课程。 您可以在此处下载源代码: advanced-java-part-8
翻译自: https://www.javacodegeeks.com/2015/09/how-and-when-to-use-exceptions.html