24、应对意外情况时的优雅处理

应对意外情况时的优雅处理

1. 计算机异常历史回顾

在计算机发展历程中,出现过许多因小失误导致大问题的情况:
- 1945年9月9日,一只飞蛾飞进哈佛Mark II计算机的一个继电器中,导致机器故障,这成为有记录以来第一个真正的计算机“bug”。
- 1957年4月19日,赫伯特·布莱特收到一盒未标记的计算机穿孔卡片,他猜测这来自FORTRAN开发团队。他编写了一个FORTRAN程序并在IBM 704上编译,最初因一个语句中缺少逗号而报错,修正后程序正常运行。
- 1962年7月22日,美国第一艘飞往其他行星的航天器水手一号在发射四分钟后出现异常并被摧毁,原因是火箭速度公式中缺少一个类似连字符的符号。同时,NASA的轨道计算软件中存在错误语句,将循环语句写成了赋值语句。
- 2000年1月1日,“千年虫问题”给现代世界带来了严重破坏。

2. 异常处理基础

假设你正在进行库存盘点,要输入大灰尘箱的数量,每个箱子价值3.25美元。以下是一个简单的库存处理程序:

import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryA {
   public static void main(String args[]) {
      final double boxPrice = 3.25;
      Scanner keyboard = new Scanner(System.in);
      NumberFormat currency = NumberFormat.getCurrencyInstance();
      out.print("How many boxes do we have? ");
      String numBoxesIn = keyboard.next();
      int numBoxes = Integer.parseInt(numBoxesIn);
      out.print("The value is ");
      out.println(currency.format(numBoxes * boxPrice));
      keyboard.close();
   }
}

当用户输入整数时,程序正常运行;但输入非整数(如3.5)时,程序会崩溃,并抛出 java.lang.NumberFormatException 异常。这是因为 Integer.parseInt 方法在处理无法解析为整数的字符串时会抛出该异常。

Java语言有异常处理机制,当程序检测到即将出现问题时,会创建一个新的异常对象,即“抛出异常”。异常对象会在代码中传递,直到被某个代码块“捕获”。Java中用于异常处理的关键字有:
- throw :创建一个新的异常对象。
- throws :将异常责任从一个方法传递给调用该方法的代码。
- try :包含可能抛出异常的代码。
- catch :处理捕获到的异常,然后继续执行后续代码。

为了修复上述程序的问题,可以使用 try-catch 语句:

import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryB {
   public static void main(String args[]) {
       final double boxPrice = 3.25;
       Scanner keyboard = new Scanner(System.in);
       NumberFormat currency = NumberFormat.getCurrencyInstance();
       out.print("How many boxes do we have? ");
       String numBoxesIn = keyboard.next();
       try {
           int numBoxes = Integer.parseInt(numBoxesIn);
           out.print("The value is ");
           out.println(currency.format(numBoxes * boxPrice));
       } catch (NumberFormatException e) {
           out.println("That's not a number.");
       }
       keyboard.close();
   }
}

在这个程序中,将 Integer.parseInt 方法调用放在 try 块中,当抛出 NumberFormatException 异常时,程序会跳转到 catch 块中执行相应的处理代码。

3. catch子句参数

catch 子句中的参数类似于方法的参数列表,它包含异常类型和一个参数名。例如:

} catch (NumberFormatException e) {
    out.println("Message: ***" + e.getMessage() + "***");
    e.printStackTrace();
}

当捕获到异常时,参数 e 会引用该异常对象。通过调用异常对象的方法(如 getMessage printStackTrace ),可以获取异常的详细信息,帮助定位问题。

4. 异常类型

除了 NumberFormatException ,Java中还有许多其他类型的异常,甚至可以自定义异常类。以下是自定义异常类和使用该异常类的示例:

@SuppressWarnings("serial")
class OutOfRangeException extends Exception {
}
import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryC {
   public static void main(String args[]) {
      final double boxPrice = 3.25;
      Scanner keyboard = new Scanner(System.in);
      NumberFormat currency = NumberFormat.getCurrencyInstance();
      out.print("How many boxes do we have? ");
      String numBoxesIn = keyboard.next();
      try {
         int numBoxes = Integer.parseInt(numBoxesIn);
         if (numBoxes < 0) {
            throw new OutOfRangeException();
         }
         out.print("The value is ");
         out.println(currency.format(numBoxes * boxPrice));
      } catch (NumberFormatException e) {
         out.println("That's not a number.");
      } catch (OutOfRangeException e) {
         out.print(numBoxesIn);
         out.println("? That's impossible!");
      }
      keyboard.close();
   }
}

