一、异常的概念和作用
在 Java 中,异常是指程序运行时可能发生的错误或意外事件。处理异常的作用是确保程序在异常情况下也能够继续正常运行,而不是出现异常就直接崩溃或停止运行。通过异常处理,我们可以对出现异常的情况进行处理,比如输出异常信息,尝试修复异常,或者让程序继续运行下去。
二、异常的分类
(1)Exception类
受检异常(Checked Exception):指在程序编译期就能够检查出来的异常。受检异常需要在代码中进行处理,否则编译器会报错。通常受检异常是由 Java API 中的方法抛出的,它们在方法签名中用 throws
关键字声明。Java中的受检异常主要包括以下几种:
- IOException:表示输入/输出操作发生异常,比如文件不存在、文件读写错误等。
- FileNotFoundException:表示指定的文件不存在。
- ClassNotFoundException:表示无法找到指定的类。
- SQLException:表示对数据库进行操作时发生异常。
- IllegalAccessException:表示没有访问权限。
- NoSuchFieldException:表示没找到指定的字段。
- NoSuchMethodException:表示没有找到指定的方法。
- InstantiationException:当试图使用Class类中的newInstance()方法创建类的实例,而该类没有无参构造函数时抛出
- CloneNotSupportedException:当尝试克隆一个不支持克隆的对象时,抛出该异常。
- InterruptedException:当一个线程被另一个线程中断时,抛出该异常。
- ReflectiveOperationException:当一个反射异常(如方法调用或字段访问异常)发生时,抛出该异常。
举个例子:
//定义Example类
public class Example {
//定义main方法
public static void main(String[] args) {
//使用try-catch语句捕获可能会抛出的异常
try {
//将命令行参数转换为整型并赋值给num1和num2
int num1 = Integer.parseInt(args[0]);
int num2 = Integer.parseInt(args[1]);
//计算num1和num2的商并赋值给result
int result = num1 / num2;
//输出结果
System.out.println("Result: " + result);
} catch (ArrayIndexOutOfBoundsException e) { //处理数组越界异常
//输出异常信息
System.out.println("Please provide two arguments");
} catch (NumberFormatException e) { //处理数字格式异常
//输出异常信息
System.out.println("Arguments must be integers");
} catch (ArithmeticException e) { //处理算术异常
//输出异常信息
System.out.println("Second argument cannot be zero");
}
}
}
这段代码中的异常具体在哪里,可以根据不同的错误输入来触发不同的异常。如果输入的命令行参数少于两个,就会抛出 ArrayIndexOutOfBoundsException 异常;如果输入命令行参数不是整数,就会抛出 NumberFormatException 异常;如果第二个参数为 0,就会抛出ArithmeticException 异常。
运行时异常(Runtime Exception):这种异常通常是程序员编写的错误或者是因为运行环境出现了错误导致的异常。例如,数组下标越界、空指针引用等。这些异常可以不显式地捕获和处理,但最好的实践是要避免这些异常的出现。Java中的运行时异常主要包括以下几种:
- ClassCastException:在强制类型转换时发生,例如将一个不是子类的对象强制转换成某个类的对象。
- NullPointerException:在尝试访问null对象时发生。
- ArrayIndexOutOfBoundsException:在尝试访问数组中不存在的索引时发生。
- IllegalArgumentException:表示调用方法时传递了不合法或不正确的参数。
- IllegalStateException:表示在调用方法之前或之后对象处于不正确的状态,例如在非法状态下调用了某个方法。
- UnsupportedOperationException:表示不支持某个方法或操作,例如尝试修改一个不可修改的集合。
- NumberFormatException:在将字符串转换成数值类型时发生,例如使用Integer.parseInt()方法将一个非数字字符串转换成整数。
ArithmeticException
:它表示在算术运算中出现了异常。通常情况下,该异常是由除数为零引起的。
举个例子:
public class DivideByZeroExample {
public static void main(String[] args) {
int a = 10;
int b = 0;
int result;
try {
result = a / b; // 试图除以0,抛出运行时异常
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
}
System.out.println("Program continues to run");
}
}
在上面的代码中,当试图除以0时,Java抛出一个ArithmeticException异常。在try-catch块中,我们捕获并处理该异常,打印出错误消息。然后程序继续执行,输出"Program continues to run"。
PS:上面给了几个Java标准异常类,Java标准异常类是一组用于表示各种异常情况的类。这些类都是预定义的,开发人员可以使用它们来描述常见的异常情况,并相应地处理它们。
(2)Error类
错误(Error):指的是 JVM 在运行时发生的严重问题,这些问题无法被程序员或者用户处理,例如 OutOfMemoryError(内存溢出)和 StackOverflowError(栈溢出)等。通常情况下,应用程序不应该抛出 Error 异常,因为这些异常无法被捕获。
- StackOverflowError:当一个应用程序递归太深或调用栈太大时,会导致此错误。
- NoClassDefFoundError:当Java虚拟机无法找到所需的类文件或类文件不存在时,会导致此错误。
- OutOfMemoryError:当应用程序在分配给它的内存空间中无法继续分配新对象时,会导致此错误。
- AssertionError:当Java断言失败时,会导致此错误。
- InternalError:当Java虚拟机出现内部错误时,会导致此错误。
- UnSupportedClassVersionError:当Java虚拟机无法识别当前运行的类文件的版本时,会导致此错误。
- VirtualMachineError:当Java虚拟机发生内部错误或资源不足时,会导致此错误。
- LinkageError:链接错误,当Java虚拟机在链接阶段遇到错误时抛出。例如,无法找到所需的类。
举个例子:
public class OutOfMemoryErrorExample {
public static void main(String[] args) {
int[] arr = new int[Integer.MAX_VALUE];
}
}
该程序试图在内存中创建一个大小为 Integer.MAX_VALUE 的整数数组,超出了 JVM 可以分配的最大内存。因此,将引发 OutOfMemoryError 错误异常。
三、异常的继承关系
所有异常类都是从 Throwable 类继承而来的。其中最顶层的类是 java.lang.Throwable
,Throwable 是 Java 异常类层次结构的根类,它定义了一些方法和属性,可以被子类继承和重写。Throwable 类有两个重要的子类:Error 和 Exception。Error 类表示 Java 运行时系统内部的错误和资源耗尽错误,java.lang.Error
代表了严重的错误,一般无法恢复或处理,通常是由系统层面的问题引起,而 Exception 类则是所有异常的超类,表示程序运行过程中可能遇到的可预见异常。java.lang.Exception
是所有受检异常和运行时异常的父类。
Throwable 类中定义了以下常用方法:
- getMessage():获取异常信息
- getCause():获取导致异常的原因
- getStackTrace():获取异常堆栈信息
- toString():返回此Throwable对象的详细信息字符串,包括其类型和消息。
- getLocalizedMessage():获取本地化的异常信息,如果没有本地化信息,则返回getMessage()返回的信息。
- fillInStackTrace():重新填充该异常对象的堆栈跟踪,以便返回包含当前线程的堆栈跟踪信息。
- initCause(Throwable cause):初始化此可抛出的原因。
- printStackTrace(PrintStream s):将此可抛出的及其回溯打印到指定的输出流。
- printStackTrace(PrintWriter s):将此可抛出的及其回溯打印到指定的PrintWriter。
- setStackTrace(StackTraceElement[] stackTrace):将此可抛出的堆栈跟踪设置为指定的堆栈跟踪。
举个例子:
public class ExceptionExample {
//定义main方法
public static void main(String[] args) {
//使用try-catch语句捕获可能会抛出的异常
try {
//调用divide方法,将返回值赋值给result变量
int result = divide(10, 0);
//输出结果
System.out.println("Result: " + result);
} catch (ArithmeticException e) { //捕获除以0的算术异常
//输出异常信息
System.out.println("Error: " + e.getMessage()); //获取异常信息
System.out.println("Cause: " + e.getCause()); //获取导致异常的原因
System.out.println("Stack trace: "); //输出异常堆栈信息
e.printStackTrace(); //打印异常堆栈信息
}
}
//定义divide方法,可能会抛出算术异常
public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) { //判断除数是否为0
throw new ArithmeticException("Divide by zero error."); //手动抛出算术异常
}
return a / b; //返回除法运算结果
}
}
四、异常处理语句
Java中用于处理异常的语句主要包括try-catch语句、try-finally语句、try-catch-finally语句和throw语句。
1.try-catch语句:
try-catch语句用于捕获可能会抛出的异常。它的基本语法如下:
try {
// 可能会抛出异常的代码
} catch (ExceptionType1 e1) {
// 处理ExceptionType1类型的异常
} catch (ExceptionType2 e2) {
// 处理ExceptionType2类型的异常
} finally {
// finally代码块会在try或catch中的代码执行完毕后被执行,用于释放资源等操作
}
try代码块中是可能会抛出异常的代码,如果抛出了指定类型的异常,catch代码块中的代码将会被执行,用于处理异常。如果try代码块中没有抛出异常,则catch代码块不会被执行。finally代码块中的代码无论是否有异常都会被执行。
2.try-finally语句:
try-finally语句用于释放资源等操作,无论是否有异常发生都会执行finally代码块中的代码。它的基本语法如下:
try {
// 可能会抛出异常的代码
} finally {
// finally代码块会在try中的代码执行完毕后被执行,用于释放资源等操作
}
如果try代码块中没有抛出异常,finally代码块中的代码会被正常执行。如果try代码块中抛出了异常,finally代码块中的代码也会被执行,用于释放资源等操作。
3.try-catch-finally语句:
try-catch-finally语句结合了try-catch和try-finally两种语句的功能,既能捕获异常,也能释放资源。它的基本语法如下:
try {
// 可能会抛出异常的代码
} catch (ExceptionType1 e1) {
// 处理ExceptionType1类型的异常
} catch (ExceptionType2 e2) {
// 处理ExceptionType2类型的异常
} finally {
// finally代码块会在try或catch中的代码执行完毕后被执行,用于释放资源等操作
}
与try-catch语句类似,如果try代码块中抛出了指定类型的异常,相应类型的catch代码块中的代码将会被执行,如果try代码块中没有抛出异常,catch代码块不会被执行。finally代码块中的代码无论是否有异常都会被执行。
4.try-with-resources语句:
try-with-resources
是 Java 7 引入的语法糖,它可以让开发者更方便地处理资源的释放,如文件、数据库连接等资源,可以自动调用资源的 close()
方法,无需手动进行关闭操作。
使用 try-with-resources
的语法格式为:
try (ResourceType resource = new ResourceType();) {
// 使用资源
} catch (Exception e) {
// 处理异常
}
其中 ResourceType
是资源的类型,需要实现 AutoCloseable
或 Closeable
接口,这两个接口都有一个 close()
方法。
在 try
代码块结束时,会自动调用资源的 close()
方法,无论代码块是否抛出异常。如果资源对象是在 try
代码块内部声明的,则该资源对象的作用域仅限于 try
代码块。
try-with-resources和传统的try-catch-finally语句的区别在于资源的释放上。
在传统的try-catch-finally语句中,我们需要在finally块中手动释放打开的资源,否则这些资源就可能会一直被占用,导致内存泄漏等问题。而在使用try-with-resources语句时,Java会自动关闭我们在try语句中打开的资源,无论在try块中是否发生异常,都会在代码块执行完毕后关闭这些资源。
在语法的使用上,try-with-resources
使用try
关键字后跟着一对圆括号,圆括号里面是需要自动关闭的资源对象,例如FileInputStream
、Scanner
等实现了AutoCloseable
接口的对象,这些资源对象在执行完毕后会自动关闭。
try (Resource1 resource1 = new Resource1();
Resource2 resource2 = new Resource2();) {
// 使用 resource1 和 resource2 执行代码
} catch (Exception e) {
// 处理异常
}
而try-catch-finally
语句块则使用try
、catch
、finally
三个关键字,语法如下:
try {
// 执行代码
} catch (Exception e) {
// 处理异常
} finally {
// 关闭资源
}
五、抛出异常与自定义异常类
1.自定义异常类
在 Java 中,除了使用已有的异常类来处理异常,我们还可以自定义异常类,以满足特定的业务需求。自定义异常类需要继承于 Exception 或 RuntimeException 类,通常情况下我们会继承 Exception 类,因为它属于受检异常,需要在代码中进行处理。
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
我们自定义了一个名为 MyException 的异常类,它继承自 Exception 类。在 MyException 类中,我们定义了一个构造方法,它接收一个字符串类型的参数 message,并将该参数传递给 Exception 类的构造方法,用于设置异常信息。
使用自定义异常类时,可以像使用其他异常类一样使用它,例如:
public class Example {
public static void main(String[] args) {
try {
throw new MyException("This is a custom exception");
} catch (MyException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
在这个例子中,我们创建了一个 MyException 异常对象并将其抛出。在 catch 语句块中,我们捕获了该异常,并输出了异常信息。
2.throw与throws
(1)throw
在 Java 中,可以使用 throw
关键字抛出异常。throw
关键字通常与 try-catch
或者 throws
关键字一起使用。使用 throw
关键字抛出异常的语法格式如下:
其中,ThrowableInstance
是指需要抛出的异常对象,它必须是 Throwable
或其子类的一个实例。使用 throw
抛出异常后,程序就会立即停止执行当前方法中剩余的代码,转而开始查找该方法对应的异常处理代码。如果当前方法没有异常处理代码,或者处理代码无法处理该异常,则该异常会沿着方法调用栈一层层向上抛出,直到被处理或者到达程序的顶层。
自定义异常类不一定必须使用throws
声明抛出异常,这取决于在什么情况下抛出异常。如果在自定义异常类中抛出的异常是在类的方法中发生的,则需要在方法签名中使用throws
关键字声明抛出该异常。但是,如果自定义异常类仅仅是作为一个标识符使用,例如表示应用程序中的某些特定情况,则可以不需要使用throws
声明抛出异常。
举个例子:
public class Example {
public static void main(String[] args) {
try {
throw new Exception("Something went wrong.");
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
在上面的代码中,我们使用 throw
关键字手动抛出了一个 Exception
类型的异常,并在 catch
块中捕获并处理了这个异常。由于我们在 throw
语句中指定了异常的消息为 "Something went wrong."
,因此在程序运行时,会输出以下信息:
(2)throws
throws是用于方法声明中的关键字,用于指示该方法可能会抛出哪些异常。它的语法格式如下:
其中,throws后面可以跟多个异常类,表示该方法可能抛出这些异常。当方法中可能会抛出异常,但是该方法不知道如何处理这些异常时,可以使用throws关键字将这些异常抛给上一级调用方法或者JVM来处理。
举个例子:
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class q {
public void readFile(String filename) throws FileNotFoundException, IOException {
FileReader fileReader = new FileReader(filename);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
}
}
其中包含一个名为"readFile"的方法。这个方法接受一个字符串类型的参数"filename",用于指定要读取的文件名。在这个方法内部,它首先使用传入的文件名创建一个FileReader对象,然后使用BufferedReader对象读取文件中的每一行,并将其输出到控制台。最后,它关闭BufferedReader对象,结束文件读取操作。
需要注意的是,readFile方法声明了抛出FileNotFoundException和IOException两个异常,这意味着在方法执行期间可能会抛出这两种异常,因此在调用该方法时,必须使用try-catch块或者继续向上抛出异常(使用throws关键字)。
由于代码中没有给出调用者,因此无法确定调用者是谁。如果此类被其他类所调用,那么抛出的异常将会传递给调用它的方法。如果此类是应用程序的入口点,那么异常将会传递给 Java 虚拟机 (JVM)。