Skip to content

异常

概述及体系结构

java
Exception:可以捕获异常,进行解决。
Error:使用代码解决不了的问题,例如内存溢出这类的问题,硬件级别的问题。例如 StackOverflowError,
    使用 null.成员方法

主要记4类常见异常

java
Object下边有Throwable异常,Throwable异常下边分为: Error和Exception,
Exception异常下边有RuntimeException异常,常用到的异常都是RuntimeException异常。

Java中的5种常见异常

image-20250708095015055

Try Catch异常

使用 顶级父类中的 方法打印出出现异常的代码行数。

只有捕获到了匹配到的异常才不会中断异常,如果出现异常的类型没有捕获到,则仍然会中断程序。

java
System.out.println(),黑色颜色的字体是由一个线程处理的。
System.err.println(),红色颜色的字体是由另一个线程处理的。

情况1

使用单个catch捕获异常,如果出现的异常和捕获的异常类型匹配,则可以正常捕获,使得程序可以顺利执行完毕。

如果出现的异常和捕获的异常类型不匹配,则程序将依然中断。

try-catch的使用

java
try代码块中存放可能会出现异常的代码
catch用于捕获异常
    
try 或者 catch 均不能单独使用
try 必须结合 catch  或者 catch -finally  或者  finally

try-catch使用示例1

java
package com.exception.Test01;

import java.util.InputMismatchException;
import java.util.Scanner;

/**
 * try-catch
 * try 代码块中存放可能会出现异常的代码
 * catch用于捕获异常
 * try或者catch均不能单独使用 try必须结合catch或者catch-finally 或者 finally
 *
 * 情况1 :
 * 使用单个catch捕获异常 如果出现的异常和捕获的异常类型匹配 则可以正常捕获
 * 使得程序可以顺利执行完毕 如果出现的异常和捕获的异常类型不匹配 则程序将依然中断
 *
 */
public class TestTryCatch {
    public static void main(String[] args) {
        try{
            Scanner in = new Scanner(System.in);
            System.out.print("请输入被除数:");
            int num1 = in.nextInt();
            System.out.print("请输入除数:");
            int num2 = in.nextInt();
            System.out.println(num1+"/"+ num2 +"="+ num1/ num2);
        }catch(InputMismatchException e){ // 捕获输入不匹配类型的异常
            e.printStackTrace(); // 打印异常堆栈信息
        }
        System.out.println("感谢使用本程序!"); // 捕获到了匹配的异常可以正常运行
    }
}

情况2

使用多个catch 块来捕获异常,按照书写顺序,从前往后匹配,只匹配第一个相符的异常,然后就退出try-catch结构。

多个catch块书写顺序 先写子类 后写 父类。

java
catch(异常类名 参数){
	
}

catch多个异常示例:

java
package com.exception.Test01;

import java.util.InputMismatchException;
import java.util.Scanner;

/**
 * 使用多个catch 块来捕获异常 按照书写顺序 从前往后匹配 只匹配第一个相符的异常.然后就退出try-catch结构
 *
 */
public class TestTryCatch2 {
    public static void main(String[] args) {
        try{
            Scanner in = new Scanner(System.in);
            System.out.print("请输入被除数:");
            int num1 = in.nextInt();
            System.out.print("请输入除数:");
            int num2 = in.nextInt();
            System.out.println(num1+"/"+ num2 +"="+ num1/ num2);
        } catch(InputMismatchException e){ // 捕获输入不匹配异常
            System.err.println("出现了输入不匹配异常"); //  e.printStackTrace();
        } catch(ArithmeticException e){ // 捕获被除数为0的异常
            System.err.println(e.getMessage()); // 获取 异常message信息
            e.printStackTrace();//System.err.println("出现了算数运算异常");//
        }catch(Exception e){ // 捕获顶层异常
            e.printStackTrace();
        }

        System.out.println("感谢使用本程序!");
    }
}

finally异常

finally 表示不管是否出现异常 以及 异常是否被捕获 都将执行的代码块。