在库存程序中,当用户输入负数时,会抛出 OutOfRangeException 异常,程序会给出相应的提示。

5. 异常匹配规则

一个 try 块可以有多个 catch 块。当异常抛出时,计算机从第一个 catch 块开始检查,判断抛出的异常是否是该 catch 块参数类型的实例。如果是,则执行该 catch 块的代码,并跳过后续的 catch 块;如果不是,则继续检查下一个 catch 块。

例如,以下代码展示了多个 catch 块的情况:

import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryD {
   public static void main(String args[]) {
       final double boxPrice = 3.25;
       Scanner keyboard = new Scanner(System.in);
       NumberFormat currency = NumberFormat.getCurrencyInstance();
       out.print("How many boxes do we have? ");
       String numBoxesIn = keyboard.next();
       try {
           int numBoxes = Integer.parseInt(numBoxesIn);
           if (numBoxes < 0) {
               throw new OutOfRangeException();
           }
           if (numBoxes > 1000) {
               throw new NumberTooLargeException();
           }
           out.print("The value is ");
           out.println(currency.format(numBoxes * boxPrice));
       }
       catch (NumberFormatException e) {
           out.println("That's not a number.");
       }
       catch (OutOfRangeException e) {
           out.print(numBoxesIn);
           out.println("? That's impossible!");
       }
       catch (Exception e) {
           out.print("Something went wrong, ");
           out.print("but I'm clueless about what ");
           out.println("it actually was.");
       }
       out.println("That's that.");
       keyboard.close();
   }
}

在这个程序中, NumberTooLargeException OutOfRangeException 的子类,当抛出 NumberTooLargeException 异常时,会被 catch (OutOfRangeException e) 块捕获。

以下是不同输入情况下程序的处理流程:
| 用户输入 | 抛出异常 | 匹配的catch块 | 处理结果 |
| — | — | — | — |
| 普通整数(如3) | 无 | 无 | 执行try块内所有语句,跳过所有catch块,执行后续代码 |
| 非整数(如fish) | NumberFormatException | catch (NumberFormatException e) | 跳过try块剩余语句,执行该catch块代码,跳过后续catch块,执行后续代码 |
| 负数(如 -25) | OutOfRangeException | catch (OutOfRangeException e) | 跳过try块剩余语句,跳过第一个catch块,执行该catch块代码,跳过后续catch块,执行后续代码 |
| 过大的数(如1001) | NumberTooLargeException | catch (OutOfRangeException e) | 跳过try块剩余语句,跳过第一个catch块,执行该catch块代码,跳过后续catch块,执行后续代码 |
| 其他不可预测异常(如IOException) | IOException | catch (Exception e) | 跳过try块剩余语句,跳过前两个catch块,执行该catch块代码,执行后续代码 |

mermaid流程图如下:

graph TD;
    A[用户输入] --> B{输入是否为整数};
    B -- 是 --> C{是否为负数};
    C -- 是 --> D(抛出OutOfRangeException);
    C -- 否 --> E{是否大于1000};
    E -- 是 --> F(抛出NumberTooLargeException);
    E -- 否 --> G(正常计算并输出结果);
    B -- 否 --> H(抛出NumberFormatException);
    D --> I(匹配catch OutOfRangeException);
    F --> I;
    H --> J(匹配catch NumberFormatException);
    I --> K(输出相应提示,继续执行后续代码);
    J --> K;
    L(其他不可预测异常) --> M(抛出异常);
    M --> N(匹配catch Exception);
    N --> K;

应对意外情况时的优雅处理

6. 一次性捕获多种异常

从Java 7开始,可以在一个 catch 子句中捕获多种类型的异常。例如,在库存程序中,如果不想区分 NumberFormatException OutOfRangeException ,可以这样编写代码:

try {
    int numBoxes = Integer.parseInt(numBoxesIn);
    if (numBoxes < 0) {
        throw new OutOfRangeException();
    }
    if (numBoxes > 1000) {
        throw new NumberTooLargeException();
    }
    out.print("The value is ");
    out.println(currency.format(numBoxes * boxPrice));
}
catch (NumberFormatException | OutOfRangeException e) {
    out.print(numBoxesIn);
    out.println("? That's impossible!");
}
catch (Exception e) {
    out.print("Something went wrong, ");
    out.print("but I'm clueless about what ");
    out.println("it actually was.");
}

使用竖线 | 来分隔不同的异常类型,当抛出 NumberFormatException OutOfRangeException 时,程序会执行相应的 catch 块代码。

7. 避免不必要的异常捕获

