异常_Java学习笔记

一、异常介绍

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"); //改成这个就不会报错
    }

}

上面结果是:

执行分析:

  1. method()内部发生编译时异常;
  2. 方法声明中使用 throws 把异常抛出;
  3. main() 方法接收到异常,继续 throws 抛出;
  4. 最终交给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的区别

特性throwthrows
位置方法内部方法声明处
作用真正抛出异常对象声明可能抛出的异常类型
数量一次只能抛出一个异常对象可以声明多个异常类型
语法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);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值