finally不能单独出现 必须结合 try-catch 或者 try。

因为finally最终都将执行 所以通常用于执行一些关闭资源的操作 比如 关闭流 关闭数据库连接对象等等。

finally不执行的唯一情况:在执行finally之前退出JVM虚拟机。(System.exit(0))

System.exit(int status) : int类型的状态码含义:0表示正常退出,非0表示非正常退出。

finally示例:

java
package com.exception.Test01;

import java.util.InputMismatchException;
import java.util.Scanner;

/**
 *   finally 表示不管是否出现异常 以及 异常是否被捕获 都将执行的代码块
 *    finally不能单独出现 必须结合 try-catch  或者 try
 *    因为finally最终都将执行 所以通常用于执行一些关闭资源的操作 比如 关闭流 关闭数据库连接对象等等
 *
 *    finally不执行的唯一情况:在执行finally之前退出JVM虚拟机
 *
 *    System.exit(int status) : int类型的状态码含义:0表示正常退出 非0表示非正常退出
 */

public class TestFinally {
    public static void main(String[] args) {
        try{
            Scanner in = new Scanner(System.in);
            System.out.print("请输入被除数:");
            int num1 = in.nextInt();
            System.out.print("请输入除数:");
            int num2 = in.nextInt();
            System.out.println(num1+"/"+ num2 +"="+ num1/ num2);
            // 退出虚拟机 System.exit(int status) : int类型的状态码含义:0表示正常退出 非0表示非正常退出
            System.exit(231321);
        }catch (InputMismatchException e){
            e.printStackTrace(); // 打印异常堆栈信息,通过 jdk文档中的 Throwable顶级异常类中的方法找到的
        }finally { // 不管是否出现异常 以及 异常是否被捕获 都将执行的代码块,除非出现 退出虚拟机
            System.out.println("感谢使用本程序!");
        }
    }
}

finally相关问题

try中存在return 是否还执行finally

java
执行

try-catch-finally中,如果try中已经return了值,那么finally中对返回值的操作会不会改变返回值?

java
如果为基本数据类型 不会改变
如果为引用数据类型 会改变

finally return示例

java
package com.exception.Test01;

import java.util.Arrays;

public class TestFinally2 {
    //
    public static int m1(){
        int num = 10; // 基本数据类型
        try{
            num++;  // 11
            return num; // 11 这时方法就出栈了,返回基本数据类型,基本数据类型就是值。
        }catch(Exception e){ // 捕获检查型异常
            e.printStackTrace(); // 打印堆栈异常信息
        }finally{
            num++; // 12 由于存在finally关键字,num会加1,但是已经返回了11,不会返回12
            // return num; // 如果这里返回了 num 则这里 最后a的值为12
        }
        return num; // 11 这个语句不会被执行
    }
    public static int[] m2(){
        int [] nums = {1,2,3,4,5};
        try{
            for (int i = 0; i < nums.length; i++) { // 遍历整个数组,每个元素加1
                nums[i]++;
            }
            return nums; // 返回数组地址,地址不会变
        }catch (Exception e){
            // 打印堆栈中的信息
            e.printStackTrace();
        }finally {
            for (int i = 0; i < nums.length; i++) { // 再次遍历整个数组,每个元素加1
                nums[i]++; // 对数组对象中的内容进行了更改,之前return 的数组地址没有改变,但是内容发生了改变
            }
        }
        // 返回nums 对象
        return nums;
    }
    // 测试类
    public static void main(String[] args) {
        int a = m1(); // 11
        System.out.println(a); // 11

        int [] nums = m2(); // [3,4,5,6,7]
        System.out.println(Arrays.toString(nums)); // [3,4,5,6,7]
    }
}

try-catch块中存在return语句,是否还执行finally块,如果执行,说出执行顺序。

java
执行 finally,执行顺序:先执行return,再执行finally。
return之后,之前的值,看是基本数据类型还是引用数据类型。
    基本数据类型return之后,值不会发生改变。
    引用数据类型return之后,地址不会发生改变,内容会发生改变。

