异常的分类
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流异常ClassNotFoundExceptionNotSerializableException 序列化异常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 NotFoundMeExceptionextends 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 NotFoundMeException02extends 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");}elseSystem.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("对不起,程序出现异常");}}}
