第十二章
12.1 引言
异常是Java中的类,构造一个异常就是在构造一个对象
12.2 异常处理概述
异常是从方法抛出的。方法的调用者可以捕获以及处理该异常。
异常可以将检测错误和处理错误分离开来,当方法检测到错误时,抛出(throw)异常并传递给调用者进行处理(catch)。
浮点数除以0不会产生异常。是因为一个正/负数除以0会得到一个无穷/小的结果,整数没有定义无穷大/小,而浮点数定义了无穷大/小,所以浮点数除以0的结果是无穷大/小
12.3 异常类型
其中,Error类和RuntimeException类以及他们的子类,被称为免检异常,Java不强制要求编写代码捕获或声明他们
而其他所有的异常被称为必检异常,编译器会强制要求程序员检查并通过try-catch处理他们,或者在方法头声明他们(更详细的内容在12.4)
12.4 关于异常处理的更多知识
12.4.1 声明异常
声明异常的语法如下
public void myMethod() throws Exception1, Exception2, ···
12.4.2 抛出异常
抛出异常的语法如下(以illegalArgumentException为例):
illegalArgumentException ex = new illegalArgumentException("Wrong Argument");
或者
throw new illegalArgumentException("Wrong Argument");
通常,Java API中的异常(也就是12.3章节的图示)至少有2个构造方法,1个无参构造方法和1个带有异常消息(exception message)的构造方法,这个异常消息可以用getMessage()获取
12.4.3 捕获异常
捕获异常的语法如下
try {
} catch (Exception1 ex) {
}
12.4.4 从异常中获取倍息
Java.lang.Throwable类中提供了很多实例方法,获取有关异常的信息。
12.4.5 理解如下(结合了廖雪峰老师官网的Java讲义)
在Java中,当前执行的语句必属于某个方法
关于必检异常的抛出
,声明
和捕获
,一共只有2点需要记忆:
1.
方法要拋出的必检异常都必须在方法头中使用throws和逗号显式声明
也就是说:一个方法,只要在方法体抛出
了异常1;就必须在方法头声明
异常1
2.
如果方法头声明了某个异常,那么调用方在调用时必须捕获他们
也就是说:一个方法,只要在方法头声明
异常1,调用方就必须捕获
异常1
举个例子 + 例外情况分析:
例如,方法getBytes()声明了一个必检异常
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
...
}
2.1 那么,getBytes()方法的调用方toGBK()必须捕获这个异常(这个调用方可以是main方法,也可以是其他的更低一级的方法,且不限于储存在哪一个类中)
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) {
//toGBK()方法调用了getBytes()方法
//理想情况下,toGBK()方法使用try-catch捕获getBytes()声明的异常
try {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:
System.out.println(e); // 打印异常信息
return s.getBytes(); // 尝试使用用默认编码
}
}
}
2.2 如果当前的调用方toGBK()没有捕获这个异常,就必须在更高的调用层捕获(main方法是最高的调用层)
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
//在更高的调用层main进行捕获
try {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
} catch (UnsupportedEncodingException e) {
System.out.println(e);
}
}
static byte[] toGBK(String s) throws UnsupportedEncodingException {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
}
}
2.3 如果不想写try-catch statement,可以直接在main方法处throws Exception,声明了可能抛出的所有的异常(或者某一类别的异常)
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Main {
//直接声明异常
public static void main(String[] args) throws Exception {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) throws UnsupportedEncodingException {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
}
}
12.5 finally子句
无论异常是否产生,finally 子句总是会被执行的。语法如下
try {
statements;
}
catch (TheException ex) {
handling ex;
}
finally {
finalStatements;
}
某些情况下,可以没有catch
,只使用try ... finally
结构。例如:
void process(String file) throws IOException {
try {
...
} finally {
System.out.println("END");
}
}
因为方法声明了可能抛出的异常,所以可以不写catch
。
12.6 何时使用异常
异常处理将错误处理代码(catch)从正常的程序设计任务中(try)分离出来,这样,可以使程序更易读 、更易修改。
但是,异常处理也会占用更多的时间和计算机资源。不要把异常处理当作简单的逻辑测试。
12.7 重新抛出异常
如果异常处理器不能处理一个异常,或者只是简单地希望它的调用者注意到该 异常,Java允许该异常处理器重新抛出异常。
12.8 链式异常
和其他异常一起抛出一个异常 ,构成了链式异常。
12.9创建自定义异常类
可以通过继承Exception或它的子类来自定义一个新的异常类,但是最好不要继承RuntimeException,只继承必检异常,这样编译器就可以强制要求程序员捕获他们
Java 提供相当多的异常类,尽量首先使用它们而不要创建自己的异常类。
12.10 File类
在程序运行时得到的数据是暂时的,程序终止运行也就意味着数据的丢失。为了将数据永久保存,可以把这些数据储存在文件中,这些文件今后就可以被其他程序访问。
对于File类,可以获取、重命名和删除文件或目录,
构建一个File实例并不会真的在计算机上创建一个文件,也不可以对文件进行输入/输出。
12.11 文件输入和输出
12.11.1 使用PrintWriter写入数据
java.io.PrintWriter 类可用来创建一个文件并向文本文件写人数据。但是一定要close(),不然会导致数据不能正确的保存
12.11.2 使用 try-with-resources 自动关闭资源
程序猿经常会忘记close(),为了避免错误,简化代码,可以使用try-with-resources自动调用close()
12.11.3 使用 Scanner 读数据
可以使用Scanner从文件中读取数据
12.11.4 Scanner 如何工作
读取用分隔符分隔开的标记。默认情况下,分隔符是空格。
对于nextInt()和nextDouble()方法,首先,他们会从当前的输入缓冲区开始,忽略所有的空格和换行符,直到遇到一个非空格非换行符的字符。
其次,开始记录接下来所有的字符,直到再次遇到空格或换行符时停止,并返回所有收集到的字符(只返回字符,不返回空格或换行符)
对于nextLine()方法,首先,他们会从当前的输入缓冲区开始,记录并返回所有的内容(包括空格和换行符),直到遇到换行符时停止,也就是读取当前行的全部剩余部分
注意,读取完成后,这个方法会将读取指针转入新的下一行
12.12 从Web上读取数据
使用URL类可以读取网页上文件的内容
12.13 示例学习:Web爬虫
开发了一个程序可以基于第一个网页的URL,爬取其后相关联的100个URL
本章拓展:
从Java 7开始,引入了java.nio.file
包,其中包含了一组新的文件和目录操作类,用于替代java.io.File
类。
总的来说,java.nio.file
包提供了更现代和强大的文件和目录操作功能,更加符合当今的编程需求。它提供了更丰富的API,更好的异常处理和性能优势,建议在新的Java项目中使用java.nio.file
包进行文件和目录操作。