try-catch-finally块中,finally块中唯一不执行的情况是什么?

java
JVM退出虚拟机

throw和throws

throws规则

1、用于在方法声明的位置,参数列表之后,声明当前方法可能会出现哪些异常,可以声明多个异常,

多个异常使用逗号分割。

2、方法调用者根据声明的异常类型不同会做出不同的处理。

3、运行时异常(RuntimeException及其子类):调用者不必处理。

4、检查异常(除了运行时异常以外的其他异常就属于检查异常CheckedException):调用者必须处理。

处理异常的方式

两种处理方式:

1.使用try-catch处理。

2.继续在main方法上声明 属于声明给JVM虚拟机 (其实属于不处理)。

throws示例:

java
package com.throwsPart.Test01;

/**
 * 运行时异常(RuntimeException及其子类):调用者不必处理
 *  *  检查异常(除了运行时异常以外的其他异常就属于检查异常CheckedException):调用者必须处理
 *  *    两种处理方式:
 *  *       1.使用try-catch处理
 *  *       2.继续在main方法上声明 属于声明给JVM虚拟机 (其实属于不处理)
 *
 *  throw:用于在方法体内抛出异常,一句只能抛出一个异常。
 *    根据抛出的异常类型不同 需要做出不同的处理
 *       如果抛出的为运行时异常 则方法上不必声明
 *       如果抛出的为检查异常 则必须在方法上声明
 *
 */

import java.util.InputMismatchException;

public class TestThrows {
    // 通过 throws 声明当前方法可能会出现哪些异常
    // 可以声明多个异常,使用逗号分割
    public static void m1() throws InputMismatchException,ArithmeticException { // 运行时异常(RuntimeException及其子类)
        System.out.println("m1方法执行");
    }
    public static void m2() throws ClassNotFoundException{ // 检查异常
        System.out.println("m2方法执行");
    }
    // 方法调用者根据声明的异常类型不同会做出不同的处理
    public static void main(String[] args) {
        m1(); // 调用m1方法,运行时异常不用处理
        try{
            m2();
        }catch (ClassNotFoundException e){ // 检查异常需要捕获处理
            throw new RuntimeException(e);
        }
    }

}

throw规则

用于在方法体内抛出异常,一句只能抛出一个异常。

根据抛出的异常类型不同 需要做出不同的处理:

  • 如果抛出的为运行时异常 则方法上不必声明
  • 如果抛出的为检查异常 则必须在方法上声明
java
package com.throwsPart.Test01;

/**
 *  throw:用于在方法体内抛出异常,一句只能抛出一个异常。
 *    根据抛出的异常类型不同 需要做出不同的处理
 *       如果抛出的为运行时异常 则方法上不必声明
 *       如果抛出的为检查异常 则必须在方法上声明
 */
public class TestThrow {
    // 抛出的是运行时异常
    public static void m3(int age){
        if(age < 0 || age > 130){
            // 抛出 运行时异常
            throw new RuntimeException("年龄不合法");
        }
    }
    // 抛出的是 检查性异常,需要在方法上做声明
    public static void m4(int age) throws Exception{
        if(age < 0 || age > 130){
            // 抛出 检查型异常
            throw new Exception("年龄不合法");
        }
    }