Java不允许捕获不可能抛出的异常。例如,以下代码会引发编译器错误:

// Bad code!
try {
    i++;
} catch (IOException e) {
    e.printStackTrace();
}

因为 i++ 语句不涉及任何输入输出操作,所以 try 块内的代码不可能抛出 IOException 异常,编译器会给出错误提示:

exception java.io.IOException is never thrown
in body of corresponding try statement
8. 异常捕获后的持续处理

前面的示例大多是捕获异常后打印错误信息并结束程序。实际上,可以让程序在捕获异常后继续运行。以下是一个在循环中使用 try-catch 语句的示例:

import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class InventoryLoop {
   public static void main(String args[]) {
      final double boxPrice = 3.25;
      boolean gotGoodInput = false;
      Scanner keyboard = new Scanner(System.in);
      NumberFormat currency = NumberFormat.getCurrencyInstance();
      do {
         out.print("How many boxes do we have? ");
         String numBoxesIn = keyboard.next();
         try {
            int numBoxes = Integer.parseInt(numBoxesIn);
            out.print("The value is ");
            out.println(currency.format(numBoxes * boxPrice));
            gotGoodInput = true;
         } catch (NumberFormatException e) {
            out.println();
            out.println("That's not a number.");
         }
      } while (!gotGoodInput);
      out.println("That's that.");
      keyboard.close();
   }
}

在这个程序中,使用 do-while 循环,只要用户输入的不是有效的整数,循环就会继续,直到用户输入正确的整数为止。

以下是该程序的执行步骤列表:
1. 初始化变量 gotGoodInput false
2. 进入 do-while 循环,提示用户输入箱子数量。
3. 获取用户输入,尝试将其转换为整数。
4. 如果转换成功,计算并输出箱子的价值,将 gotGoodInput 设置为 true
5. 如果转换失败,捕获 NumberFormatException 异常,输出错误提示,继续循环。
6. 当 gotGoodInput true 时,退出循环,输出结束信息。

9. 正常情况下的异常使用

并非所有的异常都是由错误情况引起的,有些异常是正常预期的情况。例如,在文件复制过程中,检测到文件末尾时会抛出 EOFException 异常:

try {
    while (true) {
        dataOut.writeByte(dataIn.readByte());
    }
} catch (EOFException e) {
    numFilesCopied = 1;
}

在这个示例中,使用 while (true) 创建一个看似无限的循环,当读取到文件末尾时, readByte 方法会抛出 EOFException 异常,程序会跳出循环并执行 catch 块中的代码。

mermaid流程图如下:

graph TD;
    A[开始文件复制] --> B{是否到达文件末尾};
    B -- 否 --> C(读取并写入字节);
    C --> B;
    B -- 是 --> D(抛出EOFException);
    D --> E(捕获异常,复制文件数加1);
    E --> F[结束]

综上所述,Java的异常处理机制为我们提供了强大的工具来处理程序中可能出现的意外情况。通过合理使用 try-catch 语句、自定义异常类和异常匹配规则,可以让程序更加健壮和可靠。同时,要注意避免不必要的异常捕获,并且可以让程序在捕获异常后继续执行有用的操作。

内容概要:本文介绍了一个基于事件触发机制的孤岛微电网二次电压与频率协同控制仿真模型,该模型通过Simulink实现,旨在提升微电网在孤岛运行模式下的电压和频率稳定性。通过引入事件触发机制,有效降低了控制器的通信负担和计算开销,同保证了控制性能。模基于事件触发机制的孤岛微电网二次电压与频率协同控制仿真模型(Simulink仿真实现)型涵盖了微电网中分布式电源的动态特性、控制策略的设计与实现,并通过仿真验证了所提方法在抑制电压偏差、频率波动及提升系统响应速度方面的有效性。此外,文档还列举了大量相关的电力系统、优化算法、信号处理、机器学习等领域仿真资源,展示了其在科研与工程应用中的广泛技术支持。; 适合人群:从事电力系统、微电网控制、自动化及相关领域的科研人员、研究生以及具备一定MATLAB/Simulink基础的工程技术人员。; 使用场景及目标:①研究孤岛微电网中电压与频率的协同控制策略;②优化微电网控制系统中的通信效率与实性;③开展基于事件触发机制的控制算法仿真与验证;④结合其他智能算法(如优化算法、机器学习)进行微电网高级控制策略开发。; 阅读建议:建议读者结合文中提供的仿真模型与相关资源链接,动手实践Simulink建模过程,重点关注事件触发条件的设计与控制效果的对比分析,同可拓展学习文档中提及的优化算法与智能控制方法,以提升综合科研能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值