但行好事
莫论前程❤

java基础学习总结–异常(理论再述)

参考文献:java的编程逻辑-异常
起因:一篇文章的反思内容如下:

在下面的代码中,当抛出运行时异常后,后面的代码还会执行吗,是否需要在异常后面加上return语句呢?

public void add(int index, E element){
    if(size >= elements.length) {
        throw new RuntimeException("顺序表已满,无法添加"); 
        //return;  //需要吗?
    }
    ....
}

为了回答这个问题,我编写了几段代码测试了一下,结果如下:

//代码1
public static void test() throws Exception  {

    throw new Exception("参数越界"); 
    System.out.println("异常后"); //编译错误,「无法访问的语句」
}
//代码2
try{
    throw new Exception("参数越界"); 
}catch(Exception e) {
    e.printStackTrace();
}
System.out.println("异常后");//可以执行
//代码3
if(true) {
    throw new Exception("参数越界"); 
}
System.out.println("异常后"); //抛出异常,不会执行

总结:

  1. 若一段代码前有异常抛出,并且这个异常没有被捕获,这段代码将产生编译时错误「无法访问的语句」。如代码1
  2. 若一段代码前有异常抛出,并且这个异常被try…catch所捕获,若此时catch语句中没有抛出新的异常,则这段代码能够被执行,否则,同第1条。如代码2
  3. 若在一个条件语句中抛出异常,则程序能被编译,但后面的语句不会被执行。如代码3

另外总结一下运行时异常与非运行时异常的区别:

  • 运行时异常是RuntimeException类及其子类的异常,是非受检异常,如NullPointerException、IndexOutOfBoundsException等。由于这类异常要么是系统异常,无法处理,如网络问题;
    要么是程序逻辑错误,如空指针异常;JVM必须停止运行以改正这种错误,所以运行时异常可以不进行处理(捕获或向上抛出,当然也可以处理),而由JVM自行处理。Java Runtime会自动catch到程序throw的RuntimeException,然后停止线程,打印异常。
  • 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类,是受检异常。非运行时异常必须进行处理(捕获或向上抛出),如果不处理,程序将出现编译错误。一般情况下,API中写了throws的Exception都不是RuntimeException。

常见运行时异常:

img

常见非运行时异常:

img

捕获异常的相关过程

先看一个荔枝:

public class ExceptionTest {
    public static void main( String[] args) {
        String s = null; s. indexOf(" a");
        System. out. println(" end");
    }
}
输出:
Exception in thread "main" java.lang.NullPointerException at ExceptionTest.main(ExceptionTest. java: 5)

​ Java虚拟机发现s的值为null,没有办法继续执行的时候,就会启动异常处理机制,首先创建一个异常对象,这里是类NullPointerException的对象,然后查找看谁能处理这个异常,在示例中,没有代码能处理这个异常,因此Java启动默认处理机制.即打印异常栈信息到屏幕上,并退出程序.

​ 异常栈信息就包括了从异常发生点到最上层调用者的轨迹,还包括行号,可以说,这个栈信息是分析异常最为重要的信息.

Java的默认异常处理机制是退出程序,异常发生点后的代码都不会执行.

异常可以通过try/catch语句进行捕获并处理

​ 异常处理机制会从当前函数开始查找看谁”捕获”了这个异常,当前函数没有就查看上一层,直到主函数,如果主函数也没有,就使用默认机制,即输出异常栈信息并退出.

​ 捕获异常后,程序就不会异常退出了,但try语句内异常点之后的其他代码就不会执行了,执行完catch内的语句后,程序会继续执行catch花括号外的代码.

总结:

1.1)如果某个异常发生的时候没有再任何地方进行捕获, 那程序就会运行终止: 并在控制台上打印出异常信息 , 其中包括异常的类型堆栈的内容; 

1.2)要想捕获一个异常, 必须设置 try/catch 语句块:

  • 1.2.1)如果在try语句块中抛出了一个在 catch子句中声明的异常类, 那么
    • case1)程序将跳过try 语句块的其余代码;
    • case2)程序将执行 catch 子句中 的处理器代码;
  • 1.2.2)如果在try语句块中没有抛出任何异常, 那么程序将跳过 catch子句;
  • 1.2.3)如果方法中的任何代码抛出了一个在 catch 子句中没有声明的异常类型, 那么这个方法就会立刻退出;
