异常
软件运行过程中的各种意料之外叫做Exception,比如要读取的文件找不到,准备联网发现没网,等着int参数来了个String
注意Error和exception不一样,error错的比较猛,一般是直接把JAVA整个搞崩了,比如内存空间异常等等,JVM只好终止线程,这一般都是大错了。Exception一般是程序员脑子暂时抽风了,修修改改程序还能救回来。
举例,method a 和b互相无限调用对方,陷入无限循环。运行程序,观察系统输出什么。
package exception;
public class Demo01 {
public static void main(String[] args) {
new Demo01().a();
}
public void a(){
b();
}
public void b(){
a();
}
}
运行结果:
观察到系统输出了一句StackOverflowError这意思是stack内空间耗尽了,因为我们运行了一个没有终止条件的无限循环(记住做循环或者递归,一定要有可触发的程序终止条件)。
Exception in thread "main" java.lang.StackOverflowError
at exception.Demo01.a(Demo01.java:8)
at exception.Demo01.b(Demo01.java:11)
at exception.Demo01.a(Demo01.java:8)
at exception.Demo01.b(Demo01.java:11)
N行循环
再举一个例子,除法分母是0,这在数学计算上本身就是不允许的。观察程序运行结果。
Demo01.java
package exception;
public class Demo01 {
public static void main(String[] args) {
System.out.println(6 / 0);
}
}
运行结果:
观察到程序出了关键字"ArithmeticException: / by zero"这句的意思就是,数学错误: 除以零。通过观察输出的字样,程序员可以快速找到程序出错的位置。
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.Demo01.main(Demo01.java:5)
Process finished with exit code 1
异常处理的思维
Java异常处理是Java编程语言中用于处理程序中可能出现的错误或异常情况的一种机制。它提供了一种结构化的方式来响应和处理运行时错误,使程序能够在遇到问题时相对优雅的输出有效提示,而不是程序直接崩溃。
JAVA把异常当object处理,定义一个java.lang.Throwable 作为所有异常class的超类class
JAVA API已经提前定义了许多异常class,分为两大类:错误Error和异常Exception
如上图,错误error class:JAVA直接崩了,表示JVM无法处理或恢复的严重问题
- 比如stack满了,compile一般检查不到,一般与"无限递归忘记写终止条件"或者"设置了一堆超大局部变量"或者"写了一大堆method互相调用"把stack空间占满了
- 虚拟机运行错误 Virtual MachineError 比如当JVM不再有继续执行操作所需的内存资源,会报错OutOfMemoryError。
- JVM运行时报错,比如 定义错误NoClassDefFoundError,链接错误LinkageError
如上图,Exception class一般还能救,一般都是程序员自己的锅:
Exception下分为unchecked exceptions 和 checked exception。
非检查异常unchecked exceptions:在compile阶段不需要catch或声明。并且是RuntimeException(运行时异常类)或它的子类,详细列出如下
- RuntimeException(运行时异常)
-
- ArrayIndexOutOfBoundsException数组下标越界
-
- NullPointerException空指针异常
-
- ArithmeticException算术异常
-
- MissingResourceException丢失资源
-
- ClassNotFoundException找不到类
-
- 等等
检查异常checked exception :这类异常必须在方法签名中使用throws关键字声明,或者在方法体内部使用try-catch块进行处理。这意味着编译器要求这些method要么有程序块处理这些异常(使用try-catch块),要么在方法签名中使用throws关键字声明它们(等着这个class的父类try-catch)。比如程序想开文件 文件找不到了。检查异常类一般是指Exception下的IOException类或其子类。
Exception class存在的意义----让系统跑程序时候别崩溃,抛出一下这有个XXException,JVM不崩溃。
异常处理机制:抛出异常+捕获异常
五个关键字
try catch finally throw throws(注意throw和throws完全不一样)
try-catch
try-catch一般是成对使用。try块用于包含可能会抛出异常的代码。当try块中的代码抛出异常时,控制流将立即转移到与之匹配的catch块。catch块用于捕获并处理特定类型的异常。你可以有多个catch块来处理不同类型的异常。
在非常个别情况下,也有try-finally模块一起用,一般是文件调用,网络连接,数据库连接的method结束时被释放的情况下。
比如下面的例子,
try块内int r= m/n;
是可能会抛出异常的代码,当异常被捕捉到,立刻转到catch块,如果直接碰到throw语句,那么后续代码就不看了,因为因为throw语句会立即结束当前方法的执行并将异常传递给调用者。如果throw旁边还有代码(包括System.out.println这种语句),建议写在catch块内throw关键字前面。
try块内可以写多个可能会抛出异常的代码,但一次只会触发一个,触发了一个就直接走catch模块了。
catch模块可以存在多个,是按顺序catch的,不要把大的父类放在第一个catch,那样后面的catch模组都不触发了。
package exception.demo02;
public class mytest {
public static void main(String[] args) {
int m = 10;
int n = 0;
try {
int r= m/n;
} catch (ArrayIndexOutOfBoundsException e) {System.out.println("发现ArrayIndexOutOfBounds报错");}
catch (ArithmeticException e){System.out.println("发现Arithmetic报错");}
catch (RuntimeException e){System.out.println("发现Runtime报错");}
}
}
运行结果
发现Arithmetic报错
throw关键字
在Java中,throw是一个关键字,不是method。当你想在代码中抛出一个异常时,你使用throw关键字并跟随一个异常object。这个异常对象可以是Java标准库中的异常类的一个实例object,也可以是你自定义的异常类的一个实例object。
一旦异常被抛出,程序的正常执行流程将被中断,并且控制权将转移到最近的异常处理代码,通常是catch块。
简单而言 throw后面跟着一个object,是继承于系统(或者自定义)Throwable class的object(见上图分类),触发后立刻终止程序,跳到最近的(或父级们)能处理该异常的catch模块,直到被处理或程序中止。
比如这句throw e;
用在catch模块里,直接throw了异常object e,那么运行后我们会直接获得系统默认输出的这类Exception提示信息。这个提示或许不够清晰明确,有时程序员会将throw e;
换成类似这样写 throw new ArithmeticException("啊啊啊你为什么要除以零");
,用来输出自己想要的Exception提示信息。
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.demo02.mytest.main(mytest.java:8)
整体代码见下面的例子:
先用简单的throw e;
package exception.demo02;
public class mytest {
public static void main(String[] args) {
int m = 10;
int n = 0;
try {
int r= m/n;
} catch (ArrayIndexOutOfBoundsException e) {System.out.println("发现ArrayIndexOutOfBounds报错"); throw e;}
catch (ArithmeticException e){System.out.println("发现Arithmetic报错");throw e;}
catch (RuntimeException e){System.out.println("发现Runtime报错");throw e;}
}
}
运行结果
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.demo02.mytest.main(mytest.java:8)
发现Arithmetic报错
再将throw后面换成新建object new Exception_class("用户自定义提示信息") ;
package exception.demo02;
public class mytest {
public static void main(String[] args) {
int m = 10;
int n = 0;
try {
int r= m/n;
} catch (ArrayIndexOutOfBoundsException e) {System.out.println("发现ArrayIndexOutOfBounds报错"); throw new ArrayIndexOutOfBoundsException("数组下标啊大哥大姐");}
catch (ArithmeticException e){System.out.println("发现Arithmetic报错");throw new ArithmeticException("你除数是零啊!牛逼");}
catch (RuntimeException e){System.out.println("发现Runtime报错");throw new RuntimeException("我点炒蘑菇没让你从种蘑菇开始啊");}
}
}
程序运行结果
发现Arithmetic报错
Exception in thread "main" java.lang.ArithmeticException: 你除数是零啊!牛逼
at exception.demo02.mytest.main(mytest.java:10)
Process finished with exit code 1
printStackTrace()
除了在catch模块里直接throw异常object e使用语句throw e;
有时程序员也选择使用printStackTrace();
printStackTrace()是Java中的一个异常处理方法,用于在控制台或日志文件中打印异常的堆栈跟踪信息。通过调用printStackTrace()方法,可以将这些Exception信息打印到控制台中。
package exception.demo02;
public class mytest {
public static void main(String[] args) {
int m = 10;
int n = 0;
try {
my_div(m,n);
}
catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace();}
catch (ArithmeticException e){e.printStackTrace();}
catch (RuntimeException e){e.printStackTrace();}
}
public static int my_div(int a, int b) throws ArithmeticException {
return a/b;
}
}
运行结果:
java.lang.ArithmeticException: / by zero
at exception.demo02.mytest.my_div(mytest.java:21)
at exception.demo02.mytest.main(mytest.java:8)
Process finished with exit code 0
throws
Java允许程序员在method的声明中使用throws关键字来声明可能会抛出的异常。throws的位置写在method名称那行的最后面+可能出现的Exception种类,可写多个exception种类,用逗号隔开),比如public void doSomething() throws IOException,RuntimeException,ArithmeticException
如果一个方法的声明中使用了throws关键字来声明可能会抛出的异常,那么调用这个方法的代码不一定需要使用try-catch块来捕获这个异常。有几种处理异常的方式:
- 使用try-catch块:这是最常见的处理方式。调用者可以使用try-catch块来捕获并处理可能抛出的异常。
- 使用finally块:finally块可以用于在try块之后执行一些清理代码,无论是否发生异常都会执行。但请注意,finally块不能用来处理异常,它仅仅是用来执行清理任务。
以下是伪处理方式:(实际没有真的处理异常,而是丢给了别人或者直接摆烂)
- 继续向上抛出异常:如果调用者不想在当前方法中处理异常,它可以选择不捕获该异常,这样异常会继续向上抛出,直到被某个上层方法捕获或者最终由JVM处理(通常会导致程序终止)。
- 声明方法也抛出异常:如果调用者是一个方法,并且它也不想处理异常,那么它可以在自己的方法签名中使用throws关键字来声明同样的异常。这样,异常会继续向上传递,直到最终有某个方法处理了它。
- 忽略异常:在某些特殊情况下,调用者可能会选择忽略异常。这是一种非常不推荐的做法,因为它可能会隐藏问题,导致程序在后续执行中出现不可预测的行为。
下面这个例子中,my_div method声明了它可能会抛出ArithmeticException,RuntimeException,于是在main模块try-catch程序对这些exception统统进行了涵盖,完成了异常(们)的处理。
package exception.demo02;
public class mytest {
public static void main(String[] args) {
int m = 10;
int n = 0;
try {
my_div(m,n);
}
catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace();}
catch (ArithmeticException e){e.printStackTrace();}
catch (RuntimeException e){e.printStackTrace();}
}
public static int my_div(int a, int b) throws ArithmeticException, RuntimeException{
return a/b;
}
}
java.lang.ArithmeticException: / by zero
at exception.demo02.mytest.my_div(mytest.java:21)
at exception.demo02.mytest.main(mytest.java:8)
Process finished with exit code 0
throws和throw关键字经常一起使用来处理异常,但它们也可以单独使用。可以只使用throws,表明方法不处理某些异常,让调用者去处理。也可以只使用throw,在方法内部遇到错误时直接抛出异常,并不在方法签名中声明任何异常。
下面这个例子,doSomething方法声明了它可能会抛出IOException,但方法内部没有使用throw关键字来实际抛出异常。调用这个方法的代码需要处理或继续传递这个异常。
public class Example {
public void doSomething() throws IOException {
// 这里可能会抛出IOException
FileInputStream fis = new FileInputStream("file.txt");
// ...
}
}
如果声明了可能抛出的异常,但层层调用它时最终到了main都没人处理这个异常(没人用try-catch模块处理),编译器会报错。
public class mytest {
public static void main(String[] args) {
//下面这个try-catch模块要是不存在,直接调用mymethod(),绝对报错!
try {
mymethod();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void mymethod() throws Exception {
throw new Exception("An exception occurred");
}
}
特殊的情况是,如果method声明了可能抛出的异常,层层调用它时最终到了main没人用try-catch模块处理,都在一层一层throws向上抛,main也摆烂直接用了throws向上抛,这种编译器不会报错。而是会在编译器输出Exception的具体内容。
下面这个例子就相当于层层向上抛,最后main也抛,编译器是不会报错的。
public class mytest {
public static void main(String[] args) throws Exception {
mymethod();
}
public static void mymethod() throws Exception {
throw new Exception("An exception occurred");
}
}
知识补充
快速生成try catch finally结构块:
IDEA有一个功能可以快速生成try catch finally结构块,这个功能叫做Surround with,快捷键是ctrl+alt+t
由于它和ubuntu open terminal重复了,我们可以用下面的方法,将快捷键更改,改为alt+c,下次使用时,将待检验模块高亮,然后alt+c,在里面选择 try catch finally即可。
Tips: IntelliJ IDEA 中如何更改快捷键设置?
- 打开 IntelliJ IDEA,在顶部菜单栏选择 "File",然后在下拉菜单中选择 "Settings"(对于 macOS 用户,选择 "IntelliJ IDEA" -> "Preferences")。
- 在弹出的 "Settings" 对话框中,在左侧的导航栏选择 "Keymap"。
- 在 "Keymap" 界面,你可以看到所有的快捷键设置。你可以通过搜索功能找到你想要更改的特定快捷键。在搜索框中输入你想要更改的功能名称,然后在下方的列表中找到它。
- 找到你想要更改的快捷键后,右键点击它,然后在弹出的菜单中选择 "Remove" 来删除原有的快捷键。
- 接着,右键点击你刚刚删除快捷键的功能,然后在弹出的菜单中选择 "Add Keyboard Shortcut"。
- 在弹出的 "Enter Keyboard Shortcut" 对话框中,按下你想要设置的新快捷键,然后点击 "OK"。
- 最后,点击 "Apply" 按钮应用更改,然后点击 "OK" 按钮关闭 "Settings" 对话框。
throw和throws关键字的用法:
助记
主动throw exception object inside method block using throw
主动throws (potential) exception class on method title using throws
举例,分母参数为零的情况下,将算式写完,用alt+c快捷键打开surround with选择try catch finally快速生成exception抛出模块
package exception;
public class test2 {
public static void main(String[] args) {
int a = 5;
int b = 0;
int c = 7;
try {
System.out.println(a+c/b);
} catch (Exception e) {
//e.printStackTrace();//做法1:打印错误的stack信息
//System.exit(0); //做法2:直接退出 里面的参数可以写0或者1
throw new RuntimeException(e); //做法3:直接抛出错误
} finally {
}
}
}
运行结果
Exception in thread "main" java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
at exception.test2.main(test2.java:13)
Caused by: java.lang.ArithmeticException: / by zero
at exception.test2.main(test2.java:9)