一、异常介绍
1.异常概述
异常(Exception)指的式程序在 编译或运行过程中 出现的 非正常情况,会导致程序的执行流程被中断。
常见异常示例:
- ArrayIndexOutOfBoundsException:数组索引越界异常
- ClassCastException:类型转换异常
- NullPointerException:空指针异常
- ArithmeticException:算术异常(如除以0)
注意:语法错误不是异常!
语法错误在编译阶段无法通过,而异常时在编译通过后,程序执行时才发生的错误。
2.异常体系结构
Java 中所有的异常都是类,这些异常类存在继承体系关系,关系图如下

所有的异常都继承了 Throwable,在这个类下面分为两个派系
2.1.Error(错误)
代表系统级别的严重问题,程序无法处理。
常见:
- StackOverflowError:栈内存溢出(递归调用过多)
- OutOfMemoryError:堆内存溢出(对象太多或内存不足)
这些错误一般与程序逻辑无关,而是系统资源问题。
// StackOverflowError - 栈内存溢出
public static void recursiveMethod() {
recursiveMethod(); // 无限递归
}
// OutOfMemoryError - 堆内存溢出
public static void causeOOM() {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new Object[1000000]);
}
}
2.2.Exception(异常)
程序可以通过代码处理的异常。
2.2.1.运行时异常(RuntimeException及其子类)
- 在编译阶段不会报错,但在运行时可能出现
- 通常由程序员的疏忽或逻辑错误造成
- 如果不处理,程序会中断执行
常见类型:
- ArrayIndexOutOfBoundsException:数组下标越界
- NullPointerException:空指针异常
- ClassCastException:类型转换异常
- ArithmeticException:算术错误(如除零)
public class RuntimeExceptionsDemo {
public static void main(String[] args) {
// 1. 数组索引越界异常
int[] arr = {1, 2, 3};
// System.out.println(arr[5]); // ArrayIndexOutOfBoundsException
// 2. 空指针异常
String str = null;
// System.out.println(str.length()); // NullPointerException
// 3. 类型转换异常
Object obj = "Hello";
// Integer num = (Integer) obj; // ClassCastException
// 4. 数字格式异常
// int num = Integer.parseInt("abc"); // NumberFormatException
}
}
2.2.2.编译时异常(Checked Exception)
- 在编译阶段必须进行处理(否则编译不通过)
- 提醒程序员:这里有可能出错,需要预防措施
例如:
FileReader fr = new FileReader("D:\\A.txt"); // FileNotFoundException 编译时异常
3.异常的处理方式
3.1.抛出异常(throws)
如果一个方法中可能出现编译时异常,可以使用 throws 将异常交给调用者处理:
public void readFile() throws FileNotFoundException {
FileReader fr = new FileReader("D:\\A.txt");
}
3.2.try...catch捕获异常
使用 try...catch 在程序中捕获并处理异常:
try {
FileReader fr = new FileReader("D:\\A.txt");
} catch (FileNotFoundException e) {
System.out.println("文件未找到,请检查路径!");
}
二、Java程序异常默认处理流程
Java 程序遇到异常,默认是抛出处理
public class ExceptionDemo1 {
/*
异常默认的处理方式: 向上抛出
*/
public static void main(String[] args) {
System.out.println("main方法开始执行...");
method(); // ③ main方法接收到异常对象, 继续向上抛出 new ArithmeticException();
// ④ JVM虚拟机接收到异常对象, 将异常的错误信息打印在控制台, 将程序停止.
System.out.println("main方法执行结束...");
}
private static void method(){
System.out.println("method方法开始....");
int i=1/0; // ① 会在出现异常的位置, 创建一个异常对象 new ArithmeticException();
// ② 将异常对象向上抛出, 抛给main方法
System.out.println("method方法结束....");
}
}
执行流程图:
method() 执行到 int i = 1 / 0;
↓
JVM 创建 ArithmeticException 对象
↓
method() 没有 try...catch → 异常抛给 main()
↓
main() 也没处理 → 异常抛给 JVM
↓
JVM 打印异常信息
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.itheima.exception.ExceptionDemo1.method(ExceptionDemo1.java:...)
at com.itheima.exception.ExceptionDemo1.main(ExceptionDemo1.java:...)
↓
程序终止
小结:
如果程序没有手动处理异常,JVM 默认会帮你输出错误信息并终止程序。
想让程序“不中断”,就需要使用 try...catch 来捕获异常。
运行结果:

