Skip to main content

异常

概述及体系结构

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

主要记4类常见异常

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

Java中的5种常见异常

image-20250708095015055

Try Catch异常

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

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

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

情况1

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

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

try-catch的使用

try代码块中存放可能会出现异常的代码
catch用于捕获异常

try 或者 catch 均不能单独使用
try 必须结合 catch 或者 catch -finally 或者 finally

try-catch使用示例1

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块书写顺序 先写子类 后写 父类。

catch(异常类名 参数){

}

catch多个异常示例:

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示例:

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

执行

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

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

finally return示例

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块,如果执行,说出执行顺序。

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

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

JVM退出虚拟机

throw和throws

throws规则

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

多个异常使用逗号分割。

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

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

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

处理异常的方式

两种处理方式:

1.使用try-catch处理。

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

throws示例:

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规则

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

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

  • 如果抛出的为运行时异常 则方法上不必声明
  • 如果抛出的为检查异常 则必须在方法上声明
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.调用父类中的有参构造完成异常信息的初始化。

自定义异常示例:

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 +
'}';
}
}

package com.throwsPart.Test02;
// 检查型异常
public class InputSexException extends Exception{

public InputSexException(String message) {
super(message);
}
}

package com.throwsPart.Test02;

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

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

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("程序结束");
}
}

租车案例

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);
}

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;
}
}

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;
}

}

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;
}
}

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);
}
}