再看个例子
public void read(String filename)
{
    try
    {
        InputStream in = new FileInputStream(filename);  // 创建输入流
        int b;
        while((b=in.read()) != -1)
            process input
    }
    catch(IOException exception)
    {
        exception.printStackTrace(); // 打印栈轨迹;
    }
}

Attention)编译器严格地执行 throws 说明符。 如果调用了一个抛出已检查异常的方法, 就必须对它进行处理, 或者将它继续进行传递;

两种处理异常的方法, 哪种方法更好呢?(method1:自己处理(在可能发生异常的函数中添加try/catch 语句块);method2:将异常传递(throw)给调用者,调用者处理)
  • 1.5.1)通常, 应该捕获那些知道如何处理的异常, 而将那些不知道怎么处理的异常继续进行传递;如果想传递一个异常, 就必须在方法的首部添加一个throws 说明符, 以便告知调用者这个方法可能会抛出异常;
  • 1.5.2)阅读API后, 以便知道这个方法可能会抛出哪种异常, 然后再决定是自己处理, 还是添加到 throws 列表中;

Attention)以上规则有个例外: 前面提到, 如果编写一个覆盖超类的方法, 而这个方法又没有抛出异常, 那么这个方法就必须捕获方法代码中出现的每一个已检查异常。不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围;(也就是说父类方法没有抛出异常,你子类方法也不准抛出异常,只能自己添加 try/catch 语句块自己处理)

捕获多个异常

2.1)在一个try 语句块中可以捕获多个异常, 并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的 catch 子句;

try{

}catch(FileNotFoundException e){

}catch(UnknownHostException e){

}catch(IOException e){

}

2.2)要想获得异常对象 的更多信息: 可以试着使用e.getMessage()得到详细的错误信息, 或者使用 e.getClass().getName(); 得到异常对象的实际类型; 

2.3)合并catch 子句: 在 java SE7中, 同一个 catch 子句中可以捕获多个异常类型。 例如, 假设对应缺少文件和未知主机异常的动作是一样的, 就可以合并catch 子句:

try{

}catch(FileNotFoundException | UnknownHostException e){

}catch(IOException e){

}

Attention)

  • A1)只有当捕获的异常类型彼此间不存在子类关系时才需要这个特性;
  • A2)捕获多个异常时, 异常变量隐含为 final变量。例如, 不能在以下子句体中为 e 赋不同的值;
    catch(FileNotFoundException || UnknownHostException e) {}

再次抛出异常与异常链

3.1)在catch子句中可以抛出一个异常, 这样做的目的是 改变异常类型;

  • 3.1.1)看个荔枝:
try
{}
catch(SQLException e)
{
    throw new ServletException("data error : " + e.getMessage());
}

对以上代码的分析(Analysis):

  • A1)这里, ServletException 用带有异常信息文本的构造器来构造;
  • A2)不过, 可以有一种更好的方法, 并且将原始异常设置为新异常的原因:
try{

}catch(SQLException e){
    Throwable se = new ServletException("database error");
    se.initCause(e);
    throw se;
}
  • A3)当捕获到这个异常时, 就可以使用下面的语句重新得到原始异常:
Throwable e = se.getCause();

Attention)强烈建议使用这种包装技术, 这样可以让用户抛出子系统中的高级异常, 而不会丢失原始异常的小细节; (推荐使用 strongly recommended)

Hint)

  • H1)如果在一个方法中发生了一个已检查异常,而不允许抛出它, 那么包装技术就十分有用。 我们还可以捕获这个已检查异常, 并将它包装成一个 运行时异常;
  • H2)有时候, 你可能只想记录一个异常,再将它重新抛出, 而不做任何改变:
try{
    access the database
}catch(Exception e){
    logger.log(level, message, e);
    throw e;
}
  • H3)在Java SE7 之前, 将上述代码放入下述方法中, 会出现一个问题;
public void updateRecord() throws SQLException1
  • 因为, java 编译器查看catch 块中的 throw 语句, 然后查看e的类型, 会指出这个方法可以抛出任何Exception而不仅仅是 SQLException;
  • H4)java se 7之后(编译器检测语法合法): 编译器会跟踪到 e 来自于try块中, 假设这个 try 块中仅有 的已检查异常 是 SQLException实例, 另外,假设e在catch块中未改变, 将外围方法声明为 throws SQLException 就是合法的;
赞(0) 打赏
未经允许不得转载:刘鹏博客 » java基础学习总结–异常(理论再述)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