什么是异常
在Java中,异常(Exception)是程序执行期间发生的事件,它打断了正常的指令流。这些事件通常是程序中的错误或问题,比如试图除以零、访问不存在的文件、网络问题等。Java提供了一种结构化的、统一的方式来处理这类问题,即异常处理机制。
异常的体系
运行时异常
运行时异常(Runtime Exceptions)在Java中是一类特殊的异常,它们不需要在编译时被显式地捕获或声明抛出。这些异常通常是由程序中的逻辑错误或不当操作引起的,例如空指针引用、数组越界、类型转换错误等。
由于这些异常通常是可避免的,并且通常表示程序中的错误,因此Java的设计者认为程序员应该通过编写健壮的代码来避免它们,而不是通过异常处理机制来捕获它们。
然而,尽管Java不要求捕获运行时异常,但在实际开发中,有时我们可能也需要捕获和处理这些异常,以便在它们发生时能够优雅地恢复或至少记录错误信息。
运行时异常的特点
- 非检查型异常:运行时异常不需要在编译时被显式地捕获或声明抛出。
- 通常由程序内部逻辑错误导致:例如空指针引用、数组越界、类型转换错误等。
- JVM默认处理:如果运行时异常没有被捕获,JVM会按照默认的方式处理,通常是打印异常堆栈跟踪信息到标准错误输出,并终止当前线程(如果是主线程,则终止整个Java应用程序)。
常见的运行时异常及其示例代码
1. NullPointerException
(空指针异常)
当程序试图访问或操作一个空对象引用时抛出。
public class NullPointerExample {
public static void main(String[] args) {
String text = null;
System.out.println(text.length()); // 这里会抛出NullPointerException
}
}
2. ArrayIndexOutOfBoundsException
(数组索引越界异常)
当程序试图访问数组的非法索引时抛出。
public class ArrayIndexExample {
public static void main(String[] args) {
int[] array = new int[5];
System.out.println(array[10]); // 这里会抛出ArrayIndexOutOfBoundsException
}
}
3. ClassCastException
(类型转换异常)
当试图将对象强制转换为不是实例的类时抛出。
public class ClassCastExceptionExample {
public static void main(String[] args) {
Object obj = new Integer(1);
String str = (String) obj; // 这里会抛出ClassCastException
}
}
4. ArithmeticException
(算术异常)
当算术运算中发生错误时抛出,比如除以零。
public class ArithmeticExceptionExample {
public static void main(String[] args) {
int result = 10 / 0; // 这里会抛出ArithmeticException
}
}
5. NumberFormatException
(数字格式异常)
当试图将字符串转换为数字类型而字符串的格式不正确时抛出。
public class NumberFormatExceptionExample {
public static void main(String[] args) {
int number = Integer.parseInt("abc"); // 这里会抛出NumberFormatException
}
}
编译时异常
Java中的编译时异常是那些在编译阶段就需要被检查并处理的异常。
编译时异常的特点
-
必须被显式处理:如果方法可能抛出编译时异常,那么调用该方法的代码必须显式地处理这些异常,要么通过try-catch语句捕获并处理它们,要么通过throws关键字在方法签名中声明它们,以便将异常传播给方法的调用者。
-
属于
Exception
类及其非运行时子类:编译时异常是Exception
类及其非RuntimeException
子类的实例。这意味着,所有继承自Exception
但不是RuntimeException
或其子类的异常都是编译时异常。 -
用于指示可恢复的异常情况:编译时异常通常用于指示那些可以通过合理的程序逻辑来恢复或处理的异常情况。例如,文件不存在、网络连接失败等,这些情况在程序运行时可能会发生,但通常可以通过重试、使用备用资源或向用户报告错误等方式来处理。
例子:日期解析异常
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class demo {
public static void main(String[] args) {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date day = sdf.parse("2024-7-31 19:21:11");
System.out.println(day);
}
}
这段代码就存在问题。
异常代码层次的处理方式
①抛出异常(throws)
throws
关键字用于在方法签名中声明该方法可能会抛出的异常类型。当方法内部发生了异常,并且这个异常在当前方法内部没有被捕获(即没有相应的try...catch
块来处理它)时,这个异常就可以通过throws
声明被“抛出”给方法的调用者。
使用throws
的关键点:
- 它只出现在方法签名中。
- 它后面跟的是异常类型,可以是单个异常类型,也可以是多个异常类型(Java 7及以后版本支持使用逗号分隔的多个异常类型)。
- 它是一种声明性的方式,告诉方法的调用者这个方法可能会产生哪些类型的异常。
- 如果方法内部使用了
throw
关键字来显式地抛出一个异常对象,那么这个异常对象必须是throws
声明中指定的异常类型或其子类型的实例。
②捕获异常(try...catch)
try...catch
语句块用于捕获并处理异常。当try
块中的代码发生异常时,程序会立即跳转到相应的catch
块(如果有匹配的异常类型的话)去执行。每个try
块后面可以跟着一个或多个catch
块,用于捕获并处理不同类型的异常。
使用try...catch
的关键点:
try
块包含了可能抛出异常的代码。- 每个
catch
块紧跟在try
块之后,并且指定了它能够捕获的异常类型。 - 如果有多个
catch
块,它们会按照顺序被检查,直到找到与抛出的异常类型相匹配的catch
块为止。 - 如果
try
块中的代码没有抛出任何异常,那么try...catch
结构中的catch
块将不会被执行。 try...catch
后面还可以跟着一个可选的finally
块,无论是否捕获到异常,finally
块中的代码都会被执行(除非程序在执行try
或catch
块时遇到了System.exit()
调用或JVM崩溃等极端情况)。- 快捷键:Ctrl + Alt + T(Windows/Linux)。这是IDEA中最常用的添加try-catch语句块的快捷键。用户只需选中需要包裹在try-catch块中的代码,然后按下
Ctrl + Alt + T
,IDEA会自动生成try-catch结构,并将选中的代码包含在内。
示例代码
下面是一个结合了throws
和try...catch
的异常处理示例:
//捕获异常处理
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class demo {
public static void main(String[] args) {
try {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date day = sdf.parse("2024-7-31 19:21:11");
System.out.println(day);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
//抛出异常处理
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class demo {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date day = sdf.parse("2024-7-31 19:21:11");
System.out.println(day);
}
}