计算机运行程序的时候,总会出现各种各样的错误,有一些错误是用户造成的.
// 假设用户输入了abc:
String s = "abc";
int n = Integer.parseInt(s); // NumberFormatException!
还有一些错误是随机出现的,并且无法避免.比如:
网络突然断了,连接不到远程服务器;
内存耗尽,程序崩溃了;
用户点“打印”,但根本没有打印机;
所以,一个健壮的程序必须具有处理各种错误的能力.
在java中提供了一套异常处理机制,用异常来表示错误.异常也是一种类.异常可以在任何地方抛出,但只需要在上层捕获.
异常类的继承关系如下:
Throwable是异常类的根,Throwable有两个体系:Error类和Exception类.
Error类表示严重的错误,程序一般毫无能力处理,比如内存耗尽无法加载某个类,栈溢出.
Exception类表示运行运行出错,它可以被捕获,然后处理.比如:
NumberFormatException:数值类型的格式错误
FileNotFoundException:未找到文件
SocketException:读取网络失败
Exception类又分为两大类:
RuntimeException以及它的子类;
非RuntimeException(包括IOException、ReflectiveOperationException等等)
java规定,必须捕获包括Exception及其子类,不包括RuntimeException及其子类,这种异常类称为Checked Exception类.
捕获异常使用try…catch语句,把可能发生异常的代码放到try{…}中,使用catch捕获对应的Exception及其子类(但不包括RuntimeException及其子类),这种类型的异常称为Checked Exception。
不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。
举例:
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) {
try {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:
System.out.println(e); // 打印异常信息
return s.getBytes(); // 尝试使用用默认编码
}
}
}
通过API查询,这个getBytes(String)方法的定义是:
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
...
}
因为UnsupportedEncodingException类是Checked Exception类,所以必须被捕获,否则就会报错.在toGBK(String)中,调用了s.getBytes(String)方法,就必须捕获UnsupportedEncodingException异常.也可以不在这个方法中捕获,但是必须在toGBK(String)方法前面抛出此类异常.但是这仅仅是处理了方法toGBK(String),Checked Exception类肯定要捕获的,最后调用方法的main函数必须要捕获异常.
例如:
import java.util.Arrays;
import java.io.UnsupportedEncodingException;
public class Main {
public static void main(String[] args) {
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");
}
}
将捕获异常放到了main中.可见,只要是方法声明的Checked Exception,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在main()方法中捕获,不会出现漏写try的情况。这是由编译器保证的。main()方法也是最后捕获Exception的机会。
main()也是方法,也可以抛出异常,在main()方法中抛出异常就说明在main()中不用捕获异常了,但是代价就是一旦发生异常,程序会立刻退出.
import java.util.Arrays;
import java.io.UnsupportedEncodingException;
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");
}
}
catch可以使用多次,多个catch只有一个可以执行,存在多个catch的时候,异常子类必须写在前面:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException e) {
System.out.println("IO error");
} catch (UnsupportedEncodingException e) { // 永远捕获不到,UnsupportedEncodingException异常类是IOException的子类,放在了后面,永远都无法捕获
System.out.println("Bad encoding");
}
}
正确的写法:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
}
}
无论是否有异常发生,都希望执行一些语句,作清理工作,使用finally作这些工作,无论是否发生异常,都会执行finally中的语句:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
} finally { // finally中的语句总是最后执行
System.out.println("END");
}
}
通过printStackTrace()方法打印异常栈.在try中无法实现,在异常中不做什么事情的话,应该将异常记录下来.
static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 先记下来再说:
e.printStackTrace();
}
return null;
}
某个方法抛出了异常,如果当前方法没有捕获异常,异常就会抛到上层调用方法,指导遇见某个try…catch被捕获为之.
用NumberFormatException异常类举例,这个异常类不属于Checked Exception,原则上不需要捕获,但不代表不能捕获.
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
process2();
}
static void process2() {
Integer.parseInt(null); // 会抛出NumberFormatException
}
}
最后的结果为:
这是一个栈结构, 满足后进先出的规律,所以依次调用
main()调用process1();
process1()调用process2();
process2()调用Integer.parseInt(String);
Integer.parseInt(String)调用Integer.parseInt(String, int)
当发生错误的时候,用户输入了非法字符,就可以抛出异常,用throw语句抛出,但是之前要创建某个Exception的实例,例如写一个抛出字符输入不正确的异常:
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
如果一个方法捕获了异常,有在catch()中抛出了新的异常,就相当于把异常转换:
void process1(String s) {
try {
process2(s); // 如果s为null,也会执行process2()方法,但是会抛出NullPointerException异常
} catch (NullPointerException e) { // 这条语句就会捕获异常NullPointerException
throw new IllegalArgumentException(); // 捕获异常之后又会抛出新的异常
}
}
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
查看打印的异常栈:
public class Main {
public static void main(String[] args) {
try {
process1(null);
}catch(Exception e) {
e.printStackTrace();
}
}
static void process1(String s) {
try {
process2(s);
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}
static void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zRh1UbGI-1590349721429)(2020-03-16_17-11.png)]
只会发现java.lang.IllegalArgumentException的异常信息,没有原始的NullPointerException异常信息.已经丢失了.
为了能追踪到原始的异常,在proess1捕获异常的是,将原始的NullPointerException对象传进新抛出异常的参数中;
public class Main {
public static void main(String[] args) {
try {
process1(null);
}catch(Exception e) {
e.printStackTrace();
}
}
static void process1(String s) {
try {
process2(s);
} catch (NullPointerException e) {
throw new IllegalArgumentException(e); // 传入NullPointerException 对象
}
}
static void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
}
结果会出现caused by…,说明捕获的IllegalArgumentException不是原始的异常:
在执行finally中的语句时抛出异常,原来在catch中抛出的异常不能被抛出,原因是因为只能抛出一个异常,
没有被抛出的异常被屏蔽了:
public class Main {
public static void main(String[] args) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
System.out.println("catched");
throw new RuntimeException(e);
} finally {
System.out.println("finally");
throw new IllegalArgumentException();
}
}
}
结果是执行catch的,但是不能抛出异常: