郑州市网站建设_网站建设公司_jQuery_seo优化
2025/12/23 23:12:04 网站建设 项目流程

异常(Exception)

异常概述

什么是程序的异常

在 Java 中,异常是指程序在运行过程中发生的非正常情况,它会中断程序的正常执行流程,例如:除零、数组越界、空指针访问、文件不存在等。

异常的抛出机制

Java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理;如果没有捕获这个异常对象,那么这个异常对象将会导致程序终止。

对待异常

提前预防、及时处理、避免程序崩溃

Java异常体系

概览

Java 中所有异常和错误都继承自java.lang.Throwable

Throwable├──Error(严重错误,程序通常无法处理,非受检异常) │ ├──VirtualMachineError│ │ ├──OutOfMemoryError│ │ └──StackOverflowError│ ├──LinkageError│ └──AssertionError│ └──Exception(程序可处理的异常) ├──RuntimeException(非受检异常) │ ├──NullPointerException│ ├──ArithmeticException│ ├──ClassCastException│ ├──IndexOutOfBoundsException│ ├──ArrayIndexOutOfBoundsException│ ├──StringIndexOutOfBoundsException│ └──IllegalArgumentException│ └──NumberFormatException│ └── ├──IOException│ ├──FileNotFoundException│ └──EOFException│ └──...├──SQLException├──ClassNotFoundException├──InterruptedException└──ParseException

Throwable

表示所有可以被抛出和捕获的异常或错误,只有Throwable及其子类才能被throw,只有Throwable才能被catch

常用方法

方法说明
getMessage()返回异常的详细信息
toString()返回异常类名 + 信息
printStackTrace()打印异常堆栈信息(常用)
getCause()获取引发该异常的原因
fillInStackTrace()记录堆栈信息
getStackTrace()获取堆栈元素数组

Error 和 Exception

Throwable 可分为两类:Error 和 Exception。分别对应着java.lang.Errorjava.lang.Exception两个类。

Error:JVM无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。

  • 例如:StackOverflowError(栈内存溢出)和 OutOfMemoryError(堆内存溢出,简称OOM)。

Exception:其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序也会挂掉。

  • 例如:空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界等

编译时异常和运行时异常

Java程序的执行分为编译时过程和运行时过程。有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。

根据异常可能出现的阶段,可以将异常分为:

  • 编译时期异常(即checked异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xxx异常,并明确督促程序员提前编写处理它的代码。如果程序员没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到异常)。
  • 运行时期异常(即runtime异常、unchecked异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了xxx异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。
    • java.lang.RuntimeException类及它的子类都是运行时异常。比如:ArrayIndexOutOfBoundsException 数组下标越界异常,ClassCastException 类型转换异常。

常见的错误和异常

Error

最常见的就是VirtualMachineError,其两个经典的子类:StackOverflowErrorOutOfMemoryError

方法调用层级过深(通常是无限递归),每次调用都会在线程栈中压入栈帧,最终栈空间耗尽。

publicclassStackOverflowDemo{publicstaticvoidmain(String[]args){recursive();}publicstaticvoidrecursive(){recursive();// 没有终止条件}}

内存溢出,JVM 某一块内存区域(最常见是堆内存)被耗尽,无法再分配对象。不断创建对象并保存引用,GC 无法回收。

importjava.util.ArrayList;importjava.util.List;publicclassOOMDemo{publicstaticvoidmain(String[]args){List<byte[]>list=newArrayList<>();while(true){list.add(newbyte[1024*1024]);// 每次分配 1MB}}}

运行时异常

空指针异常

Stringstr=null;System.out.println(str.length());// NullPointerException

数组越界

int[]arr=newint[5];for(inti=1;i<=5;i++){System.out.println(arr[i]);// ArrayIndexOutOfBoundsException}

编译时异常

ClassNotFoundException

当 JVM 通过类名动态加载类时(如反射),如果在 classpath 中找不到该类,就会抛出此异常。虽然发生在运行时,但它是编译时异常。

publicclassTest{publicstaticvoidmain(String[]args){try{Class.forName("com.example.NotExistClass");}catch(ClassNotFoundExceptione){e.printStackTrace();}}}

FileNotFoundException

当尝试打开一个不存在的文件时抛出

importjava.io.FileInputStream;importjava.io.FileNotFoundException;publicclassTest{publicstaticvoidmain(String[]args){try{FileInputStreamfis=newFileInputStream("a.txt");}catch(FileNotFoundExceptione){e.printStackTrace();}}}

IOException

表示在读写数据过程中发生的异常

importjava.io.FileInputStream;importjava.io.IOException;publicclassTest{publicstaticvoidmain(String[]args){try{FileInputStreamfis=newFileInputStream("a.txt");intdata=fis.read();fis.close();}catch(IOExceptione){e.printStackTrace();}}}

异常的处理

概述

Java 进行异常处理的主要目的是:提高程序的健壮性、可维护性和安全性。

具体体现在:

  • 防止程序异常终止

程序在运行过程中可能出现各种错误(如数组越界、空指针、文件不存在等),如果不处理异常,程序会直接崩溃。

  • 将正常逻辑与错误处理逻辑分离

异常处理机制使代码结构更清晰,便于阅读和维护。

  • 提供统一的错误处理机制

通过异常类型和异常链,开发者可以精确定位错误原因。

  • 增强程序的容错能力

允许程序在发生错误时采取补救措施,而不是直接终止。

Java 异常处理方式

使用try-catch-finally进行捕获处理
  • Java程序的执行过程中如果出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常
  • 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常
  • 如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。

基本格式

try{...// 可能产生异常的代码}catch(异常类型1e){...// 当产生异常类型1型异常时的处置措施}catch(异常类型2e){...// 当产生异常类型2型异常时的处置措施}[...]finally{...// 无论是否发生异常,都无条件执行的语句}

执行过程

在 Java 中,对于可能在执行过程中抛出异常的代码,无论该异常是受检异常还是非受检异常,都可以使用try语句块对其进行包裹,并在其后定义一个或多个catch子句,用于捕获并处理特定类型的异常对象。

  • 如果程序在运行过程中,try语句块中的所有代码均正常执行且未抛出任何异常,那么所有catch子句都不会被执行;程序的执行流程将直接跳过整个try-catch结构,继续向下执行其后的代码。

  • 如果程序在运行过程中,try语句块中的某条语句抛出了异常对象,则:

    • JVM 会立即终止try块中当前异常语句之后的代码执行;

    • JVM 会按照catch子句的书写顺序(自上而下),依次判断异常对象的实际类型是否与catch中声明的异常类型匹配(包括父类匹配);⚠️若异常类型有父子关系,必须保证子异常类型在上,父异常类型在下;

    • 一旦找到第一个能够匹配该异常类型的catch子句,便执行该catch中的异常处理代码;

    • 执行完匹配的catch子句后,程序将继续执行整个try-catch结构之后的代码。

  • 如果程序在运行过程中,try语句块中抛出了异常对象,但:所有catch子句中声明的异常类型都无法匹配该异常对象,则:

    • JVM 将认为该异常在当前方法中未被处理;
    • 当前方法的执行将被立即终止;
    • 该异常对象会被 JVM 沿着方法调用栈向上抛出(异常传播),交由该方法的调用者处理;
    • 如果调用者也未对该异常进行处理,则异常会继续向上传播;
    • 若最终传播至虚拟机顶层仍未被捕获,JVM 将执行默认异常处理机制,导致程序异常终止(即程序“挂掉”)。

有关 finally

在 Java 的异常处理机制中,由于异常的发生会导致程序控制流发生转移,某些语句可能无法被正常执行。然而,在实际开发中,往往存在一些无论是否发生异常都必须执行的清理或释放操作,例如:关闭数据库连接、释放 I/O 流资源、断开 Socket 连接、释放锁(Lock)等。此类代码通常应放置在finally代码块中,以确保其执行。

需要注意的是,finally代码块在绝大多数情况下都会被执行,唯一的例外是显式调用System.exit(0)终止当前正在运行的 Java 虚拟机,此时程序会立即结束,finally块将不会得到执行。

无论try代码块中是否抛出异常,catch语句是否被执行,catch语句中是否再次抛出异常,或者catch语句中是否包含return语句,finally代码块中的语句都会在方法结束前被执行。

在语法结构上,catch语句和finally语句都是可选的,但finally不能单独使用,必须与try语句块配合出现。

声明抛出异常类型throws
  • 核心意义:当前方法不处理异常,而是把异常抛给调用者处理。

基本格式

方法声明中使用 throws

访问修饰符 返回值类型 方法名(参数列表)throws异常类型1,异常类型2,...{// 方法体}

调用该方法的代码必须try-catch捕获异常或继续使用throws向上抛出

使用举例

1、编译时异常

importjava.io.FileReader;importjava.io.IOException;publicclassTest{publicstaticvoidreadFile()throwsIOException{FileReaderfr=newFileReader("a.txt");fr.read();fr.close();}}
publicstaticvoidmain(String[]args){try{readFile();}catch(IOExceptione){e.printStackTrace();}}
  • 不写throws IOException→ 编译报错,编译器强制处理

2、运行时异常

throws 后面也可以写运行时异常类型,只是写或不写对于编译器和程序执行来说都没有任何区别。如果写了,唯一的区别就是调用者调用该方法后,使用 try…catch 结构时,IDEA 可以获得更多的信息,需要添加哪种 catch 分支。

publicclassTest{publicstaticvoiddivide(inta,intb)throwsArithmeticException{System.out.println(a/b);}}
publicstaticvoidmain(String[]args){divide(10,0);// 运行时报异常}
  • throws可写可不写、编译不会报错、异常在运行时抛出

💡方法重写(override)中 throws 的要求

核心规则:子类方法抛出的异常不能“更大”,对于“throws 运行时异常”没有要求。

  • 子类可以不抛异常
  • 子类可以抛父类方法异常的子类
  • 子类不能抛父类方法没有声明的受检异常
  • 子类不能抛比父类更大的异常
classFather{publicvoidtest()throwsIOException{}}
classSonextendsFather{@Overridepublicvoidtest()throwsFileNotFoundException{}}
classSonextendsFather{@Overridepublicvoidtest(){}}
classSonextendsFather{@Overridepublicvoidtest()throwsException{// 编译错误}}

💡接口中的 throws 规则

如果接口方法声明了异常,实现类:

  • 可以不抛异常
  • 只能抛接口方法声明异常的子类
  • 不能抛接口未声明的受检异常
publicinterfaceA{voidmethod()throwsIOException;}
classImplimplementsA{@Overridepublicvoidmethod()throwsFileNotFoundException{}}
classImplimplementsA{@Overridepublicvoidmethod(){}}
classImplimplementsA{@Overridepublicvoidmethod()throwsException{// 编译错误}}
如何选择?

编译期异常处理策略的选择原则

编译期异常(Checked Exception),应根据具体的业务场景和代码结构,选择恰当的异常处理方式,主要包括try-catch-finallythrows两种机制。其选型原则如下:

  1. 涉及系统资源的操作必须就地处理异常

    当程序中涉及对系统资源的访问与使用(如 I/O 流、数据库连接、网络连接等)时,应在当前方法中使用try-catch-finally(或 try-with-resources)进行异常处理,以确保资源在异常或正常执行路径下都能够被正确释放,避免资源泄漏问题。

  2. 方法重写受父类异常声明约束

    若父类方法在声明时未通过throws抛出异常,则子类在重写该方法时,不允许声明新的编译期异常。此时,子类方法中若发生异常,必须通过try-catch-finally方式在方法内部进行处理,而不能继续向上抛出。

  3. 分层调用场景下的异常传递策略

    在实际开发中,若方法a依次调用方法bcd,且bcd之间存在明确的业务递进或逻辑依赖关系,通常采用以下异常处理策略:

    • 在底层方法(如bcd)中,通过throws将异常向上抛出,使异常信息得以完整传递;
    • 在上层方法(如a)中,统一使用try-catch-finally对异常进行集中处理,以便进行日志记录、事务回滚、异常封装或用户提示等操作。

手动抛出异常对象throw

Java 中异常对象的生成方式(两种)

1、JVM 自动生成并抛出异常

当程序在运行过程中出现不合法操作时,JVM 会自动创建异常对象并抛出。

2、程序员手动生成并抛出异常(throw)

当业务逻辑中发现不满足条件的情况,可以主动创建异常对象并抛出。

使用格式

thrownew异常类名(参数);

throw语句抛出的异常对象,和 JVM 自动创建和抛出的异常对象一样。

  • 如果是编译时异常类型的对象,必须在方法上声明throws或者在方法中使用try-catch捕获,否则编译错误。

  • 如果是运行时异常类型的对象,可以直接抛出,不要求在方法签名中用throws声明。

  • 可以抛出的异常必须是Throwable或其子类的实例。否则编译错误。

使用注意

无论是编译时异常还是运行时异常,如果在程序执行过程中未被try...catch结构合理捕获并处理,异常将沿着调用栈向上传播,最终可能导致当前线程终止,程序异常结束。

throw语句用于显式抛出一个异常对象,它会立即改变程序的正常执行流程。一旦执行到throw语句,当前代码块中位于其后的语句将不再被执行,例如:

thrownewRuntimeException("错误");System.out.println("这行不会执行");

当某个方法内部通过throw抛出了异常,而该方法自身并未使用try...catch对异常进行处理时,throw的行为等价于提前结束方法执行。与return不同的是,throw并非返回一个普通结果,而是将异常对象抛给方法的调用者,由调用者决定是否捕获和处理该异常。

在实际开发中,throw通常与条件判断语句配合使用,用于在程序检测到不符合业务或逻辑约束的情况时,主动中断当前执行流程并报告错误,例如:

if(user==null){thrownewNullPointerException("用户不能为空");}

此外,开发者可以通过继承Exception或其子类来自定义异常类型,以更准确地表达业务语义。自定义异常可以通过throw语句抛出,并在方法签名中使用throws声明该异常的传播行为,例如:

classMyExceptionextendsException{publicMyException(Stringmessage){super(message);}}voidtest()throwsMyException{thrownewMyException("自定义异常");}

这种机制有助于提高程序的健壮性、可读性以及异常处理的规范性,使异常信息更符合实际业务场景。

throws 与 throw

throws:在方法声明处声明可能抛出的异常

throw:在方法体内主动的(显示的、手动)抛出异常

自定义异常

意义

1️⃣ 提高代码可读性和语义表达

使用系统异常(如RuntimeExceptionException)往往语义不明确:

thrownewRuntimeException("error");

自定义异常可以清楚表达业务含义:

thrownewUserNotFoundException("用户不存在");

👉 一看异常名就知道问题是什么


2️⃣ 区分业务异常与系统异常

  • 系统异常:空指针、数组越界、IO 异常等(JDK 已提供)
  • 业务异常:余额不足、订单不存在、权限不足等(需自定义)

3️⃣ 统一异常处理

在 Spring / Web 项目中,自定义异常便于:

  • 统一捕获
  • 统一返回错误码和错误信息
  • 统一日志处理

4️⃣ 方便异常扩展

后期可以给异常加上:

  • 错误码
  • 业务状态
  • 额外上下文信息

如何自定义异常类

  • 继承一个异常类型

​ 自定义一个编译时异常类型:自定义类继承java.lang.Exception

​ 自定义一个运行时异常类型:自定义类继承java.lang.RuntimeException

  • 推荐提供至少两个构造器,一个是无参构造,一个是(String message)构造器。推荐增加错误码字段。

  • 自定义异常需要提供serialVersionUID

注意点

  • 自定义的异常对象只能通过 throw 手动抛出。抛出后由 try…catch 处理,也可以 throws 给调用者处理。
  • 自定义异常最重要的是异常类的名字和 message 属性。当异常出现时,可以根据名字判断异常类型。比如:TeamException("成员已满,无法添加");TeamException("该员工已是某团队成员");

举例

publicclassAgeExceptionextendsRuntimeException{publicAgeException(Stringmessage){super(message);}}
publicclassPerson{publicstaticvoidsetAge(intage){if(age<0||age>150){// 手动抛出自定义异常thrownewAgeException("年龄不合法:"+age);}System.out.println("年龄设置成功:"+age);}publicstaticvoidmain(String[]args){try{setAge(200);}catch(AgeExceptione){// try...catch 处理异常System.out.println("捕获异常:"+e.getMessage());}}}
publicclassBalanceNotEnoughExceptionextendsException{publicBalanceNotEnoughException(Stringmessage){super(message);}}
publicclassAccount{privatestaticintbalance=100;// 方法声明 throws,把异常交给调用者处理publicstaticvoidwithdraw(intmoney)throwsBalanceNotEnoughException{if(money>balance){thrownewBalanceNotEnoughException("余额不足,当前余额:"+balance);}balance-=money;System.out.println("取款成功,剩余余额:"+balance);}}
publicclassTestAccount{publicstaticvoidmain(String[]args){try{Account.withdraw(200);}catch(BalanceNotEnoughExceptione){System.out.println("取款失败:"+e.getMessage());}}}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询