ZB-025-01异常入门和控制流

异常

  • 在 return 语句之外,为方法提供另外一种出口
  • IOException 通常代表“预期之内的异常”

有毒的 IOException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
private static void readFile(File file) throws IOException {
InputStream inputStream = new FileInputStream(file);
while(true){
int b = inputStream.read();
if(b == -1){
break;
}
System.out.print((char) b);
}
}

private static void 同事A() throws IOException {
readFile(new File("my-file"));
}

private static void 同事B() throws IOException {
readFile(null);
}
}

由于一个方法readFile丢出了一个异常,就使得所有使用该方法的人全部被传染了,被迫都要加上这个 throws IOException

这个烦人的东西 源头是什么

1
2
3
4
// 这里面声明了 throws 
public FileInputStream(File file) throws FileNotFoundException {
...
}

结论就是:任何声明了 throws XXXException 的方法,再被调用的时候都必须显示的声明 throws XXX XXXException 或者 trg/catch

万能的解决方案

1
2
3
4
5
6
7
private static void 同事B(){
try {
readFile(null);
} catch (IOException e) {
e.printStackTrace();
}
}

Exception 是干嘛的?

对应任何一个函数都有入口和出口

但是 异常 给程序提供了另一种出口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}

// 这里给程序提供了另一种出口
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}

举个例子

  • 假设文件不存在你去调用 readFile 就会丢出一个RuntimeException异常
  • 它会击穿方法栈里所有的东西,一直向上抛,抛到有人处理它为止
1
2
3
4
5
6
7
8
9
10
11
12
13
private static void readFile(File file) throws IOException {
if(file == null){
throw new RuntimeException();
}
InputStream inputStream = new FileInputStream(file);
while(true){
int b = inputStream.read();
if(b == -1){
break;
}
System.out.print((char) b);
}
}

异常:异常击穿栈帧过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Main {

public static void main(String[] args) {
a();
}

private static void a(){
b();
}

private static void b(){
c();
}

private static void c(){
d();
}

private static void d(){
throw new RuntimeException();
}
}

/*
调用 main => a => b ==> c ==> d
此时 d 抛出一个异常,导致异常击穿当前方法,使得当前方法退出
击穿 c ==> 击穿 b ==> 击穿 a ==> 抛给 main
击穿所有栈帧,直接结束掉
*/

java的异常体系

处理异常

  • a方法处 try/catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {

public static void main(String[] args) {
a();
}

private static void a(){
try{
b();
}catch (Exception e){
e.printStackTrace();
}
}

private static void b(){
c();
}

private static void c(){
d();
}

private static void d(){
throw new RuntimeException();
}
}


/*
调用 main => a => b ==> c ==> d
此时 d 抛出一个异常,导致异常击穿当前方法,使得当前方法退出
异常丢到 c,c没有处理异常被击穿
==>
异常丢到 b,b没有处理异常被击穿
==>
异常丢到 a,a 里面有 try/catch 尝试捕获异常,
抓到后变成了 e ==> e.printStackTrace();

这就是 java的异常处理
*/

Finally

无论程序正常/异常都执行的操作

  • 释放 数据库连接
  • 关闭 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Main {
public static void main(String[] args) {
System.out.println(a());
}

private static int a(){
try{
b();
return 0;
}catch (Exception e){
e.printStackTrace();
return 1;
}finally{
System.out.println(222);
}
}

private static void b(){
c();
}

private static void c(){
d();
}

private static void d(){
throw new RuntimeException();
}
}

/*
java.lang.RuntimeException
at com.io.demo.Main.d(Main.java:35)
at com.io.demo.Main.c(Main.java:31)
at com.io.demo.Main.b(Main.java:27)
at com.io.demo.Main.a(Main.java:16)
at com.io.demo.Main.main(Main.java:11)
222
1
*/

// 捕获异常
// 无论a结果如何 执行 finally里的语句
// 返回 1

不要在 finally里写 return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

public class Main {

public static void main(String[] args) {
// a();
System.out.println(a());
}

private static int a(){
try{
b();
return 0;
}catch (Exception e){
e.printStackTrace();
return 1;
}finally{
System.out.println(222);
return 3;
}
}

private static void b(){
c();
}

private static void c(){
d();
}

private static void d(){
throw new RuntimeException();
}
}

执行结果

/*
java.lang.RuntimeException
at com.io.demo.Main.d(Main.java:36)
at com.io.demo.Main.c(Main.java:32)
at com.io.demo.Main.b(Main.java:28)
at com.io.demo.Main.a(Main.java:16)
at com.io.demo.Main.main(Main.java:11)
222
3
*/
  • finally 里的 return 会打破原来的流程,取代 catch里的 return
  • finally 里 应该只执行资源的清理工作
  • finally 不是必要的,其实 catch 也不是必要的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 可以没有 catch
    private static int a(){
    try{
    b();
    return 0;
    }finally{
    System.out.println(222);
    return 3;
    }
    }

    // 不能只有 try ,报错
    private static int a(){
    try{
    b();
    return 0;
    }
    }

如果你catch里什么都不做,别人想杀了你

  • 一般来说,必须在 catch 里做处理
  • 不做处理,可能项目上线一周发现问题都很难排查,因为没有任何错误信息,这个错误信息被你吃了
1
2
3
4
5
6
7
8
9
10
11
private static void a(){
try{
b();
}catch (Exception e){
// 异常被你吃掉了~
// 异常被你吃掉了~
// 异常被你吃掉了~
}finally{
System.out.println(222);
}
}

你应该在 catch里这样

  • 处理异常
  • 在日志里打印异常
  • 返回对应的异常处理

file-leak-detector

它是用于针对那些粗心的开发人员在自己的程序里忘了写 close导致各种各样的闹鬼问题,用于检测这种情况。

try with resources (JDK7+)

语法糖

1
2
3
4
5
6
7
8
9
10
// 以前你要在 finally里 关闭资源
private static void XXX(Connection conn) throws SQLException {
PreparedStatement preparedStatement = null;
try{
preparedStatement = conn.prepareStatement("sql");
preparedStatement.executeQuery();
}finally {
preparedStatement.close();
}
}

try with resources改写后

1
2
3
4
5
6
7
8
9
private static void XXX(Connection conn) throws SQLException {
try (PreparedStatement preparedStatement = conn.prepareStatement("sql")) {
preparedStatement.executeQuery();
}
}

// 你在 try 的括号里 声明了一个语句
1. 你可以不加 finally ,java 会自动给你加 finally
2. 在 try离开的时候 把括号内的资源清理掉

问题来了,什么样的东西可以自动被清理掉呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// PreparedStatement 点进去看看
public interface PreparedStatement extends Statement {
...
}
// Statement 点进去看看
public interface Statement extends Wrapper, AutoCloseable {
...
}

// 应该就是它了
/**
* An object that may hold resources (such as file or socket handles)
* until it is closed. The {@link #close()} method of an {@code AutoCloseable}
* object is called automatically when exiting a {@code
* try}-with-resources block for which the object has been declared in
* the resource specification header. This construction ensures prompt
* release, avoiding resource exhaustion exceptions and errors that
* may otherwise occur.
*/
public interface AutoCloseable {
...
}

// 任何实现 AutoCloseable 的对象在离开 try() 语句块的时候,都将调用 close();

一个注意点

try with resource 的限制,只能在同一方法里使用。如果你的业务要求你 在某处打开文件,另一个地方关闭文件,它就做不到