异常的分类
Throwable 是 Java 语言中所有错误与异常的**超类**
。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()
等接口用于获取堆栈跟踪数据等信息。
Error(错误)
定义:**Error**
类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的**错误**
。
特点: 此类错误一般表示代码运行时 **JVM **
出现问题。通常有 Virtual MachineError(虚拟机运行 错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错 误。按照Java惯例,我们是不应该实现任何新的Error子类的!
Exception(异常)
Exception是一种异常。就如我们常见的**空指针异常**
或者是**1除以0**
的异常。主要是由于我们自身的代码引起的问题。
程序本身可以捕获并且可以处理的异常。Exception
这种异常又分为两类:CheckedException(编译时异常)和 RuntimeException(运行时异常)
CheckedException
检查时异常:
ClassNotFoundException
类找不到IOException
IO流异常ClassNotFoundException
NotSerializableException
序列化异常parseException
格式化异常
Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException
(没有找到指定的类异常),IOException
(IO流异常),要么通过**throws**
进行声明抛出,要么通过**trycatch**
进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。
必须要求我们在代码里进行处理,如果我们不处理CheckedException
的话,我们的应用程序是**无法通过编译阶段**
的。
public class TestException {
public void test1() {
System.out.println("test exception");
throw new Exception("this is a error");
}
}
就如上面代码,如果我们不在test1()
方法上显示抛出throws Exception
(将异常抛给调用方进行处理,不在本方法来处理),那么IDEA就会给我提示红色警告。这个就是不处理CheckedException
的结果。或者我们不将异常直接抛出去,而是选择在test1()
方法内进行try...catch
捕捉异常,这时候我们有两种方式来处理我们捕捉到的异常:
- 选择记录日志的方式处理异常问题
- 将异常信息直接返回回去
RuntimeException
运行时异常
NullPointerException
空指针异常ClassCaseException
类转换异常IndexOutOfBoundsException
数组越界ArrithmeticException
除0异常MissingResourceException
缺少资源错误IllegalArgumentException
非法参数异常ArrayIndexOutBoundException
数组下标越界异常ArithmeticExecption
算术异常
Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既”没有通过 **throws**
声明抛出它”,也”没有用**try-catch**
语句捕获它”,还是会编译通过。
一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行 捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情 况,则需要通过代码避免该情况的发生!
public class TestException {
public void test1() {
System.out.println("test exception");
throw new RuntimeException("this is a error");
}
}
将之前的代码throw new Exception("this is a error");
改为throw new RuntimeException("this is a error");
。我们会发现即使我们不在test1()
方法上使用throws Exception或者没有在test1()
方法内进行try...catch
捕捉异常,IDEA也不会给我们进行报错。甚至它还会提示throws是多余的。所以RuntimeException
是不强制我们处理异常的。
总结
**CheckedException**
算是一种分类来着吧,其实是Exception
. 检查型异常**RuntimeException**
是有一个实际类,它继承自Exception
. 运行时异常
- 检查型异常
这种类型的异常是难以避免的,所以是必须要进行**tr...catch**
进行处理的。编译器会帮助我们进行检查,一旦我们没有进行处理(throws或者try…catch)。编译器便会在编译前给我们提示报错。需要进行处理才能通过编译。 - 运行时异常
这种类型的异常是可以避免的。既然是可以避免的话,那我们就可以不用进行处理。但是,有时候我们是很难发现这类异常,不能将它避免开的。可编译器以为我们能避免这异常,它就不会帮助我们进行检查,所以也不会在编译阶段提示报错。但是,我们代码运行至此时,这时候异常就发生了。那就GG了…
编译时异常和运行时异常的区别
- 编译时异常一般发生的概率比较高。
- 运行时异常一般发生的概率比较低。
- 对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
- 假设Java中没有对异常进行划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将是怎么样的效果呢?
- 如果这样的话,程序肯定是绝对安全,但是编写程序太累,到处都是处理异常的代码。
- 假设出门之前,把能够发生的异常都预先处理,那么会更加安全,但活得很累。
Java异常关键字
- try (用于监听)。将要被监听的代码(可能抛出
异常的代码
)放在**try**
语句块之内,当try语句块内发生异常时,异常就被抛出。 - catch (用于捕获异常)用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally (finally语句块总是会被执行)它主要用于回收在try块里打开的物力资源(如数据库连接、 网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或 者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw (用于抛出异常)
- throws – 用在方法签名中,用于声明该方法可能抛出的异常。
异常处理
捕获处理
对于检查性异常,通过 try catch finally 关键字 实现
- try{ }
尝试捕获,把可能出现异常的代码放入其中 [ 必须的 ]
- catch( 异常类型 e ){ }
捕获到异常,在这里可以做异常的修复处理[ 可以有多个,如果是运行异常可以没有,那也就处理不了异常了]
- finally{ }
始终执行的语句块,无论是否真的触发异常它都要执行。[ 可以选 ]
细节:
- 如果出现多个catch 必须 子异常再前 父异常在后
- 如果出现多个catch 也可简化成 使用一个最大异常来捕获
示例
public class TestException {
public static void main(String[] args) {
// 1. 检查异常
System.out.println("开始");
String dateStr = "2008-08-ab";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
Date date = sdf.parse(dateStr);
System.out.println("--呵呵--");
} catch (ParseException e) {
e.printStackTrace();//打印异常信息
}finally {
System.out.println("异常处理完成");
}
System.out.println("结束");
// 2. 运算时异常
System.out.println("BEIGN");
int[] data = {1,2,3};
try {
int x = data[10];
System.out.println(x);
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}finally {
System.out.println("处理完成");
}
System.out.println("OVER");
}
}
积极处理
2.2 异常抛出
声明异常抛出的方式处理异常,利用一个 throws 关键字 。在方法的签名出添加 异常声明。
Java 中异常发生之后,如果一直上抛,最终抛给了main
方法,main
方法继续往上抛,抛给了调用者JVM
,JVM
知道这个异常发生,只有一个结果「终止Java程序的执行」
细节 : 声明的异常必须与待处理异常类型相同或更大。如果内部待处理异常有多个,也可以声明多个,有时候为了简洁也可以抛出最大的异常比如Exception。 子类重写父类或者实现类重写接口中的方法,必须不能抛出比上级更大的异常。
语法 : …. xxxx() throws 异常类名1,异常类名N
public class TestException {
public static void main(String[] args) {
String xx = "2009-11-11";
Date date = null;
try {
//这里调用自己的方法需要处理,如果再抛出,到了jvm ,直接报错
date = string2Date(xx);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);
}
//封装一个转型工具 ,内部存在检查异常,在方法签名出声明抛出,由调用则处理
public static Date string2Date(String str) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(str);
return date;
}
}
自定义异常
理解自定义异常
Java 提供了不同的异常类,不同异常类是为了描述不同的意外情况。在实际开发中,有一些业务上的意外情况,没有现有的异常类来描述。比如 转账,转账金额超了余额,可以使用一个比如 MoneyTooLessException。自定义异常一般都是服务于业务逻辑的异常。一旦违背了业务规范则抛出自定义异常。
体会: 使用自定义异常体现了 规范性 以及可以实现代码解耦合( 异常的产生 和处理分开 )
实现自定义异常
自己编写一个类 去继承 Exception , 意味着我们这个类是一个检查异常。如果继承RuntimeException 就意味着这类是一个运行时异常。
/**
* 自定义异常:描述余额不足
*/
public class MoneyTooLessException extends Exception {
public MoneyTooLessException() {
}
public MoneyTooLessException(String message) {
super(message);
}
}
使用自定义异常
在达到了必须抛出此类异常的地方,创建出该异常类对象通过 throw 异常对象;方式抛出即可
public class TestMyException {
public static void main(String[] args) {
transMoney(10000);
}
public static void transMoney(int money) {
//余额--- 从数据库查
int total = 1000;
if( money > total ){
throw new MoneyTooLessException2("余额不足");//手动抛出异常
}else{
System.out.println("转账成功!");
}
}
}
异常对象的常用方法
getMessage
getMessage();
方法获取异常发生后的简单描述信息,这个信息实际上是构造方法上String
参数。
try {
System.out.println(10 / 0);
} catch(ArithmeticException e) {
String errorMessage = e.getMessage();
System.out.println(errorMessage);
}
— / by zero
ArithmeticException e = new ArithmeticException("哟!傻子又在找BUG呢?");
System.out.println(e.getMessage());
— 哟!傻子又在找BUG呢?
printStackTrace
printStackTrace
用于打印异常堆栈信息。java 后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。
try {
System.out.println(10 / 0);
} catch(ArithmeticException e) {
e.printStackTrace();
}
— java.lang.ArithmeticException: / by zero at com.upyou.learns.ExceptionTest05.main(ExceptionTest05.java:6)
ArithmeticException e = new ArithmeticException("哟!傻子又在找BUG呢?");
e.printStackTrace();
— java.lang.ArithmeticException: 哟!傻子又在找BUG呢? at com.upyou.learns.ExceptionTest05.main(ExceptionTest05.java:6)
JDK8新特性
jdk8的特性,可以使用|
的方式捕捉多个异常
public static void main(String[] args) {
try {
readFile("***");
System.out.println(10 / 0);
}catch(FileNotFoundException | ArithmeticException e){
e.printStackTrace();
System.out.println("哦豁!又是一个找不到异常的傻子!");
}
}
public static void readFile(String filePath) throws FileNotFoundException {
FileInputStream file = new FileInputStream(filePath);
}
查找异常信息
找一个并不存在的文件,肯定会报错,那么我们假装不知道这个文件存不存在!!!如何查看异常的追踪信息呢!
public static void main(String[] args) {
try {
M1();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private static void M1() throws FileNotFoundException {
FileInputStream FIS = new FileInputStream("../哈哈哈哈懵了吧!你找不到我.dog");
}
以上程序报错信息如下:
java.io.FileNotFoundException: ../哈哈哈哈懵了吧!你找不到我.dog (No such file or directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.(FileInputStream.java:138)
at java.io.FileInputStream.(FileInputStream.java:93)
at com.upyou.learns.ExceptionTest05.M1(ExceptionTest05.java:20)
at com.upyou.learns.ExceptionTest05.main(ExceptionTest05.java:9)
- 异常追踪信息应该从上往下看,首先看异常描述信息
- 需要注意的是,只需要看自己写的代码异常信息就可以了,SUN写的代码就别看了,肯定是你自己写的BUG。那么如何得知是否是SUN写的?
- 大多数报错信息前几行都是Java的代码,例如:
一般看到什么java.xxx.xxx的直接略过,我们写的代码一般都写到自己的工作空间内,例如com.upyou.learns、com.upyou.xxx - 在我们写的代码的异常中,查看第一个异常位置,通常都是一个方法的异常引起的连锁反应。
20行出现问题导致第9行出现问题 - 第一个异常位置
at com.upyou.learns.ExceptionTest05.M1(ExceptionTest05.java:20)
,错误在com.upyou.learns
包下的ExceptionTest05
类里的M1
方法,在第20行java代码。 - 找到啦!原来是没有这个
.dog
文件 🤪🤪🤪🐶🐶🐶
- 大多数报错信息前几行都是Java的代码,例如:
finally语句
try
和finally
,没有catch
可以吗?可以。
try
不能单独使用,可以和finally
连用。
try...finally
是先执行try
再执行finally
,最后执行return
语句,无论return
在哪「return
只要执行,方法必然结束」。finally
语句块一定会执行
try {
System.out.println("try...");
return;
} finally {
System.out.println("finally...");
}
— try...
— finally...
退出JVM
后,finally
就无法执行了
try {
System.out.println("try...");
System.exit(0);
} finally {
System.out.println("finally...");
}
— try...
有这么一个程序:
public static void main(String[] args) {
System.out.println(m());
}
public static int m() {
int i = 100;
try {
return i;
} finally {
i++;
}
}
— 100
Java语法规则(有一些规则是不能破坏的,一旦这么说就不能变):
方法体中的代码必须遵循自上而下顺序依次逐行执行
以上程序finally
确实是先于return
执行。
以上程序反编译之后的结果
从反编译结果来看,Java遵循了自己定的语法规则「return必须最后执行、自上而下顺序执行」
final、finally、finalize
final
是一个关键字,表示最终的,不变的。第一次赋值之后不可第二次赋值。
final int i = 100;
// i = 200; // 错误
finally
也是一个关键字,和try联合使用,使用在异常处理机制中,在finally
语句快中的代码是一定会执行的。
try{
...
} finally {
...
}
finalize
是Object
类中的一个方法,作为方法名出现。所以finalize
是标识符。这个方法是由垃圾回收器负责调用。
自定义异常
Java中如何自定义异常?
- 编写一个类继承
Exception
或者RuntimeException
。 - 提供两个构造方法,一个无参数一个带有
String
参数的构造方法。
自定义编译时异常
定义一个编译时异常
public class NotFoundMeException
extends Exception {
public NotFoundMeException(){super();}
public NotFoundMeException(String errorMessage){
super(errorMessage);
}
}
如何使用这个异常对象捏?
new
一个异常对象并且使用throw
抛出,throws
上抛给调用者
public static void test(String str) throws NotFoundMeException{
if (str.isEmpty()) {
throw new NotFoundMeException("hiahiahia!我是异常,你找不到我!!");
}
}
处理这个异常
public static void main(String[] args) {
try {
test("");
} catch(NotFoundMeException e) {
e.printStackTrace();
}
}
com.upyou.learns.MyException.NotFoundMeException: hiahiahia!我是异常,你找不到我!!
at com.upyou.learns.ExceptionTest07.test(ExceptionTest07.java:18)
at com.upyou.learns.ExceptionTest07.main(ExceptionTest07.java:10)
自定义运行时异常
定义一个运行时异常类,继承于RuntimeException
public class NotFoundMeException02
extends RuntimeException{
public NotFoundMeException02(){super();}
public NotFoundMeException02(String errorMessage){super(errorMessage);}
}
使用这个异常类
public static void runTest(String str)
throws NotFoundMeException02{
if (str.isEmpty()) {
throw new NotFoundMeException02("哟!大家快看这个傻子又在写BUG!");
}
}
runTest("");
Exception in thread “main” com.upyou.learns.MyException.NotFoundMeException02: 哟!大家快看这个傻子又在写BUG!
at com.upyou.learns.ExceptionTest07.runTest(ExceptionTest07.java:23)
at com.upyou.learns.ExceptionTest07.main(ExceptionTest07.java:10)
👉练习源码:JavaExceptionLearn-fc943937125c4893b85b66c793197763.zip👈
不建议在
main
方法上使用throws
,因为这个异常如果真正发生了,一定会抛给JVM
,JVM
只有终止。 重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。
if-else 解决异常
public class Test01 {
public static void main(String[] args) {
//实现一个功能:键盘录入两个数的商
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数");
if(sc.hasNextInt()){
int num1 = sc.nextInt();
System.out.println("请输入第二个数");
if (sc.hasNext()){
int num2 = sc.nextInt();
if(num2==0){
System.out.println("对不起,除数不能为0");
}else
System.out.println("两个数的商:"+num1/num2);
}else {
System.out.println("对不起,你录入的不是int类型的数据");
}
}else {
System.out.println("对不起,你录入的不是int类型的数据");
}
}}
用if-else堵漏洞的缺点
1、代码臃肿,业务代码和处理异常的代码混在一起
2、可读性差
3、程序员需要花费大量的经理来维护这个漏洞
4、程序员很难堵住所有的漏洞
try-catch
public class Test02 {
public static void main(String[] args) {
//实现一个功能:键盘录入两个数的商
try{
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数");
int num1 = sc.nextInt();
System.out.println("请输入第二个数");
int num2 = sc.nextInt();
System.out.println("两个数的商:"+num1/num2);
}catch (Exception ex){
System.out.println("对不起,程序出现异常");
}
}}