异常处理

异常

计算机运行程序的时候,总会出现各种各样的错误,有一些错误是用户造成的.

// 假设用户输入了abc:
String s = "abc";
int n = Integer.parseInt(s); 	// NumberFormatException!

还有一些错误是随机出现的,并且无法避免.比如:

网络突然断了,连接不到远程服务器;
内存耗尽,程序崩溃了;
用户点“打印”,但根本没有打印机;

所以,一个健壮的程序必须具有处理各种错误的能力.

在java中提供了一套异常处理机制,用异常来表示错误.异常也是一种类.异常可以在任何地方抛出,但只需要在上层捕获.

异常类的继承关系如下:

异常处理_第1张图片
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
    }
}

最后的结果为:

异常处理_第2张图片

这是一个栈结构, 满足后进先出的规律,所以依次调用

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不是原始的异常:

异常处理_第3张图片

异常屏蔽

在执行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的,但是不能抛出异常:

在这里插入图片描述

你可能感兴趣的:(java学习笔记)