    public static void main(String[] args) {
        // 调用m3方法
        m3(18);
        // 调用m4方法,检查型异常,必须处理。
        try{
            m4(189);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

自定义异常

当JDK提供的异常不能满足开发需求时,我们可以自定义异常。

自定义异常步骤:

1.继承异常父类 Throwable 、Exception 、RuntimeException 三者其中之一。

2.调用父类中的有参构造完成异常信息的初始化。

自定义异常示例:

java
package com.throwsPart.Test02;

public class Vtuber {
    private String name;
    private int age;
    private char sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    // 使用 Throw 抛出异常
    public void setAge(int age){
        if(age > 0 && age < 130){
            this.age = age;
        }else{
            throw new InputAgeNumOutOfboundsException("年龄不合法" + age);
        }

    }

    public char getSex() {
        return sex;
    }
    // 使用 throws 声明异常
    public void setSex(char sex) throws InputSexException{
        if(sex == '男' || sex == '女'){
            this.sex = sex;
        }else{
            throw new InputSexException("性别不合法" + sex);
        }
    }

    public Vtuber(String name, int age, char sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public Vtuber() {
    }

    @Override
    public String toString() {
        return "Vtuber{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}
java
package com.throwsPart.Test02;
// 检查型异常
public class InputSexException extends Exception{

    public InputSexException(String message) {
        super(message);
    }
}
java
package com.throwsPart.Test02;

// 自定义 异常 继承 异常父类
public class InputAgeNumOutOfboundsException extends RuntimeException{

    // 有参构造完成异常信息的初始化
    public InputAgeNumOutOfboundsException(String message) {
        // 使用父类中构造方法
        super(message);
    }
}
java
package com.throwsPart.Test02;

public class TestVtuber {
    public static void main(String[] args) {
        Vtuber yousa = new Vtuber("sakuna",26,'女');
        yousa.setAge(129);
        try{
            // 对 检查型异常做 catch捕获
            yousa.setSex('女');
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println(yousa.toString());
        System.out.println("程序结束");
    }
}

租车案例

java
package com.Example.RentCar;

// 机动车父类
public abstract class Vehicle {
    private String brand;
    private String vehicleId;
    private double dayRent;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getVehicleId() {
        return vehicleId;
    }

    public void setVehicleId(String vehicleId) {
        this.vehicleId = vehicleId;
    }

    public double getDayRent() {
        return dayRent;
    }

    public void setDayRent(double dayRent) {
        this.dayRent = dayRent;
    }

    public Vehicle(String brand, String vehicleId, double dayRent) {
        this.brand = brand;
        this.vehicleId = vehicleId;
        this.dayRent = dayRent;
    }

    public Vehicle() {

    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "brand='" + brand + '\'' +
                ", vehicleId='" + vehicleId + '\'' +
                ", dayRent=" + dayRent +
                '}';
    }

    // 计算租金
    public abstract double getPrice(int days);
}
java
package com.Example.RentCar;

public class Car extends Vehicle{
    private String type;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Car(String brand, String vehicleId, double dayRent, String type) {
        super(brand, vehicleId, dayRent);
        this.type = type;
    }

    public Car() {
    }

    @Override
    public String toString() {
        return "Car{" +
                "type='" + type + '\'' +
                '}';
    }

    @Override
    public double getPrice(int days) {
        double price = 0;
        if(days > 150){
            price = this.getDayRent() * days * 0.7;
        }else if(days > 30){
            price = this.getDayRent() * days * 0.8;
        }else if(days > 7){
            price = this.getDayRent() * days * 0.9;
        }else{
            price = this.getDayRent() * days;
        }
        return price;
    }
}
java
package com.Example.RentCar;

public class Bus extends Vehicle{
    private int seatCount;

    public int getSeatCount() {
        return seatCount;
    }

    public void setSeatCount(int seatCount) {
        this.seatCount = seatCount;
    }

    public Bus(String brand, String vehicleId, double dayRent, int seatCount) {
        super(brand, vehicleId, dayRent);
        this.seatCount = seatCount;
    }

    public Bus() {
    }

    @Override
    public String toString() {
        return "Bus{" +
                "seatCount=" + seatCount +
                '}';
    }

    @Override
    public double getPrice(int days) {
        double price = 0;
        if(days >= 150){
            price = this.getDayRent() * days * 0.6;
        }else if(days >= 30){
            price = this.getDayRent() * days * 0.7;
        }else if(days >= 7){
            price = this.getDayRent() * days * 0.8;
        }else if(days >= 3){
            price = this.getDayRent() * days * 0.9;
        }else{
            price = this.getDayRent() * days;
        }
        return price;
    }

}
java
package com.Example.RentCar;

// 机动车 操作类
public class VehicleOperation {
    // 定义静态属性
    static Vehicle [] vehicles = new Vehicle[8];

    // 定义静态代码块,加载类时自动执行。
    static{
        vehicles[0] = new Car("宝马", "粤A88888", 888, "X5");
        vehicles[1] = new Car("宝马", "粤A99999", 666, "740 Li");
        vehicles[2] = new Car("奔驰", "粤A12345", 456, "S500");
        vehicles[3] = new Car("奔驰", "粤A98765", 950, "S600");

        vehicles[4] = new Bus("金杯", "粤B11111", 888, 16);
        vehicles[5] = new Bus("金杯", "粤A22222", 666, 34);
        vehicles[6] = new Bus("比亚迪", "粤A3333", 456, 16);
        vehicles[7] = new Bus("比亚迪", "粤A55555", 950, 34);
    }

    // 根据参数返回 机动车
    public Vehicle getVehicle(String brand,String type,int seatCount){
        // 遍历数组
        for(int i = 0;i < vehicles.length;i++){
            // 汽车类型
            if(vehicles[i] instanceof Car){
                // 多态向下转型
                Car car = (Car)vehicles[i];
                // 匹配到汽车
                if(brand.equals(car.getBrand()) && type.equals(car.getType())){
                    return car;
                }
            }else{
                Bus bus = (Bus)vehicles[i];
                // 匹配到大巴
                if(brand.equals(bus.getBrand()) && seatCount == bus.getSeatCount()){
                    return bus;
                }
            }
        }
        // 否则没找到则返回null
        return null;
    }
}
java
package com.Example.RentCar;

import java.util.Scanner;

public class TestVehicle {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("欢迎使用此系统~~~");
        System.out.println("输入汽车类型: 1.轿车 2.客车");
        // 输入选择
        int choice = input.nextInt();

        String brand = ""; // 用于保存用户选择的品牌
        String type = "";// 用于保存用户选择的型号
        int seatCount = 0; // 用于保存用户选择的座位数

        if(choice == 1){
            // 轿车
            System.out.println("请选择汽车品牌:1.奔驰 2.宝马");
            brand = input.nextInt() == 1 ? "奔驰" : "宝马";
            if(brand.equals("奔驰")){
                System.out.println("请选择汽车型号:1.  S500  2.  S600");
                type = input.nextInt() == 1 ? "S500" : "S600";
            }else{
                System.out.println("请选择汽车型号:1.  X5  2.  740Li");
                type = input.nextInt() == 1 ? "X5" : "740Li";
            }
        }else{
            // 客车
            System.out.println("请选择汽车品牌:1.金杯    2.比亚迪");
            brand = input.nextInt() == 1 ? "金杯" : "比亚迪";
            System.out.println("请选择汽车座位数:1.  16座  2.  34座");
            seatCount = input.nextInt() == 1 ? 16 : 34;
        }

        // 获取到了用户选择的汽车信息
        System.out.println("品牌:" + brand);
        System.out.println("型号:" + type);
        System.out.println("座位数:" + seatCount);

        // 创建汽车操作类对象
        VehicleOperation vehicleOperation = new VehicleOperation();
        // 通过选择的品牌 类型 座位数 获取机动车对象
        Vehicle vehicle = vehicleOperation.getVehicle(brand,type,seatCount);
        //
        System.out.println("请输入租赁天数:");
        int days = input.nextInt();
        // 计算价格
        double price = vehicle.getPrice(days);
        // 打印租车结果
        System.out.println("分配跟您的车牌号为:" + vehicle.getVehicleId());
        System.out.println("您应该支付的租金为:" + price);
    }
}

细节问题

打印堆栈信息和抛出新的异常区别

java
try{
    m4(189);
}catch (Exception e){
    // 方式1:抛出新的一个运行时异常
    throw new RuntimeException(e); // 又抛出一个新的异常,但是后续再没有捕获到,会中断程序。
    // 方式2:打印堆栈信息
    e.printStackTrace(); // 打印异常,但是不会中断程序,后续代码继续执行。
}
System.out.println("程序结束") // 只要捕获到异常,后续的代码就可以执行