在很多场景都有异常处理,python、C++、java中都存在,但是一直不理解异常处理的一些深层逻辑,在这里简要学习一下

Reference

什么是异常处理

  • 简单来说就是尽可能避免程序不正常运行的技术

我们可以自己写一个简单的swtich来判断接受的正确或错误的错误码,这个错误码通常是编程语言和API固定的

int code = processFile("C:\\test.txt");
if (code == 0) {
// ok:
} else {
// error:
switch (code) {
case 1:
// file not found:
case 2:
// no read permission:
default:
// unknown error:
}
}

但是上面这个判断有个问题,如果程序本身有严重错误,或者不可抗力的中断,处理起来就很困难

所有基本上每个语言都有自己的异常处理机制

try {
String s = processFile(“C:\\test.txt”);
// ok:
} catch (FileNotFoundException e) {
// file not found:
} catch (SecurityException e) {
// no read permission:
} catch (IOException e) {
// io error:
} catch (Exception e) {
// other error:
}

在廖雪峰的网站上可以看到一个异常体系图,Java的异常是class,很多异常存在继承关系,并且都继承自object

错误总体上分为:

  • Error表示严重的错误
  • Exception则是运行时的错误

举例分析

拿廖雪峰的例子分析一下

// try...catch
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(); // 尝试使用用默认编码
}
}
}

但是如果我们不捕获的话,会在toGBK()中提示你做异常处理,可以试一试

import java.sql.*;
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) {
return s.getBytes("GBK"); //没有捕获异常
}
}

输出为:java: 未报告的异常错误java.io.UnsupportedEncodingException; 必须对其进行捕获或声明以便抛出

但是这种机制是如何建立的?

编译器是如何知道需要写一个异常的?

这是因为String.getBytes(String)方法定义是:

public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
...
}

**在方法定义的时候,使用throws Xxx表示该方法可能抛出的异常类型。**调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。也就是说,自己的写的方法如果包含了throws,如果你在调用时没有做异常处理,那么同样也会有编译器的提醒

那么修改为如下的代码

// try...catch
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) throws UnsupportedEncodingException {
return s.getBytes("GBK");
}
}

此时编译也是不通过的,异常处理的提醒就是在main函数中,因为toGBK定义了throws 异常UnsupportedEncodingException,所有调用者都应该用try…catch…捕获这个异常

正确的修改如下:

// try...catch
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

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");
}
}

下面这个结论很重要:

只要是方法声明的Checked Exception,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在main()方法中捕获,不会出现漏写try的情况。这是由编译器保证的。main()方法也是最后捕获Exception的机会。

日常技巧

偷懒可以这样写

public class Main {
public static void main(String[] args) throws Exception { // 在main函数抛出
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}

static byte[] toGBK(String s) throws UnsupportedEncodingException {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
}
}

代价就是一旦发生异常,程序会立刻退出。

另一种就是,不知道出现异常做什么的时候,用异常栈来记录

static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 先记下来再说:
e.printStackTrace();
}
return null;

所有异常都可以调用printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法。