三、异常处理方式1-try...catch
try...catch 是最常用的异常处理方式,用来 捕获异常对象 并执行自定义的处理逻辑。
public class ExceptionDemo2 {
/*
异常处理方式1
try {
...
} catch (异常类名 对象名) {
...
}
执行流程:
1. 执行try语句中的代码, 看是否有异常发生
2. 有的话, catch捕获异常, 执行内部的异常处理代码
3. 没有的话, 不会执行catch内部代码, 程序继续执行.
*/
public static void main(String[] args) {
System.out.println("开始");
try {
int i=1/1;
int[] arr= new int[10];
System.out.println(arr[10]);
}catch (ArithmeticException e){ // ArithmeticException e = new ArithmeticException();
System.out.println("捕获了运算异常...");
}catch (NullPointerException e){ // NullPointerException e = new NullPointerException();
System.out.println("捕获了空指针异常...");
}catch (Exception e){ // Exception e = new ArrayIndexOutOfBoundsException();
System.out.println("捕获了其它异常...");
}
System.out.println("结束");
}
}
执行流程:
程序执行流程:
1. 输出"开始"
2. 进入try块
- 执行 int i = 1 / 1; // 正常,不会抛出异常
- 执行 System.out.println(arr[10]); // 抛出ArrayIndexOutOfBoundsException
3. 异常被捕获:
- 第一个catch:ArithmeticException - 不匹配
- 第二个catch:NullPointerException - 不匹配
- 第三个catch:Exception - 匹配!执行catch块内代码
4. 输出"捕获了其它异常..."
5. 输出"结束"
结果如下:

四、练习题目
需求:键盘录入学生的姓名和年龄,封装为学生对象
学生的年龄是整数,用户在输入的时候有可能输入非数字的内容,这就会引发程序出错
解决方案:以字符串的形式录入年龄,随后通过 int age = Integer.parseInt("23"); 进行转换
转换期间如果出现问题,将会出现 NumberFormartException,我们使用 try...catch 进行捕获
Integer 类的静态方法 parseInt(); 可以将传入的数字字符串,转换为 int 类型的数字。
import java.util.Scanner;
public class ExceptionTest {
public static void main(String[] args) {
Scanner sr= new Scanner(System.in);
Student stu = new Student();
System.out.println("请输入学生姓名: ");
String name = sr.next();
stu.setName(name);
while (true){
try {
System.out.println("请输入学生年龄:");
int age = Integer.parseInt(sr.next());
stu.setAge(age);
break;
} catch (NumberFormatException e){
System.out.println("您输入的年龄有误, 请检查!");
}
}
System.out.println(stu);
}
}
五、异常处理方式2-throws抛出
1.throws的作用
throws用于 方法声明 上,作用“声明此方法中可能抛出的异常”,让调用者决定如何处理这些异常。
语法格式:
public void method() throws 异常类型1, 异常类型2, 异常类型3... {
// 方法体
}
特点:
- 只负责声明异常,不做真正的处理。
- 如果方法中发生了异常,异常会被“抛给调用者”。
- 调用者可以选择:
- 继续throws抛出;
- 用 try...catch捕获
2.示例
package com.models.exception;
import java.io.FileNotFoundException;
import java.io.FileReader;
public class ExceptionDemo3 {
/*
异常处理方式2: 抛出异常
throws: 用在方法上, 作用是声明, 声明这个方法中的异常是抛出处理.
在继承关系中, 子类重写父类方法, 不能抛出父类中不存在的, 或者比父类更大的异常.
*/
public static void main(String[] args) throws Exception {
method();
}
public static void method() throws FileNotFoundException, ClassNotFoundException {
System.out.println("开始");
// FileReader fr=new FileReader("D:\\\\abc.txt"); //FileNotFoundException找不到对应的文件使用报错
System.out.println("结束");
// Class.forName("com.models.pojo.Student"); //这个也报错表示 JVM 没有找到指定的类
Class.forName("com.models.exception.ExceptionDemo3"); //改成这个就不会报错
}
}
上面结果是:

