视频链接:https://www.bilibili.com/video/BV1Rx411876f?p=1
视频范围P629 - P659
异常
1.异常概述
- 异常:以下程序执行过程中发生了不正常的情况
package exception;
public class ExceptionTest01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a / b;
System.out.println(c);
}
}
JVM打印的异常信息:
Exception in thread “main” java.lang.ArithmeticException: / by zero at exception.ExceptionTest01.main(ExceptionTest01.java:7)
- java提供异常处理机制作用:java把异常信息打印输出到控制台,供程序员参考,看到异常信息之后,可以对程序进行修改,让程序更加健壮
- 当上面代码中,JVM执行到int c = a / b;会new异常对象:new ArithmeticException(" / by zero "),并且JVM将new的异常对象抛出,打印输出信息到控制台了
2.异常存在形式
- 异常在java中以类的形式存在,每一个异常类都可以创建异常对象
- 异常对应的现实生活:
类是:模板 对象是:实际存在的个体 比如: 火灾是(异常类); 2022年3月13日,小红家着火了(异常对象)
package exception;
public class ExceptionTest02 {
public static void main(String[] args) {
NumberFormatException nfe = new NumberFormatException("数字格式化!");
System.out.println(nfe);
NullPointerException npe = new NullPointerException("空指针异常发生了!");
System.out.println(npe);
}
}
3.异常的继承结构
3.1 UML图
- UML是一种统一建模语言,一种图标式语言(画图的)
- UML不是只有java中使用,只要是面向对象的编程语言都有UML
- 画UML图的都是:软件架构师或者系统分析师,或者软件设计人员
- UML图中可以描述类和类之间的关系,程序执行的流程,对象的状态
- 画UML图的工具:Rational Rose(收费)、starUML(免费)…
3.2 异常的继承结构图
- Object
- Object下有Throwable(可抛出的)
- Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
- Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理,编译器报错) RuntimeException:运行时异常(在编写程序阶段程序员可以预先处理,也可以不管)
使用starUML绘制:
3.3 编译时异常 和 运行时异常 区别
- 这两个异常多是发生在运行阶段,编译阶段这两个异常是不会发生的
- 编译时异常命名由来:因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错
- 所有异常都是在运行阶段发生的,因为只有程序运行阶段才可以new对象(异常的发生就是new异常对象)
- 编译时异常又被称为受检异常(CheckedException)、受控异常
- 运行时异常又被称为未受检异常(UnCheckedException)、非受控异常
-
编译时异常一般发生的概率比较高 举例:你看见外面下雨,倾盆大雨,你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。 “拿把伞”就是对“生病异常”发生之前的一种处理方式 对一些发生概率较高的异常,需要在运行之前对其进行预处理 -
运行时异常一般发生的概率比较低 举例:小明走在大街上,可能会被天上的飞机轮子砸到(被砸到也是一种异常)。但是这种异常发生概率较低。 在出门之前没必要提前对这种发生概率较低的异常进行预处理,如果进行预处理,就会很累。 -
如果java没有对异常进行划分,没有分为:编译时异常 和 运行时异常 那么所有的异常都需要在编写程序阶段对其进行预处理,虽然程序这样肯定是绝对的安全,但是程序员编写程序太累,代码到处都是处理异常的代码。
4.异常的处理方式
- 第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级
- 第二种方式:使用try…catch语句进行异常的捕捉
举例: 我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,“损失1000元”这可以看做是一个异常发生了,我有两种处理方式: 第一种方式:我把这件事情告诉了我的领导【异常上抛】 第二种方式:我自己掏腰包把这个钱补上【异常的捕捉】
- 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式
- java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果,终止java程序的执行
- 只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行
- try语句块中的某一行出现异常,该行后面的代码不会执行,try…catch捕捉异常之后,后续代码可以执行
- 如果希望调用者来处理,选择throws上报
4.1 运行时异常举例
ArithmeticException 继承 RuntimeException,属于运行时异常,在编写程序阶段不需要对这种异常进行预先的处理
package exception;
public class ExceptionTest03 {
public static void main(String[] args) {
System.out.println(100 / 0);
System.out.println("hello world!");
}
}
4.2 编译时异常举例
一下代码报错的原因: 因为doSome()方法声明位置上使用了:throws ClassNotFoundException,而ClassNotFoundException是编写代码时异常,没有处理,编译器就报错
package exception;
public class ExceptionTest04 {
public static void main(String[] args) {
doSome();
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}
解决方案 第一种方式: 在方法声明的位置上继续使用:throws 来完成异常的继续上抛,抛给调用者 上抛类似于推卸责任(继续把异常传递给调用者)
package exception;
public class ExceptionTest05 {
public static void main(String[] args) throws ClassNotFoundException {
doSome();
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}
第二种方式: try…catch进行捕捉 捕捉等于把异常拦下了,异常真正的解决了(调用者是不知道的)
package exception;
public class ExceptionTest05 {
public static void main(String[] args) {
try {
doSome();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}
4.3 处理异常的第一种方式:throws
在方法声明的位置上使用throws关键字抛出,谁调用这个方法,就抛给谁,抛给调用者来处理 处理异常的态度:上报
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest06 {
public static void main(String[] args) throws FileNotFoundException {
System.out.println("main begin");
m1();
System.out.println("main over");
}
private static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
private static void m2() throws FileNotFoundException {
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
private static void m3() throws FileNotFoundException {
new FileInputStream("C:\\Users\\keith\\1.txt");
}
}
总结:一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM,JVM只有终止,异常处理机制的作用就是增强程序的健壮性,所以一般main方法中的异常建议使用try…catch进行捕捉,main就不要继续上抛了。
4.4 处理异常的第二种方式:try…catch
使用try…catch语句对异常进行捕捉 这个异常不会上报(上抛),自己把这个事儿处理了
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest06 {
public static void main(String[] args) {
System.out.println("main begin");
try {
m1();
} catch (FileNotFoundException e) {
System.out.println("文件不存在,可能路径错误,也可能该文件被删除了!");
}
System.out.println("main over");
}
private static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
private static void m2() throws FileNotFoundException {
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
private static void m3() throws FileNotFoundException {
new FileInputStream("C:\\Users\\keith\\1.txt");
}
}
当路径正确的时候,运行结果:
当路径不正确的时候,运行结果:
4.5 深入 try…catch
- catch后面的小括号中的类型可以是具体的异常类型,也可以是异常类型的父类型
- catch可以写多个,建议catch的时候,精确的一个一个处理,这样有利于程序的调试
- catch写多个的时候,从上到下,必须遵守从小到大
测试实验一:
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest07 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("C:\\Users\\keith\\1.txt");
System.out.println("以上出现异常,这里无法执行!!!");
} catch (FileNotFoundException e) {
System.out.println("文件不存在!");
}
System.out.println("hello world!!!");
}
}
当路径正确的时候,运行结果:
当路径不正确的时候,运行结果:
测试实验二:
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest07 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("C:\\Users\\keith\\12.txt");
} catch (IOException e) {
System.out.println("文件不存在!");
}
System.out.println("hello world!!!");
}
}
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest07 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("C:\\Users\\keith\\12.txt");
} catch (Exception e) {
System.out.println("文件不存在!");
}
System.out.println("hello world!!!");
}
}
测试实验三:
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest07 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("C:\\Users\\keith\\12.txt");
fis.read();
} catch (FileNotFoundException e) {
System.out.println("文件不存在!");
}catch (IOException e){
System.out.println("读取失败!");
}
System.out.println("hello world!!!");
}
}
测试实验四: JDK8的新特性
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest07 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("C:\\Users\\keith\\12.txt");
System.out.println(100 / 0);
} catch (FileNotFoundException | ArithmeticException | NullPointerException e) {
System.out.println("文件不存在?数学异常?空指针异常?");
}
}
}
5.异常对象的常用方法
5.1 getMessage方法
获取异常简单的描述信息
String msg = exception.getMessage();
演示代码:
package exception;
public class ExceptionTest08 {
public static void main(String[] args) {
NullPointerException e = new NullPointerException("空指针异常!!!");
String msg = e.getMessage();
System.out.println(msg);
}
}
5.2 printStackTrace()方法
打印异常追踪的堆栈信息:
exception.printStackTrace();
演示代码一:
package exception;
public class ExceptionTest08 {
public static void main(String[] args) {
NullPointerException e = new NullPointerException("空指针异常!!!");
e.printStackTrace();
System.out.println("hello world!!!");
}
}
运行结果:
演示代码二:
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest09 {
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("hello world!");
}
private static void m1() throws FileNotFoundException {
m2();
}
private static void m2() throws FileNotFoundException {
m3();
}
private static void m3() throws FileNotFoundException {
new FileInputStream("C:\\Users\\keith\\12.txt");
}
}
运行结果:
6.查看异常的追踪信息
- 从上往下一行一行看
- SUN写的代码就不用看了,主要的问题出现在自己编写的代码上【看包名判断是不是SUN写的】
演示: 注:第一行是异常信息,中间的是SUN写的,下面的是自己的错误部分!
7.finally关键字
- finally子句属于try…catch语句中
- 在finally子句中的代码时最后执行的,并且时一定会执行的,即使try语句块中的代码出现了异常。
- finally子句必须和try一起使用,不能单独编写
- 通常在finally语句块中完成资源的释放/关闭,因为finally中的代码比较有保障,即使try语句块中的代码出现异常,finally中代码也会正常执行
7.1 代码实训一
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest10 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("C:\\Users\\keith\\12.txt");
String s = null;
s.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}catch (NullPointerException e){
e.printStackTrace();
}finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
7.2 代码实训二
- try和finally,没有catch也是可以使用的,即try和finally可以联合使用
- try不能单独使用
- 放在finally语句块中的代码是一定会执行的
以下代码执行顺序:
- 先执行try
- 再执行finally
- 最后执行return(return语句只要执行方法必然结束)
package exception;
public class ExceptionTest11 {
public static void main(String[] args) {
try {
System.out.println("try");
return;
}finally {
System.out.println("finally");
}
}
}
运行结果:
7.3 代码实训三
退出JVM之后,finally语句中的代码就不执行了
package exception;
public class ExceptionTest12 {
public static void main(String[] args) {
try {
System.out.println("try");
System.exit(0);
}finally {
System.out.println("finally");
}
}
}
7.4 finally面试题
package exception;
public class ExceptionTest13 {
public static void main(String[] args) {
int result = m();
System.out.println(result);
}
private static int m() {
int i = 100;
try{
return i;
}finally {
i++;
}
}
}
m()函数反编译之后效果:
private static int m() {
int i = 100;
int j = i;
i++;
return i;
}
8.final、finalize和finally区别
| | |
---|
final | 关键字 | final修饰的类无法继承;final修饰的方法无法覆盖;final修饰的变量不能重新赋值 | finally | 关键字 | 和try一起联合使用;finally语句块中的代码时必须执行的 | finalize | 标识符 | 是一个Object类中的方法名,整个方法是由垃圾回收器GC负责调用 |
package exception;
public class ExceptionTest14 {
public static void main(String[] args) {
final int i = 100;
try{
}finally {
System.out.println("finally");
}
Object obj;
}
}
9.如何自定义异常类
SUN提供的JDK内置的异常肯定是不够用的,在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的, 和业务挂钩的,那么异常类可以由程序员自己定义。
Java中自定义异常两步骤:
- 编写一个类继承Exception或者RuntimeException
- 提供两个构造方法,一个无参数的,一个带有String参数的
异常类:
package exception;
public class MyException extends Exception {
public MyException(){
}
public MyException(String s){
super(s);
}
}
测试类:
package exception;
public class ExceptionTest15 {
public static void main(String[] args) {
MyException e = new MyException("用户名不能为空!");
e.printStackTrace();
String msg = e.getMessage();
System.out.println(msg);
}
}
运行结果:
10.异常在实际开发中的作用
异常类: 栈操作异常
package exception;
public class MyStackOperationException extends Exception {
public MyStackOperationException() {
}
public MyStackOperationException(String message) {
super(message);
}
}
栈:
package exception;
public class MyStack {
private Object[] elements;
private int index = -1;
public MyStack() {
this.elements = new Object[10];
}
public void push(Object obj) throws MyStackOperationException {
if (this.index >= this.elements.length - 1){
MyStackOperationException e = new MyStackOperationException("压栈失败,栈已满!");
throw e;
}
this.index++;
this.elements[index] = obj;
System.out.println("压栈"+ obj + "元素成功,栈帧指向 " + this.index);
}
public void pop() throws MyStackOperationException {
if (this.index < 0){
throw new MyStackOperationException("栈已空,弹栈失败!");
}
System.out.println("弹栈"+ this.elements[this.index] + "元素成功 ");
this.index--;
System.out.println(" 栈帧指向 " + this.index);
}
public Object[] getElements() {
return elements;
}
public void setElements(Object[] elements) {
this.elements = elements;
}
}
自定义测试类:
package exception;
public class ExceptionTest16 {
public static void main(String[] args) {
MyStack stack = new MyStack();
try {
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
} catch (MyStackOperationException e) {
System.out.println(e.getMessage());
}
try {
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
} catch (MyStackOperationException e) {
System.out.println(e.getMessage());
}
}
}
运行结果:
11.异常和方法覆盖
在方法覆盖内容部分,有一个遗留问题: 重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少
package exception;
class Animal{
public void doSome(){
}
public void doOther() throws Exception{
}
}
class Cat extends Animal{
public void doSome() throws RuntimeException{
}
public void doOther() throws NullPointerException{
}
}
总结异常中的关键字: 异常捕捉:try、catch、finally throws:在方法声明位置上使用,表示上报异常信息给调用者 throw:手动抛出异常!
12.异常作业
编写程序模拟用户注册:
- 程序开始执行时,提示用户输入“用户名”和“密码”信息
- 输入信息之后,后台java程序模拟用户注册
- 注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常
注意:完成注册的方法放到一个单独的类中,异常类自定义即可。
class UserService{
public void register(String username,String password){
}
}
编写main方法,在main方法中接收用户输入的信息,在main方法中调用UserServicv的register方法完成注册!
自定义异常类:
package exception.homework;
public class IllegalNameException extends Exception{
public IllegalNameException() {
}
public IllegalNameException(String message) {
super(message);
}
}
用户业务类: 处理用户相关的业务:例如登录、注册等功能。
package exception.homework;
public class UserService {
public void register(String username,String password) throws IllegalNameException {
if (null == username || username.length() < 6 || username.length() > 14){
throw new IllegalNameException("用户名不合法,长度必须在[6-14]之间!");
}
System.out.println("注册成功,欢迎[" + username + "]");
}
}
测试类:
package exception.homework;
public class Test {
public static void main(String[] args) {
UserService userService = new UserService();
try {
userService.register("zhangsan","123");
} catch (IllegalNameException e) {
System.out.println(e.getMessage());
}
}
}
运行结果:
|