异常(Exception)
异常的概念
一个小例子来引出异常
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;//Scanner();
try {
int res = num1 / num2;
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序继续运行....");
}
代码解读:
- num1 / num2 => 10 / 0
- 当执行到 num1 / num2 因为 num2 = 0, 程序就会出现(抛出)异常 ArithmeticException
- 当抛出异常后,程序就退出,崩溃了 , 下面的代码就不在执行
- 大家想想这样的程序好吗? 不好,不应该出现了一个不算致命的问题,就导致整个系统崩溃
- java 设计者,提供了一个叫 异常处理机制来解决该问题,如果程序员,认为一段代码可能出现异常/问题,可以使用 try-catch 异常处理机制来解决,从而保证程序的健壮性
- 将该代码块->选中->快捷键 ctrl + alt + t -> 选中 try-catch
- 如果进行异常处理,那么即使出现了异常,程序可以继续执行
代码运行效果:
如果感觉红色太“喜庆”,可以将上述代码的第 7 行改为:
System.out.println("出现异常的原因=" + e.getMessage());//输出异常信息
代码运行结果:
基本概念
Java 语言中,将程序执行中发生的不正常情况称为“异常”
**注意:**开发过程中的语法错误和逻辑错误不是异常
执行过程中所发生的异常事件可分为两大类
- Error(错误),Java 虚拟机无法解决的严重问题。如 JVM 系统内部错误、资源耗尽等严重情况。比如 StackOverflowError[栈溢出]和 OOM(out of memeory,内存不足),Error 是严重错误,程序会崩溃
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:空指针访问、读取不存在的文件、网络连接中断等等,Exception 分为两大类:运行时异常【程序运行时,发生的异常】和编译时异常【编程时,编译器检查出的异常】。
上面的小例子就是运行时异常
异常体系图🚩
异常中我们主要研究 Exception,Exception 中主要研究运行时异常(Runtime Exception)
![image-20221110200712196](https://typora-ac999.oss-cn-shanghai.aliyuncs.com/image-20221110200712196.png)
![image-20221110195551999](https://typora-ac999.oss-cn-shanghai.aliyuncs.com/image-20221110195551999.png)
异常体系图小结:
- 异常分为两大类,运行时异常和编译时异常
- 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免出现的错误。java.lang.RuntimeException 类及它的子类都是运行时异常。
- 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
- 编译时异常是编译器必须处置的异常。(意思就是平时在写代码时,写的错误代码,编译器在代码下有红色波浪线,你不解决,编译器就不编译。)
常见的异常
常见的运行时异常
- NullPointerException 空指针异常
- ArithmeticException 数学运算异常
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 类型转换异常
- NumberFormatException 数字格式不正确异常
NullPointerException 空指针异常
当应用程序试图在需要对象的地方使用 null 时,抛出该异常。也就是说这个对象你还没有创建起来你就去使用它,就会抛出空指针异常。
String name = null;
System.out.println(name.length());
报错信息:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
ArithmeticException 数学运算异常
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时
int num1 = 10;
int num2 = 0;//Scanner();
int res = num1 / num2;
报错信息:
Exception in thread "main" java.lang.ArithmeticException: / by zero
ArrayIndexOutOfBoundsException 数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
int[] arr = {1,2,4};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
arr.length = 3,arr[3] ,下标 3 越界
报错信息:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
ClassCastException 类型转换异常
当试图将对象强制转换为不是实例的子类时,抛出该异常
public class test1 {
public static void main(String[] args) {
A b = new B(); //向上转型
B b2 = (B)b;//向下转型,这里是 OK
C c2 = (C)b;//这里抛出 ClassCastException
}
}
class A {}
class B extends A {}
class C extends A {}
报错信息:
Exception in thread "main" java.lang.ClassCastException: class test.B cannot be cast to class test.C
NumberFormatException 数字格式不正确异常
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常 => 使用异常我们 可以确保输入是满足条件数字
String name = "bestpig";
//将 String 转成 int
int num = Integer.parseInt(name);//抛出 NumberFormatException
System.out.println(num);
报错信息:
Exception in thread "main" java.lang.NumberFormatException: For input string: "bestpig"
常见的编译异常
编译异常是指在编译期间就必须处理的异常,否则代码不能通过编译
- SQLException // 操作数据库时,查询表可能发生的异常
- IOException // 操作文件时发生的异常
- FileNotFoundException //当操作一个不存在的文件时发生的异常
- ClassNotFoundException // 加载类,而该类不存在时发生的异常
- EOFException // 操作文件,到文件末尾,发生的异常
- IllegalArguementException // 参数异常
举一个 FileNotFoundException 的例子:
FileInputStream fis;
fis = new FileInputStream("d:\\aa.jpg");
int len;
while ((len = fis.read()) != -1) {
System.out.println(len);
}
fis.close();
写完之后,在 new FileInputStream("d:\\aa.jpg")
,下会有红色波浪线,鼠标放上去显示报错信息
Unhandled exception type FileNotFoundException
异常处理🚩
基本介绍
异常处理就是当异常发生时,对异常处理的方式
异常处理的方式
- try - catch - finally
程序员在代码中捕获发生的异常,自行处理
- throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是 JVM
示意图
说明:
f2 方法抛出异常,抛给 f1方法,f1可以通过 try - catch - finally 处理,也可以抛给 main 方法……。
**注意:**try -catch 和 throws 二选一
try - catch 异常处理
基本介绍
-
Java 提供 try 和 catch 块来处理异常。try 块用于包含可能出错的代码。catch 块用于处理 try 块中发生的异常。可以根据需要在程序中有多个 try- catch 块。
-
基本语法
try{ // 可疑代码 // 将异常生成对应的异常对象,传递给 catch 块 }catch(异常){ // 对异常的处理 } // 如果没有 finally,语法可以通过
注意事项
-
如果异常发生了,则异常发生后面的代码不会执行,直接进入到 catch 块
try { String str = "bestpig"; int a = Integer.parseInt(str); System.out.println("数字:" + a); } catch (NumberFormatException e) { System.out.println("异常信息=" + e.getMessage()); } System.out.println("程序继续...");
运行结果:
异常信息=For input string: "bestpig" 程序继续...
可见第 4 行代码并没有输出
-
如果异常没有发生,则顺序执行 try 的代码块,不会进入到 catch
try { String str = "123"; int a = Integer.parseInt(str); System.out.println("数字:" + a); } catch (NumberFormatException e) { System.out.println("异常信息=" + e.getMessage()); } System.out.println("程序继续...");
运行结果:
数字:123 程序继续...
可见第 6 行代码并没有输出
-
如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用如下代码- finally
try { String str = "123"; int a = Integer.parseInt(str); System.out.println("数字:" + a); } catch (NumberFormatException e) { System.out.println("异常信息=" + e.getMessage()); }finally{ System.out.println("finally 代码块被执行……"); } System.out.println("程序继续...");
运行结果:
数字:123 finally 代码块被执行…… 程序继续...
try { String str = "bestpig"; int a = Integer.parseInt(str); System.out.println("数字:" + a); } catch (NumberFormatException e) { System.out.println("异常信息=" + e.getMessage()); }finally{ System.out.println("finally 代码块被执行……"); } System.out.println("程序继续...");
运行结果:
异常信息=For input string: "bestpig" finally 代码块被执行…… 程序继续...
可见 第 8 行 都有被执行
-
可以有多个 catch 语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception 在后, NullPointerException 在前),如果发生异常,只会匹配一个 catch
public class test1 { public static void main(String[] args) { try { Person person = new Person(); person = null; System.out.println(person.getName());//NullPointerException int n1 = 10; int n2 = 0; int res = n1 / n2;//ArithmeticException } catch (NullPointerException e) { System.out.println("空指针异常=" + e.getMessage()); } catch (ArithmeticException e) { System.out.println("算术异常=" + e.getMessage()); } catch (Exception e) { System.out.println(e.getMessage()); } finally { } } } class Person { private String name = "jack"; public String getName() { return name; } }
运行结果:
空指针异常=Cannot invoke "test.Person.getName()" because "person" is null
如果将第 6 行注释掉,则会捕获 ArithmeticException 异常
jack 算术异常=/ by zero
- 可以进行 try - finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。应用场景:就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。
执行顺序小结
- 如果没有出现异常,则执行 try 块中所有的语句,不执行 catch 块中语句,如果有 finally,最后还需要执行 fianlly 里面的语句
- 入股出现异常,则 try 块中异常发生后, try 块剩下的语句不再执行。将执行 catch 块中的语句,如果有 finally,最后还需要执行 fianlly 里面的语句
throws 异常处理
基本介绍
- 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
- 在方法声明中用 throws 语句可以声明抛出异常的列表,throws 后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
快速入门案例
public void f2() throws FileNotFoundException {
//创建了一个文件流对象
//这里的异常是一个FileNotFoundException 编译异常
//使用throws ,抛出异常, 让调用f2方法的调用者(方法)处理
FileInputStream fis = new FileInputStream("d://aa.txt");
}
throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
public void f2() throws Exception {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
throws 关键字后也可以是 异常列表, 即可以抛出多个异常
public void f2() throws FileNotFoundException,NullPointerException,ArithmeticException {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
注意事项和使用细节
- 对于编译异常,程序中必须处理,比如 try - catch 或者 throws
- 对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理
这一个就是说你代码出错了,你没有写异常处理的方法,最终抛给了 JVM 来处理,JVM 就是直接打出错误信息。通俗点说就是平时写代码出错,控制台给出报错信息
- 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的子类型
class Father { //父类
public void method() throws RuntimeException {
}
}
class Son extends Father {//子类
//子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
@Override
public void method() throws ArithmeticException {
}
}
- 在 throws 过程中,如果有方法 try - catch,就相当于处理异常,就可以不必 throws
这个在前面的示意图已经说过,就是 try - catch 和 throws 二选一
补充一点:
public static void f1() {
f3(); // 抛出异常
}
public static void f3() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
这里大家思考问题:调用f3() 报错
因为f3() 方法抛出的是一个编译异常, 即这时,就要 f1() 必须处理这个编译异常,在 f1() 中,要么 try-catch-finally ,或者继续throws 这个编译异常
public static void f4() {
//老韩解读:
//1. 在f4()中调用方法f5() 是OK
//2. 原因是f5() 抛出的是运行异常
//3. 而java中,并不要求程序员显示处理,因为有默认处理机制
f5();
}
public static void f5() throws ArithmeticException {
}
在 f4() 中调用方法 f5() 是不会报错的,原因是f5() 抛出的是运行异常,而java中,并不要求程序员显示处理,因为有默认处理机制(即默认向上抛出),就是相当于将第一行代码看为
public static void f4() throws ArithmeticException
只是 throws ArithmeticException
不用我们去写,java 默认处理了。
自定义异常
基本概念
当程序中出现了某些“错误”,但该错误信息并没有在 Throwable 子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息
自定义异常的步骤
- 定义类:自定义异常类名(程序员自己写)继承 Exception 或 RuntimeException
- 如果继承 Exception,属于编译异常
- 如果继承 RuntimeException,属于运行异常
强烈建议:继承 RuntimeException
public class CustomException {
public static void main(String[] args) /*throws AgeException*/ {
int age = 180;
//要求范围在 18 – 120 之间,否则抛出一个自定义异常
if(!(age >= 18 && age <= 120)) {
//这里我们可以通过构造器,设置信息
throw new AgeException("年龄需要在 18~120 之间");
}
System.out.println("你的年龄范围正确.");
}
}
class AgeException extends RuntimeException {
public AgeException(String message) {//构造器
super(message);
}
}
throw 和 throws 的对比
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |