java异常处理

2018/02/01 Java 共 4856 字,约 14 分钟

异常

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • Error:Error 属于程序无法处理的错误 ,可通过 catch 来进行捕获。但是不建议通过catch捕获, 捕获它可能掩盖系统级问题,导致程序状态不一致、资源泄漏。 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: Error 和 Exception。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:

  1. 受检异常 :需要用 try…catch… 语句捕获并进行处理,并且可以从异常中恢复;
  2. 非受检异常 :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。

Java 入门之异常处理

Java 异常的面试问题及答案 -Part 1

如果希望写一个检查性异常类,则需要继承 Exception 类。

如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

种类

Java 异常类层次结构图 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

Java 内置异常类

Java 语言定义了一些异常类在 java.lang 标准包中。

异常方法

1	public String getMessage()
返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
2	public Throwable getCause()
返回一个Throwable 对象代表异常原因。
3	public String toString()
使用getMessage()的结果返回类的串级名字。
4	public void printStackTrace()
打印toString()结果和栈层次到System.err,即错误输出流。
5	public StackTraceElement [] getStackTrace()
返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
6	public Throwable fillInStackTrace()
用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。

捕获异常

try
{
// 程序代码
}catch(ExceptionName e1)
{
//Catch 块
}

throws/throw 关键字:

如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。

也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

import java.io.*;
public class className
{
  public void deposit(double amount) throws RemoteException
  {
    // Method implementation
    throw new RemoteException();
  }
  //Remainder of class definition
}

finally关键字

finally 关键字用来创建在 try 代码块后面执行的代码块。

无论是否发生异常,finally 代码块中的代码总会被执行。

在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

finally 代码块出现在 catch 代码块最后,语法如下:

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

声明自定义异常

在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。 所有异常都必须是 Throwable 的子类。

如果希望写一个检查性异常类,则需要继承 Exception 类。

如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

可以像下面这样定义自己的异常类:

class MyException extends Exception{
}

只继承Exception 类来创建的异常类是检查性异常类。

描述Java 7 ARM(Automatic Resource Management,自动资源管理)特征和多个catch块的使用

答:如果一个try块中有多个异常要被捕获,catch块中的代码会变丑陋的同时还要用多余的代码来记录异常。有鉴于此,Java 7的一个新特征是:一个catch子句中可以捕获多个异常。示例代码如下:

catch(IOException | SQLException | Exception ex){
     logger.error(ex);
     throw new MyException(ex.getMessage());
}

try-with-resources

大多数情况下,当忘记关闭资源或因资源耗尽出现运行时异常时,我们只是用finally子句来关闭资源。这些异常很难调试,我们需要深入到资源使用的每一步来确定是否已关闭。比如try里有file.readline和finally file.close都可能抛出异常,这样异常会覆盖。

因此,Java 7用try-with-resources进行了改进:在try子句中能创建一个资源对象,当程序的执行完try-catch之后,运行环境自动关闭资源。下面是这方面改进的示例代码:

class MyResource implements AutoCloseable {
    MyResource() {
    }

    public void close() throws Exception {
        System.out.println("关闭自定义资源");
    }
}



try (MyResource mr = new MyResource()) {
            System.out.println("MyResource created in try-with-resources");
        } catch (Exception e) {
            e.printStackTrace();
        }

实现上,是try里自动加了close的代码在try的字节码代码块里。参考

常见错误和异常

Error

  • Java 虚拟机运行错误(Virtual MachineError) 这是一个抽象错误类,通常不会直接抛出,而是由其子类抛出,如 OutOfMemoryError、StackOverflowError、InternalError 等。 虚拟机内部出现了严重问题,比如内存耗尽、栈溢出、JVM 内部错误等,JVM 抛出此类错误来终止操作。
  • 虚拟机内存不够错误(OutOfMemoryError)
  • 类定义错误(NoClassDefFoundError)编译时存在该类,但运行时找不到,说明编译成功后,运行时该类文件未能被 JVM 加载。
    • 原因
      • 编译时存在该类,但运行时 .class 文件不在 classpath 中
      • 被删除或路径错误
      • 动态加载类失败(如反射加载)
      • 也可能是maven原因 依赖调解原因,它是最短路径、最早声明优先。如果 C 依赖用到了 1.5 版本的 X 中才有的一个类,运行项目就会报NoClassDefFoundError错误。如果 C 依赖用到了 1.5 版本的 X 中才有的一个方法,运行项目就会报NoSuchMethodError错误。
  • UnsupportedClassVersionError 类版本不兼容。 使用高版本 JDK 编译,低版本运行
  • ExceptionInInitializerError 静态初始化器出错。 类静态代码块/字段抛异常

Exception

  • NullPointerException(空指针错误)
  • IllegalArgumentException(参数错误比如方法入参类型错误)
  • NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ClassCastException(向下类型转换错误) 在尝试将一个对象强制转换为不是它实际类型的子类时会抛出。也就是说,编译器允许转换,但运行时发现对象类型不兼容,就会报这个错。
  • ArithmeticException(算术错误)
  • SecurityException (安全错误比如权限不够)
  • UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)
  • ClassNotFoundException 类找不到。 使用 Class.forName 时

ClassNotFoundException vs NoClassDefFoundError

异常/错误类型是否受检异常触发场景
ClassNotFoundExceptionException(受检)✅ 是通过反射等方式显式加载类时找不到,如 Class.forName(). 编译时不报错,运行时检查的
NoClassDefFoundErrorError(非受检)❌ 否类在编译时存在,但运行时无法加载该类(通常因类路径问题、依赖未加载)

两者的区别是:一个是你“主动”找类没找到(ClassNotFoundException),一个是 JVM 自动找类但没找到(NoClassDefFoundError)。

最佳实践

  • try 块中的 return 语句执行成功后,并不会马上返回,而是继续执行 finally 块中的语句,如果 finally 块中也存在 return 语句,那么 try 块中的 return 就将被覆盖。注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
  • 抛出具体定义的检查性异常而不是-exception
  • 自定义异常时不要丢失堆栈跟踪
  • finally 块中不要抛出任何异常。 如果在 finally 块中抛出异常,可能会导致原始异常被掩盖。比如说上例中,一旦 cleanup 抛出异常,someMethod 中的异常将会被覆盖。
  • printStackTrace() 会阻塞 I/O,在高并发环境下,可能会造成性能问题。 频繁调用 printStackTrace() 可能会影响垃圾回收(GC),因为 Throwable.fillInStackTrace() 需要收集完整的堆栈信息。
  • 对于不打算处理的异常,直接使用 try-finally,不用 catch
  • 记住早 throw 晚 catch 原则。 “早 throw, 晚 catch” 是 Java 中的一种异常处理原则。这个原则指的是在代码中尽可能早地抛出异常,以便在异常发生时能够及时地处理异常。同时,在 catch 块中尽可能晚地捕获异常,以便在捕获异常时能够获得更多的上下文信息,从而更好地处理异常。
  • 切勿在代码中使用异常来进行流程控制
  • error可以catch, 但不推荐。 因为它表示 JVM 运行环境的严重问题(如内存耗尽、栈溢出、类加载失败等),程序一般无法也不应该尝试恢复。

Search

    Table of Contents