目录
一. 异常概述、体系
什么是异常?
为什么要学习异常?
如何处理异常?
假设我们有一个接收String参数的方法,方法中会对该参数进行一些逻辑处理,正常的业务流程要求不允许null值出现,可如果调用者传递了一个null值进来,此时我们该怎么做呢?
二. 常见运行时异常
三. 常见编译时异常
java.util.concurrent.BrokenBarrierException {CyclicBarrier.await()}
java.util.concurrent.ExecutionException
NoSuchMethodException{反射}
四. 异常的默认处理流程
五. 编译时异常的处理机制
编辑
六.Throwable类常用方法有哪些?try-catch-finally如何使用?
String getMessage(): 返回异常发生时的简要描述
String toString(): 返回异常发生时的详细信息
void printStackTrace(): 在控制台上打印 Throwable 对象封装的异常信息
try-catch-finally 如何使用?
finally 中的代码一定会执行吗?
如何使用 try-with-resources 代替try-catch-finally?
throw和throws的区别:
七. 运行时异常的处理机制
七. 异常处理使代码更稳健的案例
八. 自定义异常
补充:
1. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
2. try-catch-finally 中哪个部分可以省略?
3. 主线程可以捕获到子线程的异常吗?
4. final、finally、finalize 有什么区别?
比如:数组索引越界异常、空指针异常、日期格式化异常,等....
Virtual MachineError:
Java 虚拟机运行错误 => 内存溢出(OutOfMemoryError --- 堆内存溢出,StackOverflowError --- 栈内存溢出)
- Java中的所有异常都来⾃顶级⽗类Throwable,Throwable下有两个⼦类Exception和Error。
- Throwable类,是所有异常类的根类,Java异常的顶层父类,所有的异常类都是由它继承。
- Exception:异常,程序本身可以处理的异常。
- Java中异常继承的根类是:Throwable。Throwable是根类,不是异常类。
- Exception才是异常类,当程序出现Exception时,是可以靠程序⾃⼰来解决的,它才是开发中代码在编译或者执行的过程中可能出现的错误,它是需要提前处理的,以便程序更健壮!
- 异常体系的最上层父类是Exception。
- Exception的⼦类通常⼜可以分为RuntimeException和⾮RuntimeException两类
- 异常分为两类:编译时异常(受检异常,必须处理)、运行时异常(非受检异常)。
- 受检异常:就是程序必须手动处理的异常,如果不手动处理,则会编译失败。
- 非受检异常:不强制程序处理的异常,不强制你手动处理,无需try...catch...,也无需throws,代码也能编译成功。
- Error也属于非受检异常。
- 编译时异常{受检异常-Checked Exception}:没有继承RuntimeException的异常,直接继承于Exception。编译阶段就会错误提示 / 报错,必须要手动处理(如果受检查异常没有被
catch
或者throws
关键字处理的话,就没办法通过编译),否则代码报错不通过。编译时异常是为了提醒程序员,比如:IOException、ClassNotFoundException、ParseException、SQLException、java.io.FileNotFoundException...- 运行时异常{非受检异常-Unchecked Exception}:继承了RuntimeException,RuntimeException本身和子类。编译阶段不会报错 / 出现异常提醒,运行时出现的异常。运行时异常是代码出错而导致程序出现的问题。
package com.gch.d3_exception;
/**
目标:异常的概念和体系。
*/
public class ExceptionDemo {
public static void main(String[] args) {
int[] arr = {10, 20, 40};
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]); // ArrayIndexOutOfBoundsException
System.out.println("-----------程序截止---------");
}
}
package com.gch.d3_exception;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionDemo1 {
public static void main(String[] args) throws ParseException {
// 编译时异常(在编译阶段,必须要手动处理,否则代码报错)
String time = "2030年1月1日";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
Date date = sdf.parse(time);
System.out.println(date); // Tue Jan 01 00:00:00 CST 2030
// 运行时报错(在编译阶段是不需要处理的,是代码运行时出现的异常)
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException
}
}
在Java中有两个处理异常的方式,一个是try...catch...,一个是throws。
try...catch:
package com.gch.exceptiondemo;
import java.io.IOException;
/**
try捕获异常,catch处理异常
*/
public class ExceptionDemo {
public static void main(String[] args) throws Exception {
// 可以catch一个也可以catch多个异常
try{
// 可能会发生异常的逻辑
throw new Exception();
}catch(IOException e){
// 发生IOException时,执行此代码块
System.out.println(e);
}catch(ClassNotFoundException e){
// 发生ClassNotFoundException,执行此代码块
System.out.println(e);
}catch(Exception e){
// 发生其它异常时,执行此代码块
// 父类异常应放在子类异常后面,否则子类不会被捕获
}finally{
// 无论是否发生异常,都会执行
// 如果没有加catch处理异常,finally虽会执行,但异常还是会影响正常逻辑
}
}
}
throws{抛出异常}:在方法上使用throws关键字可以声明该方法可能会抛出的异常。
package com.gch.exceptiondemo;
import java.io.IOException;
/**
try捕获异常,catch处理异常
*/
public class ExceptionDemo {
public static void main(String[] args) throws Exception {
// 可以catch一个也可以catch多个异常
try{
// 可能会发生异常的逻辑
throw new Exception();
}catch(IOException e){
// 发生IOException时,执行此代码块
System.out.println(e);
}catch(ClassNotFoundException e){
// 发生ClassNotFoundException,执行此代码块
System.out.println(e);
}catch(Exception e){
// 发生其它异常时,执行此代码块
// 父类异常应放在子类异常后面,否则子类不会被捕获
}finally{
// 无论是否发生异常,都会执行
// 如果没有加catch处理异常,finally虽会执行,但异常还是会影响正常逻辑
}
}
/** 可以throws声明一个或多个异常 */
/**
* 可以throws声明一个或多个异常
* @throws IOException
* @throws ClassNotFoundException
*/
public static void process() throws IOException,ClassNotFoundException{
// 可能会发生异常的逻辑
}
public static void f1() {
// 编译失败,编译器直接报错,因为没有手动处理受检异常{try...catch... / throws}
process();
}
public static void f2() {
// 编译成功,因为try...catch处理异常了
// try {
// process();
// } catch (IOException e) {
// throw new RuntimeException(e);
// } catch (ClassNotFoundException e) {
// throw new RuntimeException(e);
// }
try{
process();
}catch(IOException | ClassNotFoundException e){
// ...
}
}
public static void f3() throws IOException, ClassNotFoundException {
// 编译成功,因为throws往外抛异常了
process();
}
public static void f4(){
// 因为方法f2()已经try...catch...处理异常了,所以不用再手动处理了
f2();
}
}
什么时候该try...catch...,什么时候该throws呢?
/**
* @param arg 参数不允许为null
*/
public static void method(String arg){
if(arg == null){
arg = "默认值";
}
// 继续执行业务逻辑...
}
/**
* @param arg 参数不能为null
* @return 参数如果为null则返回false,否则为true
*/
public static boolean method(String arg) {
if(arg == null){
return false;
}
// 业务逻辑...
return true;
}
/**
* @param arg 参数不能为null
*/
public static void method3(String arg) {
if(arg == null){
throw new RuntimeException();
}
// 业务逻辑...
}
总结: 如果碰到了会影响正常逻辑的情况,基本就这三大类处理方式 =>
运行时异常的概念:
继承自RuntimeException的异常或者其子类,
编译阶段是不会出错的,它是在运行时阶段可能出现的错误,
运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!!
并发修改异常:java.util.ConcurrentModificationException(多线程下集合操作元素)
IllegalArgumentException:参数错误(比如方法入参类型错误)
SecurityException:安全错误(比如权限不够)
UnsupportedOperationException:不支持的操作错误(比如重复创建同一用户)
ArrayStoreException:数据存储异常(操作数组时类型不一致)
java.lang.IllegalStateException{Queue full依然add()添加元素}
FileSizeLimitExceededException:在SpringBoot中,文件上传时默认单个文件最大大小为1M,当上传一个较大的文件(超出1M)时,运行和后端程序报错
NoSuchBeanDefinitionException:表示在应用程序中没有找到指定的Bean的定义!在Spring容器ApplicationContext中调用getBean()获取bean对象时,bean的名称填错,就会报改错,或者就跟着没有该Bean对象!
UnsupportedClassVersionError:比如在JDK8上面运行JDK11环境下的程序!
java.lang.UnsupportedOperationException异常:使用Collections.unmodifiableCollection(Collection c)方法来创建一个只读集合,这样改变集合的任何操作都会抛出该异常~!
package com.gch.d4_exception_runtimeException;
/**
拓展: 常见的运行时异常。(面试题)
运行时异常的概念:
继承自RuntimeException的异常或者其子类,
编译阶段是不会出错的,它是在运行时阶段可能出现的错误,
运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!!
1.数组索引越界异常: ArrayIndexOutOfBoundsException。
2.空指针异常 : NullPointerException。
直接输出没有问题。但是调用空指针的变量的功能就会报错!!
3.类型转换异常:ClassCastException。
4.迭代器遍历没有此元素异常:NoSuchElementException。
5.算术异常:ArithmeticException。
6.数字转换异常:NumberFormatException。
小结:
运行时异常继承了RuntimeException ,编译阶段不报错,运行时才可能会出现错误!
*/
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("程序开始。。。。。。");
/** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/
int[] arr = {1, 2, 3};
System.out.println(arr[2]);
// System.out.println(arr[3]); // 运行出错,程序终止
/** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */
String name = null;
System.out.println(name); // null
// System.out.println(name.length()); // 运行出错,程序终止
/** 3.类型转换异常:ClassCastException。 */
Object o = 23;
// String s = (String) o; // 运行出错,程序终止
/** 5.数学操作 / 算术异常:ArithmeticException。 */
//int c = 10 / 0;
/** 6.数字转换异常: NumberFormatException。 */
//String number = "23";
String number = "23aabbc";
Integer it = Integer.valueOf(number); // 运行出错,程序终止
System.out.println(it + 1);
System.out.println("程序结束。。。。。");
}
}
当调用一个抛出异常的方法的时候,调用者必须处理这个异常(try...catch...或者throws),如果不处理,编译失败!
package com.gch.d5_exception_javac;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
目标:常见的编译时异常认识。
编译时异常:继承自Exception的异常或者其子类,没有继承RuntimeException
"编译时异常是编译阶段就会报错",
必须程序员编译阶段就处理的。否则代码编译就报错!!
编译时异常的作用是什么:
是担心程序员的技术不行,在编译阶段就爆出一个错误, 目的在于提醒!
提醒程序员这里很可能出错,请检查并注意不要出bug。
编译时异常是可遇不可求。遇到了就遇到了呗。
了解:
*/
public class ExceptionDemo { // 解析异常
public static void main(String[] args) throws ParseException {
String date = "2015-01-12 10:23:21";
// 创建一个简单日期格式化类:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 解析字符串时间成为日期对象
Date d = sdf.parse(date);
System.out.println(d); // Mon Jan 12 10:23:21 CST 2015
}
}
Throwable类常用方法:
String getMessage()
: 返回异常发生时的简要描述String toString()
: 返回异常发生时的详细信息void printStackTrace()
: 在控制台上打印 Throwable
对象封装的异常信息public synchronized Throwable getCause():获取实际的异常原因
try
块:用于捕获异常。其后可接零个或多个 catch
块,如果没有 catch
块,则必须跟一个 finally
块。catch
块:用于处理 try 捕获到的异常。finally
块:无论是否捕获或处理异常,finally
块里的语句都会被执行。当在 try
块或 catch
块中遇到 return
语句时,finally
语句块将在方法返回之前被执行。可以在finally代码块中进行资源的释放!注意:不要在 finally 语句块中使用 return!
jvm 官方文档open in new windowhttps://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.5JVM官方文档中有明确提到:
代码示例:
public static void main(String[] args) {
System.out.println(f(2));
}
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
try {
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
// 终止当前正在运行的Java虚拟机
System.exit(1);
} finally {
System.out.println("Finally");
}
另外,在以下几种特殊情况下,finally
块的代码也不会被执行:
try-with-resources
代替try-catch-finally
?java.lang.AutoCloseable
或者 java.io.Closeable
接口的类对象才能定义为资源。thow
throws
注意:如果main方法里面单独开启了一条线程,并且调用了抛出异常的方法,则只能在方法内部进行try...catch处理,因为方法内部不能使用throws抛出异常类!
package com.gch.d8_handle_runtime;
import java.sql.SQLOutput;
import java.util.Scanner;
/**
需求:需要输入一个合法的价格为止 要求价格大于0
*/
public class Test2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while(true){
try {
System.out.println("请您输入价格:");
String priceStr = sc.nextLine();
// 转换成Double类型的价格
double price = Double.valueOf(priceStr);
// 判断价格是否大于0
if(price > 0){
System.out.println("定价:" + price);
break;
}else{
System.out.println("价格必须是正数~~~");
}
} catch (NumberFormatException e) {
System.out.println("程序有误!");
// throw new RuntimeException(e);
}
}
}
}
Java标准库中提供了非常多表的异常类型,用来表达各种异常情况,然而在真实开发中,这些异常并不能完全满足我们的需求,因为标准库的异常往往表达的是技术层面,而不是业务层面,像账号密码错误这种情况,用标准库的异常就不太合适,所以在开发中我们会自定义异常类型,来表达符合我们业务的异常情况。
只要继承异常类,就可以定义我们自己的异常,强烈建议大家继承RuntimeException,在自定义异常时,应当照着父类学习,提供多个构造方法,因为这样就能传递错误信息和堆栈信息。
自定义异常异常定义好后,我们就可以new出异常对象,并用throw关键字来主动抛出异常。