基础入门|你尽管try我尽量catch,二当家的带你搞明白java的异常机制
文章图片
前言 java的异常机制你都懂了么?二当家的一文带你深入浅出,理解透彻。
本文由 二当家的白帽子 https://le-yi.blog.csdn.net/ 博客原创,转载请注明来源,谢谢~
文章目录
- 前言
- 程序异常
- 运行期间的错误怎么管
- catch可以捕获哪些错误
- 如何发起一个异常
- 可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)
- 自定义异常
- try的姿势
- 什么情况需要用到finally块呢
- try-with-resources语法
- 尾声
程序异常 异常本质上是程序上的错误,错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误。
- 编译期间的错误:
通常都是语法错误,这个不是重点,毕竟运行不了的程序不会发生什么大事。
- 运行期间的错误:
有的错误程序没办法管,有的错误程序不应该管,还有的错误程序必须管。运行时的错误如果没有处理好,业务上是会出问题的哦,很可能是一发而不可收拾呢。
try-catch
语句处理运行期间的错误,用关键字try
去执行可能发生错误的代码,然后用关键字catch
去捕获程序运行期间发生的错误。public class Test {private static void printDiv(int dividend, int divisor) {System.out.println(dividend + " / " + divisor + " = " + (dividend / divisor));
}public static void main(String[] args) {printDiv(10, 5);
printDiv(10, 0);
printDiv(10, 2);
}
}
文章图片
上面的代码分别输出 10 / 5,10 / 0 和 10 / 2 的结果。但是运行结果显示,在执行 10 / 0 时程序就异常退出了,0是不能作为除数的。
接下来我们修改下代码,让我们的程序可以兼容异常情况。
public class Test {private static void printDiv(int dividend, int divisor) {try {System.out.println(dividend + " / " + divisor + " = " + (dividend / divisor));
} catch(ArithmeticException e) {System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
}
}public static void main(String[] args) {printDiv(10, 5);
printDiv(10, 0);
printDiv(10, 2);
}
}
文章图片
在使用
try-catch
语法处理了错误以后,可以看到,当程序执行到发生错误的代码行,就会跳转到catch
的块里继续执行,程序没有再异常退出了。catch可以捕获哪些错误
cache
捕获的都是Throwable
类和它的子类,Throwable
下有两个大类Error
和Exception
。其他的错误或异常都应该继承自他们俩。文章图片
-
Error
:
Error
是系统级别的错误,不该由应用程序捕获处理,应该交给系统或者框架来捕获处理。
-
Exception
:
Exception
是程序级别的错误,应该由程序模块去捕获处理。
throw
关键字。public class Test { private static void someMethod() {// 使用throw关键字抛出异常
throw new RuntimeException("运行中发生异常");
} public static void main(String[] args) {someMethod();
System.out.println("主方法执行完毕");
}
}
文章图片
我们也可以抛出
Error
和它的子类,但是通常我们应该抛出Exception
和它的子类。之前已经讲过他们的区别。public class Test { private static void someMethod() {// 使用throw关键字抛出错误
throw new Error("运行中发生错误");
} public static void main(String[] args) {someMethod();
System.out.println("主方法执行完毕");
}
}
文章图片
我们抛出另外一种异常,竟然编译就不通过了。
import java.sql.SQLException;
public class Test { private static void someMethod() {// 使用throw关键字抛出异常
throw new SQLException("运行中发生异常");
} public static void main(String[] args) {someMethod();
System.out.println("主方法执行完毕");
}
}
文章图片
可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)
文章图片
- 可查的异常(checked exceptions)
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。Java编译器会检查这种异常,当程序中可能出现这类异常,要么用try-catch
语句捕获它,要么用throws
子句声明抛出它,否则编译不会通过。
- 不可查的异常(unchecked exceptions)
不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。
try-catch
捕获,或者声明抛出,捕获我们已经会了,怎么声明抛出呢?可以使用throws
关键字。import java.sql.SQLException;
public class Test { private static void someMethod() throws SQLException {// 使用throw关键字抛出异常
throw new SQLException("运行中发生异常");
} public static void main(String[] args) throws SQLException {someMethod();
System.out.println("主方法执行完毕");
}
}
这样就可以编译通过了呢。
自定义异常 java已经内置了很多错误和异常类型,我就不罗列了,很容易查到,当内置的异常类型不能正确表达我们的错误时,我们也可以自定义错误和异常类型,只需要继承
Error
或者Exception
。public class Test { /**
* 自定义的异常类型
*/
static class MyRuntimeException extends RuntimeException {public MyRuntimeException(String message) {super(message);
}
} private static void someMethod() {// 使用throw关键字抛出异常
throw new MyRuntimeException("运行中发生异常");
} public static void main(String[] args) {someMethod();
System.out.println("主方法执行完毕");
}
}
try的姿势
try
的一般姿势有三种public class Test {public static void main(String[] args) {// 姿势一
try {// 有可能抛出异常的部分
} catch (Exception e) {// 对异常的处理
}// 姿势二
try {// 有可能抛出异常的部分
} finally {// 不管是否发生异常,都要执行的部分
}// 姿势三
try {// 有可能抛出异常的部分
} catch (Exception e) {// 对异常的处理
} finally {// 不管是否发生异常,都要执行的部分
}
}
}
上面的方式都是针对错误进行统一的处理,如果一段代码可能发生多种错误,我要针对某种错误进行特殊的处理怎么办?
import java.io.IOException;
public class Test {/**
* 将结果写入文件
* @param ret
* @throws IOException
*/
private static void writeFile(int ret) throws IOException {// 我假装将结果写入文件,其实没那么干,但是那是我的事,调用的人请保密
throw new IOException("磁盘满了啦");
}private static void printDiv(int dividend, int divisor) {try {int ret = dividend / divisor;
System.out.println(dividend + " / " + divisor + " = " + ret);
writeFile(ret);
} catch(Exception e) {if (e instanceof IOException) {System.out.println(dividend + " / " + divisor + " 发生IO错误:" + e.getMessage());
} else {System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
}
}
}public static void main(String[] args) {printDiv(10, 5);
printDiv(10, 0);
printDiv(10, 2);
}
}
文章图片
上面的代码达到了想要的效果,然而有点傻,其实java已经有语法直接处理这种情况。下面才是正确的方式哦,执行结果和上面的一样。
import java.io.IOException;
public class Test {/**
* 将结果写入文件
* @param ret
* @throws IOException
*/
private static void writeFile(int ret) throws IOException {// 我假装将结果写入文件,其实没那么干,但是那是我的事,调用的人请保密
throw new IOException("磁盘满了啦");
}private static void printDiv(int dividend, int divisor) {try {int ret = dividend / divisor;
System.out.println(dividend + " / " + divisor + " = " + ret);
writeFile(ret);
} catch (IOException e) {System.out.println(dividend + " / " + divisor + " 发生IO错误:" + e.getMessage());
} catch(Exception e) {System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
}
}public static void main(String[] args) {printDiv(10, 5);
printDiv(10, 0);
printDiv(10, 2);
}
}
有一点要注意,如果我们需要同时
catch
父子类异常,那么子类一定要先catch
,因为java是按照代码顺序匹配的,如果把父类的catch
块写在前面,那子类的catch
块就永远不会运行到。如果我要对发生的多种异常进行分组处理,其中几种用第一种处理方式,另外几种用第二种处理方式,其他的用第三种处理方式统一处理呢?我们当然可以
cache
多次,然后几个cache
块写一样的内容,但是那样依然很傻。下面才是正确的方式。import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.WriteAbortedException;
public class Test {/**
* 将结果写入文件
* @param ret
* @throws IOException
*/
private static void writeFile(int ret) throws FileNotFoundException, WriteAbortedException {// 我假装将结果写入文件,其实没那么干,但是那是我的事,调用的人请保密
int i = (int) (Math.random() * 3);
switch (i) {case 0:
throw new FileNotFoundException("文件不存在");
case 1:
throw new FileNotFoundException("文件写入被中断");
case 2:
throw new FileNotFoundException("磁盘满了啦");
}
}private static void printDiv(int dividend, int divisor) {try {int ret = dividend / divisor;
System.out.println(dividend + " / " + divisor + " = " + ret);
writeFile(ret);
} catch (FileNotFoundException | WriteAbortedException e) {System.out.println(dividend + " / " + divisor + " 文件写入发生错误:" + e.getMessage());
} catch (IOException e) {System.out.println(dividend + " / " + divisor + " 发生IO错误:" + e.getMessage());
} catch(Exception e) {System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
}
}public static void main(String[] args) {printDiv(10, 5);
printDiv(10, 0);
printDiv(10, 2);
}
}
文章图片
什么情况需要用到finally块呢 先看下面一段代码
import java.io.FileWriter;
import java.io.IOException;
public class Test { /**
* 写入文件
* @param text
* @throws IOException
*/
private static boolean writeFile(String text) throws IOException {try {System.out.println("申请使用资源");
FileWriter fw = new FileWriter("test.txt");
// 在这之后可能发生异常
fw.write(text);
if (true) {throw new RuntimeException("运行中发生的异常");
}
// 这里关闭流资源,但是可能在这之前发生异常
System.out.println("关闭资源");
fw.close();
return true;
} catch (Exception e) {System.out.println("处理异常:" + e.getMessage());
return false;
}
} public static void main(String[] args) throws IOException {writeFile("test");
}
}
文章图片
像IO流等系统资源,在从系统申请用完之后,需要手动释放,否则就会发生泄露。上面的代码,如果在关闭资源之前,发生了异常,程序就会跳到
catch
块继续执行,那关闭资源的代码就永远不会执行,直到程序退出。该怎么办呢?这时候就需要finally
了。import java.io.FileWriter;
import java.io.IOException;
public class Test { /**
* 写入文件
* @param text
* @throws IOException
*/
private static boolean writeFile(String text) throws IOException {FileWriter fw = null;
try {System.out.println("申请使用资源");
fw = new FileWriter("test.txt");
// 在这之后可能发生异常
fw.write(text);
if (true) {throw new RuntimeException("运行中发生的异常");
}
return true;
} catch (Exception e) {System.out.println("处理异常:" + e.getMessage());
return false;
} finally {if (fw != null) {System.out.println("关闭资源");
fw.close();
}
}
} public static void main(String[] args) throws IOException {writeFile("test");
}
}
文章图片
上面的代码做了修改,将关闭资源的代码放到了
finally
块内,无论try
是否发生异常,finally
内的代码都会执行。不光是关闭资源,任何无论是否发生异常都应该执行的逻辑都可以放在finally
块内。另外注意一点,
catch
块也可能发生异常,但是finally
块内的代码还是会执行。import java.io.FileWriter;
import java.io.IOException;
public class Test { /**
* 写入文件
* @param text
* @throws IOException
*/
private static boolean writeFile(String text) throws IOException {FileWriter fw = null;
try {System.out.println("申请使用资源");
fw = new FileWriter("test.txt");
// 在这之后可能发生异常
fw.write(text);
if (true) {throw new RuntimeException("运行中发生的异常");
}
return true;
} catch (Exception e) {System.out.println("处理异常:" + e.getMessage());
throw new RuntimeException("处理异常情况时发生异常");
//return false;
} finally {if (fw != null) {System.out.println("关闭资源");
fw.close();
}
}
} public static void main(String[] args) throws IOException {writeFile("test");
}
}
文章图片
但是如果
finally
块发生异常,后面的代码就不会继续执行了。import java.io.FileWriter;
import java.io.IOException;
public class Test { /**
* 写入文件
* @param text
* @throws IOException
*/
private static boolean writeFile(String text) throws IOException {FileWriter fw = null;
try {System.out.println("申请使用资源");
fw = new FileWriter("test.txt");
// 在这之后可能发生异常
fw.write(text);
if (true) {throw new RuntimeException("运行中发生的异常");
}
return true;
} catch (Exception e) {System.out.println("处理异常:" + e.getMessage());
return false;
} finally {if (true) {throw new RuntimeException("finally块发生异常");
}
if (fw != null) {System.out.println("关闭资源");
fw.close();
}
}
} public static void main(String[] args) throws IOException {writeFile("test");
}
}
文章图片
finally
块的代码通常是必须执行的,所以要catch
掉可能的异常,try-catch
语法是可以嵌套的哦。import java.io.FileWriter;
import java.io.IOException;
public class Test { /**
* 写入文件
* @param text
* @throws IOException
*/
private static boolean writeFile(String text) throws IOException {FileWriter fw = null;
try {System.out.println("申请使用资源");
fw = new FileWriter("test.txt");
// 在这之后可能发生异常
fw.write(text);
if (true) {throw new RuntimeException("运行中发生的异常");
}
return true;
} catch (Exception e) {System.out.println("处理异常:" + e.getMessage());
return false;
} finally {try {throw new RuntimeException("finally块发生异常");
} finally {if (fw != null) {System.out.println("关闭资源");
fw.close();
}
}
}
} public static void main(String[] args) throws IOException {writeFile("test");
}
}
文章图片
try-with-resources语法 像IO流这种有限系统资源的情况有很多,都大同小异,都是需要在最终使用完去手动调用关闭。每次都写模板一般的代码,有点傻,重复劳动,而且为了
finally
块和try
块都可以访问那个变量,就必须声明在try-catch
之外,这就导致finally
块后面的代码还可以访问那个变量,这是不合理的。所以后来java有了新的语法来处理这种情况。实现AutoCloseable
接口的类,都可以实现自动关闭。import java.io.IOException;
public class Test { static class SomeResource implements AutoCloseable {public void use() {System.out.println("使用资源");
}@Override
public void close() {System.out.println("关闭资源");
}
} /**
* 写入文件
* @param text
*/
private static boolean writeFile(String text) {try (SomeResource resource = new SomeResource();
) {resource.use();
// 在这之后可能发生异常
if (true) {throw new RuntimeException("运行中发生的异常");
}
return true;
}
} public static void main(String[] args) throws IOException {writeFile("test");
}
}
文章图片
可以看到上面的代码使用资源后,并没有手动调用关闭方法,关闭方法是被自动调用的。我们之前使用的FileWriter已经实现了
AutoCloseable
接口,所以也可以自动关闭了。可以在一个块里使用多个可自动关闭资源,顺序是初始化的逆序。
import java.io.IOException;
public class Test { static class SomeResource implements AutoCloseable {private final String name;
SomeResource(String name) {this.name = name;
System.out.println("初始化资源:" + name);
}public void use() {System.out.println("使用资源:" + name);
}@Override
public void close() {System.out.println("关闭资源:" + name);
}
} /**
* 写入文件
*
* @param text
*/
private static boolean writeFile(String text) {try (
SomeResource resource1 = new SomeResource("测试资源1");
SomeResource resource2 = new SomeResource("测试资源2");
SomeResource resource3 = new SomeResource("测试资源3");
) {resource2.use();
resource1.use();
resource3.use();
// 在这之后可能发生异常
if (true) {throw new RuntimeException("运行中发生的异常");
}
return true;
}
} public static void main(String[] args) throws IOException {writeFile("test");
}
}
文章图片
尾声 二当家的已经力求写的全面,如有不尽或者不准确的地方还请小伙伴们多见谅,欢迎评论区讨论。另外请赏个三连吧,多谢。
推荐阅读
- 热闹中的孤独
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 尽力
- 你到家了吗
- 爱就是希望你好好活着
- 为什么你的路演总会超时()
- 死结。
- 跌跌撞撞奔向你|跌跌撞撞奔向你 第四章(你补英语,我补物理)
- 奔向你的城市
- 喂,你结婚我给你随了个红包