执行分析:
- method()内部发生编译时异常;
- 方法声明中使用 throws 把异常抛出;
- main() 方法接收到异常,继续 throws 抛出;
- 最终交给JVM默认处理。
3. throws 在继承中的使用规则
class Fu {
public void show() throws Exception {
System.out.println("Fu...show...");
}
}
class Zi extends Fu {
@Override
public void show() throws Exception {
FileReader fr = new FileReader("D:\\abc.txt");
Class.forName("com.models.pojo.Student");
}
}
// 错误示例
class Zi2 extends Fu {
@Override
public void show() throws IOException, SQLException {
// 编译错误:不能抛出比父类更大的异常或多个异常
}
}
规则说明:
- 子类重写父类方法时:
- 不能抛出比父类方法更大的异常;
- 可以抛出更小的异常或不抛出
4.throw抛出异常对象
throw 关键字不同于 throws,它是用在方法中,后面跟的是异常对象,是真正抛出异常对象的关键字
throws 用在方法声明上,只起到声明作用,声明方法中的异常如果真的爆发了,就继续向上抛出
public class Student {
private String name;
private int age;
public void setAge(int age) {
if (age >= 0 && age <= 100) {
this.age = age;
} else {
// 抛出运行时异常,不需要throws声明
throw new IllegalArgumentException("年龄有误,请检查是否是0~100之间的!");
}
}
public void setAge2(int age) throws Exception {
if (age >= 0 && age <= 100) {
this.age = age;
} else {
// 抛出编译时异常,必须使用throws声明
throw new Exception("年龄有误,请检查是否是0~100之间的!");
}
}
}
细节 :
抛出的异常对象如果是编译时异常, 必须使用 throws 声明
如果是运行时异常, 则不需要写 throws
5.throw与throws的区别
| 特性 | throw | throws |
| 位置 | 方法内部 | 方法声明处 |
| 作用 | 真正抛出异常对象 | 声明可能抛出的异常类型 |
| 数量 | 一次只能抛出一个异常对象 | 可以声明多个异常类型 |
| 语法 | throw new Exception(); | void method() throws Exception |
六、自定义异常
1.自定义异常的作用
-
Java无法为所有业务问题提供异常类
-
企业需要通过异常方式来管理特定业务问题
-
提供更明确的异常信息和类型
通俗解释:刚刚的练习中,如果年龄错误,我们抛出的是 Exception 异常,但这个异常类型太大了,不能专门表示学生的年龄异常,需要一个 StudentAgeException 类来专门表示这种错误,Java 中没有这种类,我们需要自己定义。
2.自定义编译时异常
- 定义一个异常类继承Exception
- 重写构造器
// 自定义编译时异常:姓名格式非法
public class StudentNameFormatException extends Exception {
public StudentNameFormatException() {
}
public StudentNameFormatException(String message) {
super(message);
}
}
3.自定义运行时异常
- 定义一个异常类继承RuntimeException。
- 重写构造器
// 自定义运行时异常:年龄非法
public class StudentAgeException extends RuntimeException {
public StudentAgeException() {
}
public StudentAgeException(String message) {
super(message);
}
}
例子:
import StudentAgeException;
import StudentNameFormatException;
public class Student {
private String name;
private int age;
public Student() {}
public Student(String name, int age) throws StudentNameFormatException {
setName(name);
setAge(age);
}
public String getName() {
return name;
}
// 注意:这里声明抛出编译时异常
public void setName(String name) throws StudentNameFormatException {
if (name != null && name.matches("[\\u4e00-\\u9fa5]{2,4}")) {
this.name = name;
} else {
throw new StudentNameFormatException("姓名格式有误!必须是2~4个中文字符。");
}
}
public int getAge() {
return age;
}
// 注意:运行时异常不用声明 throws
public void setAge(int age) {
if (age >= 0 && age <= 100) {
this.age = age;
} else {
throw new StudentAgeException("年龄有误!必须在0~100之间。");
}
}
@Override
public String toString() {
return "学生信息:姓名 = " + name + ",年龄 = " + age;
}
}
import StudentAgeException;
import StudentNameFormatException;
import Student;
import java.util.Scanner;
public class ExceptionTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Student stu = new Student();
while (true) {
try {
System.out.println("请输入学生姓名(2~4个中文字符):");
String name = sc.next();
stu.setName(name); // 可能抛出 StudentNameFormatException
break;
} catch (StudentNameFormatException e) {
System.out.println(e.getMessage());
}
}
while (true) {
try {
System.out.println("请输入学生年龄(0~100):");
int age = Integer.parseInt(sc.next());
stu.setAge(age); // 可能抛出 StudentAgeException
break;
} catch (NumberFormatException e) {
System.out.println("输入的年龄不是数字,请重新输入!");
} catch (StudentAgeException e) {
System.out.println(e.getMessage());
}
}
System.out.println(stu);
}
}

2137

被折叠的 条评论
为什么被折叠?



