Java - 异常
异常(类)(Exception):
指程序运行过程中, 因为用户的误操作, 代码的 bug 等等一系列原因引起程序的崩溃
不是 Error, 异常是可以挽救的错误, 而 Error 是不可挽救的.
异常分类:
- 编译型异常: 在源码编译阶段时抛出, 这种异常必须处理, 不处理就无法编译成功
- 运行时异常: 编译阶段看不出有错误, 在运行时有可能出现的异常
异常类继承树:
从继承关系可知:
Throwable
是异常体系的根, 它继承自Object
.
Throwable
有两个体 系: Error
和, 表示严重的错误, 程序对此一般无能为力, 例如:
-
OutOfMemoryError
: 内存耗尽 -
NoClassDefFoundError
: 无法加载某个Class
-
StackOverflowError
: 栈溢出
而 Exception
则是运行时的错误, 它可以被捕获并处理.
某些异常是应用程序逻辑处理的一部分, 应该捕获并处理. 例如:
-
NumberFormatException
: 数值类型的格式错误 -
FileNotFoundException
: 未找到文件 -
SocketException
: 读取网络失败
还有一些异常是程序逻辑编写不对造成的, 应该修复程序本身. 例如:
-
NullPointerException
: 对某个 null 的对象调用方法或字段 -
IndexOutOfBoundException
: 数组索引越界
Exception
又分为两大类:
RuntimeException
以及它的子类;- 非
RuntimeException
(包括IOException
,ReflectiveOperationException
等等)
Java规定:
必须捕获的异常, 包括 Exception
及其子类, 但不包括 RuntimeException
及其子类, 这 种类型的异常为 Checked Exception
.
不需要捕获的异常, 包括 Error
及其子类, RuntimeException
及其子类.
异常处理:
解决掉异常的现象, 让程序继续运行
提高程序的容错能力, 也就提高了程序的稳定性
java 有一个 Exception
的对象处理异常
尽量不要直接用 Exception
类来处理, 应该先用 Exception
的子类, 最后再写一个 Exception
java进行异常处理, 有两种解决方案:
- 抓捕异常(重点)
- 抛出异常
throw和throws
throw
用在方法里, throws
用在方法上(处理编译阶段的异常)
class Test {
public static void test() {
throw new MyException("异常");
}
public static void test2() throws Exception {
}
}
抓捕异常:
针对于可能出现异常的代码, 进行抓捕
class Test {
public static void main(String[] args) {
try {
} catch (Exception e) {
// 如果出现异常, 代码会立刻进入catch中
// 出现异常的语句后面的代码不会执行, 会立即进入catch
// 在这里解决抓捕到的异常
// 只有出现了异常catch中的代码才会执行
} finally {
// 必须执行的代码
}
}
}
如果使用抓捕异常, 通过这种处理, 程序即便是遇到了, 也不崩溃
class Test {
public static void main(String[] args) {
int result = 0;
int num1;
int num2;
Scanner scan = new Scanner(System.in);
num1 = scan.nextInt();
num2 = scan.nextInt();
try {
System.out.println(1);
result = num1 / num2;
System.out.println(2);
} catch (Exception e) {
e.printStackTrace(); // 可以用此方法打印出异常
System.out.println(3);
}
System.out.println(4);
System.out.println(result);
}
}
捕获异常使用try...catch
语句, 把可能发生异常的代码放到try {...}
中, 然后使用 catch
捕获对应的 Exception
及其子类:
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(); // 尝试使用用默认编码
}
}
}
如果我们不捕获 UnsupportedEncodingException
, 会出现编译失败的问题.
编译器会报错, 错误信息类似: unreported exception UnsupportedEncodingException; must be caught or declared to be thrown, 并且准确地指出需要捕获的语句是
return s.getBytes("GBK")
; .
意思是说, 像 UnsupportedEncodingException
这样的 Checked Exception
, 必须被捕获.
这是因为 String.getBytes(String)
方法定义是:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc {
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
//...
}
}
在方法定义的时候, 使用 throws xxx 表示该方法可能抛出的异常类型. 调用方在调用的时候, 必须强制捕获这些异常, 否则编译器会报错.
在 toGBK()
方法中, 因为调用了 String.getBytes(String)
方法, 就必须捕获 .
我们也可以不捕获它, 而是在方法定义处用 throws 表示 toGBK() 方法可能会抛出 UnsupportedEncodingException
, 就可以让 toGBK()
方法通过编译器检查:
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");
}
}
上述代码仍然会得到编译错误, 但这一次, 编译器提示的不是调用return s.getBytes("GBK")
的问题, 而是 byte[] bs = toGBK("中文")
.
因为在 main()
方法中调用 toGBK()
, 没有捕获它声明的可能抛出的 UnsupportedEncodingException.
修饰方法是在main()方法中捕获异常并处理:
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 {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) throws UnsupportedEncodingException {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
}
}
因为 main()
方法声明了可能抛出 Exception
, 也就声明了可能抛出所有的 Exception
, 因此 在内部就无需捕获了. 代价就是一旦发生异常,
程序会立刻退出.
也可在内部消化
class Test {
static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 什么也不干
}
return null;
}
}
这种捕获后不处理的方式是非常不好的, 即使真的什么也做不了, 也要先把异常记录下来:
class Test {
static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 先记下来再说:
e.printStackTrace();
}
return null;
}
}
所有异常都可以调用 printStackTrace()
方法打印异常栈, 这是一个简单有用的快速打印异常的方法.
抛出异常:
一种消极处理
方法后面用 throws
关键字加上异常类, 把异常抛给调用者, 让调用者处理, 如果抛到main函数不解决再继续抛, 最后会抛给JVM处理
子类不能抛出比父类更多的异常
class Test {
public static void main() {
try {
createFile("Users/usus/a.txt");
} catch (IoException e) {
throw new IoExcption();
} catch (Exception e) {
throw new Exception();
}
}
public static void createFile(String path) throws IoException, Exception {
File file = new file(path);
file.createNewFile();
}
}
finally关键字:
finally
中的代码一定会执行, 即使 finally
之前有 return
也会执行 finally
中的代码, 因为 finally
是jvm级别而不是源码级别
class Test {
public static void main(String[] args) {
try {
// 可能存在异常的代码
} catch (Exception e) {
// 处理的方式
} finally {
// 这里的代码可以不用写
// 一旦这里写了代码, 不管try中有没有异常这里必须执行
}
}
}
finally中写什么:
- 回收垃圾
- 关闭IO流
- 关闭数据库链接
- ..........
- 类似的核心代码
finally特殊用法:
在开发中有一些代码必须执行但不知道会发生什么异常, 为了保证执行, 可以这么写:
class Test {
void test() {
try {
// 这里不写
} catch (Exception e) {
// 这里不写
} finally {
// 这里写重要代码
}
}
}
以下返回值是多少?
class Test {
public static int test() {
int a = 10;
int b = 20;
try {
b += a++;
return b;
} catch (Exception e) {
} finally {
b += 10;
a += 10;
}
return a;
}
}
// 这个函数的返回值是30
自定义异常类:
继承异常类, 再自定义
自定义异常可以向调用者传递信息
class MyException extends RuntimeException {
MyException(String s) {
super(s);
}
MyException() {
super();
}
}