目录
Java异常类
Java中的异常体系
在Java中,异常类的父类为Throwable
类,在Throwable
下,存在两个子类:
Error
类:错误类,一般错误类中的错误都是致命错误,例如栈溢出错误StackOverflowError
Exception
类:异常类,一般是小型错误,例如数组越界ArrayIndexOutofBoundException
一般情况下Exception
类是编译时异常,但是其中有一个特殊的异常RuntimeException
,该异常为运行时异常,例如数组越界异常就属于RuntimeException
上面的体系可以参考下图:
抛出异常
在Java中,使用throw
关键字抛出异常对象,基本格式如下:
throw new 异常类(<可选打印信息>);
例如下面的代码:
public class Test {
public static void main(String[] args) {
String s = "test.txt";
method(s);
}
public static void method(String s) {
if(!s.endsWith(".txt")) {
throw new FileNotFoundException("文件不存在");
}
else {
System.out.println("文件存在");
}
}
}
处理异常
处理异常的两种方式
在Java中一共有两种处理异常的方式:
- 使用
throws
关键字抛出当前方法中的异常,如果一个方法有多个异常抛出,可以使用,
分隔不同的异常类,使用格式如下:
方法(形参列表) throws 异常类名1, 异常类2... {
// 方法体
throw new 异常对象名();
}
// 例如
public static void method(String s) throws FileNotFoundException {
if(!s.endsWith(".txt")) {
throw new FileNotFoundException("文件不存在");
}
else {
System.out.println("文件存在");
}
}
- 使用
try...catch
捕获异常,使用try
包裹可能出现异常的语句,catch
捕获异常对象,catch
语句可以不止一个,基本格式如下:
try{
// 可能出现异常的语句
}
catch(异常类 异常对象名)
{
// 处理语句
}
// 可以有多个catch语句
例如下面的代码:
public class Test {
public static void main(String[] args) {
String s = "test.txt1";
try {
method(s);
} catch (FileNotFoundException e) {
System.out.println(e);
// 也可以使用下面的方式打印完整信息
// e.printStackTrace();
}
}
public static void method(String s) throws FileNotFoundException {
if (!s.endsWith(".txt")) {
throw new FileNotFoundException("文件不存在");
} else {
System.out.println("文件存在");
}
}
}
在处理异常时,如果抛出的异常类是另一个异常类的子类,那么可以使用throws
向上抛出对应父类异常类,例如上面的FileNotFoundException
属于父类IOException
,所以可以抛出IOException
或者Exception
同样,对于try...catch
语句也是如此
public class Test {
public static void main(String[] args) {
String s = "test.txt1";
try {
method(s);
} catch (Exception e) {
System.out.println(e);
}
}
public static void method(String s) throws IOException {
if (!s.endsWith(".txt")) {
throw new FileNotFoundException("文件不存在");
} else {
System.out.println("文件存在");
}
}
}
如果可能出现的异常语句在try
中确实出现了异常,则当前try
中出现异常的语句之后的语句不会再执行,否则正常执行,例如下面的代码:
public class Test {
public static void main(String[] args) {
String s = "test.txt1";
try {
method(s);
System.out.println("出现异常时我不会出现");
} catch (FileNotFoundException e) {
System.out.println(e);
}
}
public static void method(String s) throws IOException {
if (!s.endsWith(".txt")) {
throw new FileNotFoundException("文件不存在");
} else {
System.out.println("文件存在");
}
}
}
输出结果:
java.io.FileNotFoundException: 文件不存在
try...catch
和throws
的区别
使用throws
处理异常时,第一个出现异常的位置开始抛出异常,此时该方法不再执行剩余的代码,将异常向方法调用处抛,如果方法调用处依旧没有处理异常,则继续向上抛出,以此类推,如果最后一个调用处依旧没有处理,则此时JVM就会报错,打印对应的异常信息
使用try...catch
处理异常时,如果catch
可以捕获到try
中的异常,则代表异常被正常处理,此时其他的catch
将不会被执行(即从上而下依次匹配直到遇到合适的),但是不可以将父类异常放在子类异常前,否则编译报错,当正常处理完异常后,try...catch
后面的语句将正常执行
finally
关键字
finally
关键字用于一定要执行的代码,放在最后一个catch
之后,有了finally
后,不论是否出现了异常,都会走finally
中的语句,除非在可以捕获到对应异常的catch
语句中使用了System.exit(0)
(结束JVM虚拟机运行),例如下面的代码:
public class Test {
public static void main(String[] args) {
String s = "test.txt1";
try {
method(s);
System.out.println("出现异常时我不会出现");
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("必须要执行我");
}
}
public static void method(String s) throws IOException {
if (!s.endsWith(".txt")) {
throw new FileNotFoundException("文件不存在");
} else {
System.out.println("文件存在");
}
}
}
输出结果:
java.io.FileNotFoundException: 文件不存在
at com.epsda.advanced.test_exception.Test.method(Test.java:30)
at com.epsda.advanced.test_exception.Test.main(Test.java:18)
必须要执行我
// 如果加了System.exit(0)
public class Test {
public static void main(String[] args) {
String s = "test.txt1";
try {
method(s);
System.out.println("出现异常时我不会出现");
} catch (IOException e) {
e.printStackTrace();
System.exit(0); 使用 System.exit(0) 退出程序,此时finally也不会执行
} finally {
System.out.println("必须要执行我");
}
}
public static void method(String s) throws IOException {
if (!s.endsWith(".txt")) {
throw new FileNotFoundException("文件不存在");
} else {
System.out.println("文件存在");
}
}
}
输出结果:
java.io.FileNotFoundException: 文件不存在
at com.epsda.advanced.test_exception.Test.method(Test.java:30)
at com.epsda.advanced.test_exception.Test.main(Test.java:18)
一般使用finally
在关闭资源时,因为部分情况下GC(垃圾回收器)无法回收堆内存中的文件,从而无法释放内存,此时需要在finally
中手动关闭资源
抛出异常注意事项
- 如果父类方法已经抛出了异常,子类重写父类对应的方法就可以不再抛出异常
- 如果父类方法没有抛出异常,子类重写父类对应的方法就不要抛出异常
例如下面的代码:
// 父类方法不抛出异常,但子类对应的重写方法抛出异常
public class Test1 {
public static void main(String[] args) {
}
class A {
public void method() {
}
}
class B extends A {
@Override
public void method() throws Exception{
}
}
}
输出结果:
overridden method does not throw java.lang.Exception
自定义异常类
如果想要自定义一个异常类,则自定义的类就必须继承自Exception
,否则编译器不会将其当作异常类,格式如下:
public class 自定义异常类名 extends Exception {
// 内容
}
例如下面的代码:
// 自定义异常类
public class LoginFailException extends Exception{
}
// 测试
public class Test2 {
public static void main(String[] args) {
String name = "admin";
String password = "123456";
try {
login(name, password);
} catch (LoginFailException e) {
e.printStackTrace();
}
}
public static void login(String name, String password) throws LoginFailException{
if(!name.equals("admin") || !password.equals("123456")){
throw new LoginFailException();
}
}
}
如果想添加自定义异常信息,需要提供一个有参构造函数,该构造函数使用一个String
类型的message
构造对象,此时会调用父类的构造函数,因为Exception
类中存在对应的构造函数
// 自定义异常类
public class LoginFailException extends Exception{
public LoginFailException() {
}
public LoginFailException(String message) {
super(message);
}
}
// 测试
public class Test2 {
public static void main(String[] args) {
String name = "admin";
String password = "123456";
try {
login(name, password);
} catch (LoginFailException e) {
e.printStackTrace();
}
}
public static void login(String name, String password) throws LoginFailException{
if(!name.equals("admin") || !password.equals("123456")){
throw new LoginFailException("登录失败");
}
}
}