目录
3.1.3 ArrayIndexOutOfBoundsException
1.异常概述
1.1 生活中的异常
小明每天开车上班,正常车程 1 小时,但是可能会出意外情况。比如说堵车了、车没油了、出现交通事故了等等
出现意外,即为异常情况。如果不处理异常情况,则到不了公司。所以需要对不同的异常情况做相应的处理,处理完后则可以正常开车去公司
1.2 程序的异常
异常 :指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致 JVM 的非正常停止
在使用计算机语言进行开发的过程中,即使程序员把代码写得 "尽善尽美" ,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式问题,读取文件是否存在,网络是否始终保持通畅等等
补充:异常指的并不是语法错误和逻辑错误
语法错误:编译不通过,不会产生 .class 字节码文件,不能执行程序
逻辑错误:只是没有得到想要的结果,例如:求 a 与 b 的和 a+b,写成了 a-b
1.3 异常的抛出机制
Java中是如何表示不同的异常情况,又是如何让程序员得知,并处理异常的呢?
Java 中把不同的异常用不同的类表示,一旦发生某种异常,就会创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获 (catch)到这个异常对象,并处理;如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止
代码案例
下面的代码会产生一个数组下标越界异常 ArrayIndexOfBoundsException 。通过图解来解析异常产生和抛出的过程
class ArrayTools{
//对给定的数组通过给定的下标获取元素
public static int getElement(int[] arr,int index){
int element = arr[index];
return element;
}
}
class ExceptionDemo{
public static void main(String[] args){
int[] arr = {34,12,67};
int num = ArrayTools.getElement(arr,4);
System.out.println("num="+num);
System.out.println("over");
}
}
图解异常产生和抛出的过程
1.4 如何对待异常
对于程序出现的异常,一般有两种解决方法:
① 遇到错误就终止程序的运行,但这样处理系统健壮性太差
② 程序员在编写程序时,就充分考虑到各种可能发生的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常的检测、以及异常的处理,保证代码的健壮性。Java 中通常采用的处理异常方式: throws+异常类型、 try-catch-finally块
2.异常体系
2.1 异常体系图
2.2 Throwable
java.lang.Throwable 类是 Java 程序执行过程中发生的异常事件对应的类的根父类
Throwable中的常用方法:
① public void printStackTrace():打印异常的详细信息
包含了异常的类型、异常的原因、异常出现的位置、在开发和调试阶段都得使用 printStackTrace
② public String getMessage():返回异常的字符串描述信息
2.3 异常分类
Java 中的异常情况可分为两个大类:Error 和 Exception,分别对应着 java.lang.Error 与java.lang.Exception 两个类
① Error:Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。比如栈溢出 StackOverflowError 和 内存不足OOM(out of memory)。Error 是严重错误,程序会直接崩溃,所以一般不编写针对性的代码进行处理
② Exception:因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序也会挂掉。例如:空指针访问、试图读取不存在的文件、网络连接中断、数组下标越界等等
Exception又包含两类:编译时异常【编程时,编译器检测出的异常】和运行时异常【程序执行时,发生的异常】
2.3 常见的Error
最常见的就是 VirtualMachineError,它有 2 个经典的子类:
① StackOverflowError:栈溢出
② OutOfMemoryError:内存不足
2.3.1 StatckOverflowError
案例
public class demo {
public static void main(String[] args) {
new TestStackOverflowError().recursion();
}
}
class TestStackOverflowError {
public void recursion(){ //递归方法
recursion();
}
}
执行结果
2.3.2 OutOfMemoryError
案例
public class demo{
public static void main(String[] args) {
//OutOfMemoryError
int[] arr = new int[Integer.MAX_VALUE];
}
}
执行结果
2.4 编译时异常
编译时异常:也叫 checked(受检) 异常
① 在编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xx异常,并要求程序员必须提前编写处理该异常的代码。如果没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码 .class 文件。
② 编译时异常的发生通常不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到时发生的异常)
代码案例
import java.io.FileInputStream;
public class demo {
public static void main(String[] args) {
FileInputStream fis;
fis = new FileInputStream("d:\\aa.jpg");
int len;
while ((len = fis.read()) != -1){
System.out.println(len);
}
fis.close();
}
}
解决方案
2.5 运行时异常
运行时异常:也叫 unchecked(非受检) 异常
① 在代码编写阶段,运行时异常编译器检查不出来。只有等代码运行起来并确实发生了xx异常,它才能被发现
② 运行时异常通常是由程序员的代码编写不当引起的,只要细心检查就可以避免
③ java.lang.RuntimeException 类及它的子类都是运行时异常。对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
代码案例
最经典的运行时异常案例:两数相除,分母为 0 ,发生 ArithmeticException 异常
public class demo {
public static void main(String[] args) {
int num1 = 100;
int num2 = 0;
System.out.println(num1/num2);
}
}
/*控制台输出
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/
3.常见的运行时异常、编译时异常
3.1 运行时异常
① NullPointerException:空指针异常
② ArithmeticException:数学运算异常
③ ArrayIndexOutOfBoundsException:数组下标越界异常
④ ClassCastException:类型转换异常
⑤ NumberFormatException:数字格式不正确异常
3.1.1 NullPointerException
NullPointerException:空指针异常
发生时机:当程序试图在需要对象的地方使用 null 时,抛出该异常
案例
3.1.2 ArithmeticException
ArithmeticException:数学运算异常
发生时机:当出现异常的运算条件时,抛出该异常。最经典的例子就是两数相除,分母为 0
案例
3.1.3 ArrayIndexOutOfBoundsException
ArrayIndexOutOfBoundsException:数组下标越界异常
发生时机:用非法索引访问数组时抛出的异常。非法索引:索引为负或者大于等于数组大小
案例
3.1.4 ClassCastException
ClassCastException:类型转换异常
发生时机:当试图将对象强制转换为不是实例的子类时,抛出该异常
案例
3.1.5 NumberFormatException
NumberFormatException:数字格式不正确异常
发生时机:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
案例
3.2 编译时异常
① SQLException:操作数据库时,查询表可能发生的异常
② IOException:操作文件时,可能发生的异常
③ FileNotFoundException:找不到文件的异常
④ IllegalArgumentException:参数异常
4.异常的处理机制
4.1 异常处理概述
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行 x/y 运算时,要检测分母是否为0,数据为空,输入的不是数据而是字符等
过多的 if-else 分支会导致程序的代码加长、臃肿,可读性差,程序员需要花很大的精力"堵漏洞"。因此采用异常处理机制
Java异常处理
Java 采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护
Java异常处理方式
① try-catch-finally
② throws + 异常类型
4.2 方式1:try-catch-finally
① 在 "1.3异常的抛出机制" 提到,Java 程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给 Java 运行时系统,这个过程称为:抛出(throw)异常
② 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为:捕获(catch)异常
4.2.1 基本语法
捕获异常语法如下:
try{
...... //可能产生异常的代码
}
catch( 异常类型1 e ){
...... //当产生异常类型1型异常时的处置措施
}
catch( 异常类型2 e ){
...... //当产生异常类型2型异常时的处置措施
}
finally{
...... //无论是否发生异常,finally都会执行
}
4.2.2 整体的执行流程
① 如果在程序运行时,try块 中的代码没有发生异常,那么 catch 所有的分支都不执行
② 如果在程序运行时,try块 中的代码发生异常,根据异常对象的类型,将从上到下选择第一个匹配的 catch 分支执行。此时 try块 中发生异常的语句之后的代码将不执行,而在整个 try-catch块 之后的代码可以继续运行
③ 如果在程序运行时,try块 中的代码发生了异常,如果所有 catch 分支都无法匹配(捕获)这个异常,那么 JVM 将会终止当前方法的执行,并把异常对象抛 (throw) 给调用者。如果调用者不处理,程序终止
4.2.3 try
捕获异常的第一步是用 try{...} 选定捕获异常的范围,将可能出现异常的业务逻辑代码放在 try{...} 中
4.2.4 catch(Exceptiontype e)
① catch(){}分支,分为两个部分, catch() 中编写异常类型和异常形参名, {} 中编写处理该异常的代码
② 如果明确知道产生的是何种异常,可以用该异常类作为 catch 的参数类型,也可以用该异常的父类作为 catch 的参数类型
public class demo {
public static void main(String[] args) {
try{
System.out.println(100/0);//发生ArithmeticException
}catch (RuntimeException e){//catch可以用其父类RuntimeException来接收
//处理异常代码
}
}
}
③ 每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象
④ 如果有多个 catch 分支,并且多个异常类型有父子类关系,必须保证小的子异常类型在上,大的父异常类型在下。否则编译不通过
⑤ catch 中常用异常处理的方式:
-
public String getMessage():返回异常的字符串描述信息
-
public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。包含了异常的类型、异常的原因、异常出现位置,在开发和调试阶段,都得使用 printStackTrace()
4.2.5 使用举例
案例1:数组下标访问越界ArrayIndexOutOfBoundsException
public class demo {
public static void main(String[] args) {
String friends[] = { "lisa", "bily", "kessy" };
try {
System.out.println(friends[5]);//数组下标访问越界,发生异常
System.out.println("异常语句后的代码");
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("index err");
}catch (NullPointerException e){
System.out.println("null err");
}
System.out.println("try-catch块后的代码");
}
}
案例1执行结果
案例2:空指针异常NullPointerException
public class demo{
public static void main(String[] args) {
try{
String str1 = null;
System.out.println(str1.charAt(0));//空指针异常
}catch(NullPointerException e){
//异常的处理方式1
System.out.println("出现了空指针异常");
}catch(ClassCastException e){
//异常的处理方式2
System.out.println("出现了类型转换的异常");
}catch(RuntimeException e){
//异常的处理方式3
System.out.println("出现了运行时异常");
}
System.out.println("try-catch块后的代码");
}
}
案例2执行结果
4.2.6 finally使用及案例
① 因为异常会引发程序跳转,从而会导致有些语句执行不到。而程序中有一些特定的代码无论异常是否发生都需要执行。例如,数据库连接、输入流输出流、Socket连接、Lock锁的关闭等,所以这样的代码就可以放到 finally 中。所以,通常将一定要被执行的代码声明在 finally 中
② finally 语句和 catch 语句是可选的,但 finally、catch 都不能单独出现,只能和 try 配合使用
③ 不论在 try 代码块中是否发生了异常事件,是否有 catch 块, catch 块语句是否执行,catch 语句是否有异常,catch 语句中是否有 return ,finally 块中的语句都会被执行。唯一的例外就是,使用 System.exit(0) 来终止当前正在运行的 JVM ,则 finally 中的代码不会执行
public class demo{
public static void main(String[] args) {
try{
System.exit(0);
}catch (Exception e){
System.out.println("catch");
}finally {
System.out.println("finally");
}
}
}
4.3 方式2:throws+异常
如果一个方法中某句代码可能发生某种异常,但是并不能确定如何处理这种异常,则该方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理
4.3.1 throws处理机制图
4.3.2 基本语法
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{
}
在 throws 后面可以写多个异常类型,用逗号隔开。与 catch 中一致,throws 抛出的异常类型可以是其父类
public void readFile(String file) throws FileNotFoundException,IOException {
...
// 读文件的操作可能产生FileNotFoundException或IOException类型的异常
FileInputStream fis = new FileInputStream(file);
//...
}
4.3.3 使用细节
①(1)对于编译时异常,程序中必须处理,使用 try-catch 或者 throws
(2)如果方法 f1 使用 throws 处理了编译时异常,而 f2 方法调用了 f1 方法,这相当于在 f2 方法中抛出了一个编译时异常,f2 必须使用 try-catch 或者 throws 处理,否则编译不通过
② 对于运行时异常,Java 中是有默认处理机制的:程序中如果没有处理,默认就是 throws 的方式处理
证明如下,两种情况下控制台的输出结果是完全一样的,这也证明②是正确的:
③ 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的类型要么和父类的保持一致,要么为父类抛出的异常类型的子类型。这跟子类重写父类方法时不能缩小方法的访问权限是一个道理
④ 在 throws 过程中,如果有方法使用 try-catch 处理了异常,就可以不必再使用 throws 了。另外异常在此处终止了向上 throws
5.手动抛出异常对象:throw
Java 中异常对象的生成有两种方式:
① 由 JVM 自动生成:程序运行过程中,JVM 检测到程序发生了问题,那么针对当前代码,就会在底层自动创建一个对应的异常类型的实例对象并抛出
② 由开发人员手动创建: throw new 异常类型([实参列表]);
5.1 语法格式
throw new 异常类名(参数);
5.2 使用的注意事项
① throw语句抛出的异常对象,其处理方式如下:
-
如果是编译时异常类型的对象,同样需要使用 throws 或者 try-catch 处理,否则编译不通过
-
如果是运行时异常类型的对象,不做处理编译仍然可以通过
-
可以抛出的异常必须是Throwable或是其子类。下面的语句在编译时将会产生语法错误:
throw new String("want to throw");
② throw 的无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有使用 try-catch 处理,都会导致程序崩溃
③(1)使用 try-catch 处理 throw 的异常:在 try 块中 throw 语句后的代码不执行,finally 中的代码正常执行,finally 后的代码正常执行
public class demo{
public static void main(String[] args){
try{
System.out.println("hello");
throw new RuntimeException("抛出异常");
}catch (RuntimeException e){//上面的throw的异常会被这里的catch捕获到
System.out.println(e.getMessage());//输出"抛出异常"
}finally {
System.out.println("finally代码块");
}
System.out.println("程序继续执行");
}
}
(2)没有使用 try-catch 处理 throw 的异常:throw 语句后不能编写代码,否则编译不通过
(3)使用 throws 处理 throw 的异常:throw 语句后仍然不能编写代码,否则编译不通过
④ 如果当前方法没有使用 try-catch 处理 throw 的异常,throw 语句就会代替 return 语句提前终止当前方法的执行,并返回一个异常对象给调用者
6.自定义异常
6.1 为什么需要自定义异常
Java 中不同的异常类,分别表示着某一种具体的异常情况
但是在开发中总是有些异常情况是在 Throwable 及其子类中没有定义好的,此时需要根据自己业务的异常情况来定义异常类。例如年龄范围问题,考试成绩负数问题,某员工已在团队中等
6.2 如何自定义异常类
① 要选择继承一个异常类型
-
自定义一个编译时异常类型:自定义类继承 java.lang.Exception
-
自定义一个运行时异常类型:自定义类继承 java.lang.RuntimeException
② 建议提供至少两个构造器,一个是无参构造,一个是(String message)构造器
③ 自定义异常需要提供 serialVersionUID
在6.4部分会举案例
6.3 注意事项
① 自定义的异常只能通过 throw 抛出
② 自定义异常最重要的是异常类的名字和 message 属性。message 继承认于父类,用来描述异常原因。当异常出现时,可以输出异常原因。比如:AgeException("年龄不符合正常范围")、GradeException("成绩需为正数")
③ 自定义异常对象只能由程序员用 throw 手动抛出。抛出后由 try-catch 处理,也可以甩锅 throws 给调用者处理
6.4 案例
① 使用 throws 处理异常,throw 后的所有代码不执行
public class demo {
public static void main(String args[]) throws MyException {
int age = 130;
//要求age范围在18-120之间,否则抛出一个自定义异常
if(!(age>=18&&age<=120)){
throw new MyException("年龄需要在18-120之间");
}
System.out.println("程序继续执行");
}
}
//自定义一个编译时异常类
class MyException extends Exception {
static final long serialVersionUID = 23423423435L;
public MyException(){}
public MyException(String message) {
super(message);
}
}
② 使用 try-catch-finally 处理异常,try-catch-finally 后的代码正常执行
public class demo {
public static void main(String args[]){
try{
int age = 130;
//要求age范围在18-120之间,否则抛出一个自定义异常
if(!(age>=18&&age<=120)){
throw new MyException("年龄需要在18-120之间");
}
}catch (Exception e){//上面抛出的异常会被catch捕获,e.getMessage = "年龄需要在18-120之间"
System.out.println(e.getMessage());
}finally{
System.out.println("finally");
}
System.out.println("程序继续执行");
}
}
//自定义一个编译时异常类
class MyException extends Exception {
static final long serialVersionUID = 23423423435L;
public MyException(){}
public MyException(String message) {
super(message);
}
}
7.throws和throw的对比
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
throws | 处理异常的方式之一 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象 | 方法体中 | 异常对象 |
8.练习题
8.1 练习题1
写出程序结果
public class ReturnExceptionDemo {
public static void main(String[] args) {
try {
methodA();
} catch (Exception e) {//A方法throw的异常被这个catch块捕获,e.getMessage = "制造异常"
System.out.println(e.getMessage());//(3)
}
methodB();
}
static void methodA() {
try {
System.out.println("进入方法A");//(1)
throw new RuntimeException("制造异常");
}finally {
System.out.println("用A方法的finally");//(2)
}
}
static void methodB() {
try {
System.out.println("进入方法B");//(4)
return;
} finally {
System.out.println("调用B方法的finally");//(5)
}
}
}
/*输出结果
进入方法A
用A方法的finally
制造异常
进入方法B
调用B方法的finally
*/
8.2 练习题2
写出程序结果
public class demo {
public static void main(String[] args) {
try{
showExce();
System.out.println("A");
} catch (Exception e) {
System.out.println("B");
}finally {
System.out.println("C");
}
System.out.println("D");
}
public static void showExce() throws Exception{
throw new Exception();
}
}
/*
B
C
D
*/
8.3 练习题3
写出程序结果
public class demo {
public static void main(String[] args) {
try{
func();
System.out.println("A");
} catch (Exception e) {
System.out.println("C");//(2)
}
System.out.println("D");//(3)
}
public static void func(){
try{
throw new RuntimeException();
}finally {
System.out.println("B");//(1)
}
}
}
/*
B
C
D
*/
8.4 练习题4
以下代码是否会发生异常?如果会,是哪种异常?
public class demo {
public static void main(String[] args) {
if (args[4].equals("蔡徐坤")){//❌,发生NullPointerException
System.out.println("唱跳rap篮球");
}else{
System.out.println("哎哟你干嘛");
}
Object o = args[2];//✔,向上转型
Integer i = (Integer) o;//❌,发生classCastException
}
}