-
- 1.1. 异常分类
- 1.2. 声明检查型异常
- 1.3. 抛出异常的情况
- 1.4. 如何抛出异常
- 1.5. 创建异常类
- 1.6. 捕获异常
- 1.7. 再次抛出异常与异常链
- 1.8. finally子句
- 1.9. try-with-Resources语句
- 1.10. 使用异常的技巧
1. 异常
异常对象都是派生于Throwable类的一个类实例
1.1. 异常分类
1 | graph LR |
- Error类层次结构描述了java运行时系统的内部错误和资源耗尽错误。你对此无能为力。
- Exception层次又分为两个分支:一个分支派生于RuntimeException;另一分支包含其他异常。
- 由编程错误导致的异常属于RuntimeException(数组越界,访问null指针,错误的强制类型转换)
- 如果程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常(如文件不存在,试图超越文件末尾继续读取数据等)
Java语言将派生于Error类或RuntimeException类的所有异常称为非检查型异常,所有的其他的异常称为检查型异常。
检查型异常编译期必须进行处理(捕获或者声明),否则程序不能通过编译;而非检查型异常可以通过编译(如数组越界)
1.2. 声明检查型异常
当调用了一个抛出检查型异常的方法时,需要在方法首部中添加throws子句,声明检查型异常
1 | class NyAnimation |
它指出这个方法可能抛出一个异常
不需要声明非检查型异常(虽然也可以声明)
1 | class NyAnimation |
总之,一个方法必须声明所有可能抛出的检查型异常,而不应该声明非检查型异常。
如果一个异常继承自Error或RuntimeException类,则无需处理,如果是继承自Exception类,则必须处理。
1.3. 抛出异常的情况
- 调用了一个抛出检查型异常的方法
- 检测到一个错误,并且利用throw语句抛出一个检查型异常
- 程序出现错误(如数组越界)
- java虚拟机或运行时库出现内部错误
1.4. 如何抛出异常
- 找到一个合适的异常类。(查文档)
- 创建这个类的一个对象。
- 将对象抛出。
1 | throw new 异常类();//如果是检查型异常还需声明或者放在try块中,否则不能通过编译 |
一旦方法抛出了异常,这个方法就不会返回调用者。
1.5. 创建异常类
当你的代码可能遇到任何标准异常类都无法描述清楚的问题时,可以创建自己的异常类。
可以定义一个派生于Exception类或其子类的异常类(为检查型异常),这个类应该包含两个构造器,一个是默认的构造器,一个是包含详细信息的构造器。
1 | public class ErrorTest extends Exception { |
现在可以抛出自定义的异常类了
1 | public class ExceptionDemo { |
其中带参构造方法能够输出信息
1 | if(true) throw new ErrorTest("但参构造"); |
1.6. 捕获异常
如果发生了某个异常,但没有在任何地方捕获这个异常,程序就会终止。
1 | public class ExceptionDemo { |
尽管在之后有一条输出语句,但是没有执行,因为程序发现有异常后就终止了,但是捕获了这个异常之后程序就不会终止。
1 | public class ExceptionDemo { |
要想捕获一个异常,需要用try catch语句块,如果try语句块中的任何代码抛出了catch子句中指定的一个异常类,那么
- 程序将跳过try语句块的其余代码
- 程序将执行catch子句中的处理器代码
请注意:
- 如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
- 如果方法中的任何代码抛出了catch子句中没有声明的一个异常类型,那么这个方法就会立即退出。
如果调用了一个抛出检查型异常的方法,就必须处理这个异常(try),或传递这个异常(throws)。
1.7. 再次抛出异常与异常链
可以在catch子句中继续抛出一个异常
1 | try{ |
还可以将原始异常设置为新异常的“原因”
1 | try { |
当捕获到这个异常时,可以使用下面这条语句获取原始异常
1 | .... |
使用这种包装技术可以在抛出高层异常时不会丢失原始异常
1.8. finally子句
不管异常有没有被捕获,finally子句中的代码都会执行
finally语句主要用于清理资源。
1.9. try-with-Resources语句
对于以下代码模式
1 | //open a resource |
如果资源属于一个实现了AutoCloseable接口的类,这个类中提供了这样的一个方法
1 | public interface AutoCloseable { |
那么就可以使用另一种快捷方式
1 | try(Resource res = ...){ |
try块自动退出时会调用res.close()来关闭资源。对于读取文件等资源流,就可以使用这种方式,在其完成后会自动关闭资源流
如果try块抛出一个异常,而close方法也抛出一个异常,则close方法的异常会被抑制。
1.10. 使用异常的技巧
- 异常不能代替简单的测试,只在异常情况下使用异常。
- 不要过分地细化异常,即不要将每一条语句都分装在一个独立的try块中,而是将正常处理和错误处理分开。
- 充分利用异常层次结构,不要只适用Throwable。
- 不要压制异常。
- 不要羞于传递异常。如果调用了一个抛出异常的方法,最好继续传递这个异常,而不是自己捕获。(throws)
2. 断言
断言机制允许在测试期间向代码中插入一些检查,而在生产代码中会自动删除这些检查。
使用关键字assert来使用断言,语法类似于if
1 | assert i>0; |
如果为真,继续执行下面的语句,如果为假则抛出一个AssertionError异常。
一般情况下断言是禁用的,需要用-ea去启动断言
3. 日志
平常我在进行调试时都会在代码中插入一些System.out.println语句来观察程序的行为,但是调试完后删起来很麻烦。因此可以用日志来代替,可以很容易地取消全部日志或者取消某个级别以下的日志。
3.1. 基本日志
要生成简单的日志记录,可以使用全局日志记录器并调用其info方法
1 | //生成基本日志 |
在适当的地方(如main的最前面)调用下面语句即可以取消所有的日志
1 | Logger.getGlobal().setLevel(Level.OFF); |
3.2. 日志级别
可以使用getLogger方法来创建或获取日志记录器:
1 | Logger logger = Logger.getLogger(LoggerDemo.class.getName()); |
通常有七个日志级别
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
在默认情况下只记录前三个级别。
也可以设置一个不同的级别
1 | logger.setLevel(Level.ALL);//所有的级别都会被记录 |
所有的级别都有日志记录方法
1 | logger.finest("message"); |
但是即使开启了所有的记录级别,仍然不会再控制台输出INFO以下级别的信息,此时需要修改配置信息或者构建一个新的控制台处理器
1 | Logger logger = Logger.getLogger(LoggerDemo.class.getName());//默认不输出INFO以下的日志,需要修改配置文件 |
3.3. 日志技巧
- 对于一个简单的应用,选择一个日志记录器。可以把日志记录器命名为与主应用包一样的名字。
- 默认的日志配置会把级别等于或高于INFO的所有消息记录的控制台。
高级日志有部分为略读