什么是Java异常机制
Java异常机制是java语言为我们提供一种异常处理机制,在java语言中,异常本身是一个类,产生异常就是创建异常对象并抛出这个异常对象,程序发生异常情况之后程序会抛出封装了错误信息的异常对象,程序员通过这个信息,可以对程序进行一些处理,使程序更加健壮。
异常指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
Java异常架构
1、Throwable
Throwable 是 Java 语言中所有错误与异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
2、Error(错误)
定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
特点:此类错误一般表示代码运行时 JVM 出现问题。
常见的Error错误:
-
OutOfMemoryError(内存溢出错误):当程序申请的内存超过了JVM的限制时,会抛出该错误。
-
StackOverflowError(栈溢出错误):当方法调用的层数过多时,会导致栈溢出,从而抛出该错误。
-
NoClassDefFoundError(类未定义错误):当试图使用不存在的类或接口时,会抛出该错误。
-
UnsatisfiedLinkError(链接错误):当试图使用不存在的本地方法库时,会抛出该错误。
-
AssertionError(断言错误):当断言语句失败时,会抛出该错误
此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!
3、Exception(异常)
程序本身可以捕获并且可以处理的异常。
运行时异常是非受检异常,是RuntimeException的子类,即编译器无法检测,因此也不会强制要求程序员处理。
编译时异常是受检异常,编译器检测到代码抛出编译时异常时,会要求程序员必须对该异常做处理(throws或try…catch)否则,编译不通过。
1、运行时异常(非受检异常(unchecked exception))
定义:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。
特点:Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws 声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。
常见的运行时异常:
-
NullPointerException(空指针异常):当尝试访问一个空引用时,会抛出该异常。
-
ArrayIndexOutOfBoundsException(数组越界异常):当尝试访问数组中不存在的元素时,会抛出该异常。
-
ClassCastException(类型转换异常):当尝试将一个对象转换成不兼容的类型时,会抛出该异常。
-
IllegalArgumentException(非法参数异常):当方法接收到一个不合法的参数时,会抛出该异常。
-
IllegalStateException(非法状态异常):当对象的状态不符合方法的要求时,会抛出该异常。
-
UnsupportedOperationException(不支持的操作异常):当对象不支持当前方法的操作时,会抛出该异常。
-
ArithmeticException(算术异常):当算术运算出现异常时,会抛出该异常,例如除以0。
-
ConcurrentModificationException(并发修改异常):当多个线程同时修改同一个对象时,会抛出该异常。
此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生! RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
2、编译时异常(受检异常(checked exception))
定义: Exception 中除 RuntimeException 及其子类之外的异常。
特点: Java 编译器会检查它。如果程序中出现此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。
常见的编译时异常:
-
IOException(输入输出异常):当读写文件或网络连接时出现异常时,会抛出该异常。
-
ClassNotFoundException(类未找到异常):当试图加载不存在的类时,会抛出该异常。
-
NoSuchMethodException(方法未找到异常):当试图调用不存在的方法时,会抛出该异常。
-
IllegalAccessException(非法访问异常):当试图访问私有方法或属性时,会抛出该异常。
-
InstantiationException(实例化异常):当试图实例化抽象类或接口时,会抛出该异常。
-
InterruptedException(中断异常):当线程被中断时,会抛出该异常。
-
SQLException(SQL异常):当访问数据库时出现异常时,会抛出该异常。
Java异常关键字
try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被catch捕获。
catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。
throw – 用于抛出异常,在方法内部通过 throw 拋出异常对象。
throws – throws在方法上声明该方法要拋出的异常
Java异常处理机制
Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。
在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。
1、声明异常
当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。可以在方法签名处使用 throws 关键字声明可能会抛出的异常。
returnType method_name(paramList) throws Exception 1,Exception2,…{…}
2、抛出异常
检测到错误的程序可以创建一个合适的异常类型的实例并抛出它, 这就称为抛出一个异常(throwing an exception)。throw关键字作用是在方法内部抛出一个Throwable类型的异常。任何Java代码都可以通过throw语句抛出异常。
3、捕获异常
程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。
如何选择异常类型
在Java中,异常分为两种:checked异常和unchecked异常。选择异常类型的原则如下:
1. 如果异常是可以预见的,并且可以通过代码来处理,那么应该使用checked异常。例如,如果在方法中打开文件,就应该使用IOException来表示文件操作可能会出现的异常。
2. 如果异常是不可预见的,并且无法通过代码来处理,那么应该使用unchecked异常。例如,如果在方法中使用数组,但是数组下标超出了范围,就应该使用ArrayIndexOutOfBoundsException来表示此类异常。
3. 如果异常是由程序员编程错误引起的,那么应该使用unchecked异常。例如,如果在方法中传递了null参数,就应该使用NullPointerException来表示此类异常。
4. 如果异常是由于环境因素引起的,例如网络连接中断、数据库连接超时等,那么应该使用checked异常来表示此类异常。
总之,选择异常类型应该根据异常的特点和产生原因来进行选择,以便在程序运行时能够更好地处理异常,提高程序的健壮性和可靠性。
常见异常处理方式
1. try-catch块
使用try-catch块可以捕获并处理异常。try块中包含可能会抛出异常的代码,如果抛出了异常,则会跳转到对应的catch块进行处理。catch块中可以处理异常,并进行相应的操作。
示例代码:
try {
// 可能会抛出异常的代码
int a = 1 / 0;
} catch (Exception e) {
// 处理异常并进行相应的操作
System.out.println("发生异常:" + e.getMessage());
}
同一个 catch 也可以捕获多种类型异常,用 | 隔开
private static void readFile(String filePath){
try{
// code
}catch(FileNotFoundException| UnknownHostException e){
// handle FileNotFoundException or UnknownHostException
}catch(IOException e){
// handle IOException
}
}
2. throws关键字
使用throws关键字可以将异常抛给调用该方法的代码进行处理。在方法声明中使用throws关键字声明可能会抛出的异常,如果方法中抛出了该异常,则该异常会被抛给调用该方法的代码进行处理。
示例代码:
public void readFile(String filePath) throws IOException {
// 可能会抛出异常的代码
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
}
3. try-catch-finally块
finally块中的代码总是会被执行,无论是否发生异常。在finally块中可以进行资源的释放、清理等操作。
示例代码:
FileWriter writer = null;
try {
writer = new FileWriter("test.txt");
writer.write("Hello World!");
} catch (IOException e) {
System.out.println("发生异常:" + e.getMessage());
} finally {
// 释放资源
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
System.out.println("关闭资源时发生异常:" + e.getMessage());
}
}
}
4. 自定义异常类
在Java中,可以通过继承Exception或RuntimeException类来定义自己的异常类。在程序中抛出自定义异常,可以让调用该方法的代码更好地理解异常的含义。
示例代码:
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
public void test() throws MyException {
// 抛出自定义异常
throw new MyException("自定义异常");
}
5、try-with-resource
上面例子中,try-with-resources是Java 7引入的一种异常处理方式,用于自动关闭资源。在try块中打开资源,try块结束时会自动关闭资源,无需手动关闭。如果在关闭资源时发生异常,则会抛出该异常。finally 中的 close 方法也可能抛出 IOException, 从而覆盖了原始异常。JAVA 7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类。
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
骚戴理解:首先细心看出try-with-resources和try-catch的区别,区别就是两者写法不同,try-with-resources的写法try(创建需要自动关闭资源的对象){对对象的操作}catch{},也就是在try()里的对象会自动关闭资源
try-with-resources写法
try-catch-finally写法
Error 和 Exception 区别是什么?
在Java中,Error和Exception都是Throwable的子类,表示程序中可能出现的异常情况。它们的区别如下:
1. Error是指在程序运行时出现的严重问题,通常是由于系统错误或资源耗尽等问题引起的,例如OutOfMemoryError、StackOverflowError等。这些异常一般是无法恢复的,程序无法继续执行,因此不需要进行捕获和处理。
2. Exception是指在程序运行时出现的一般性问题,通常是由于用户输入错误、网络连接中断等问题引起的,例如IOException、SQLException等。这些异常可以被捕获和处理,程序可以通过处理异常来恢复正常的执行。
运行时异常和一般异常(受检异常)区别是什么?
1、运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。受检异常(一般异常)是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
2、是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。
JVM 是如何处理异常的?
在一个方法中如果发生异常,这个方法会创建一个异常对象(包含异常名称,异常描述以及异常发生时应用程序的状态)并转交给JVM,这个过程叫抛出异常。JVM 会顺着调用栈去查找看是否有可以处理异常的代码(在进入抛出异常的方法之前可能会先调用一系列的方法,这一系列方法调用的有序列表叫做调用栈)
- 如果有,则调用异常处理代码。即当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。
- 如果 JVM 没有找到可以处理该异常的代码块, JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
throw 和 throws 的区别是什么?
throw和throws都与Java中的异常处理相关,但它们的含义和用法不同。
1. throw
throw用于抛出一个异常对象。在程序中,如果遇到了某种错误或异常情况,可以使用throw语句抛出一个异常对象。throw语句后面跟着一个异常对象,可以是Java内置的异常类,也可以是自定义的异常类。
示例代码:
if (a < 0) {
throw new IllegalArgumentException("a不能小于0");
}
在这个示例中,如果a的值小于0,就会抛出一个IllegalArgumentException异常对象。
2. throws
throws用于声明一个方法可能会抛出的异常。在Java中,如果一个方法可能会抛出某种异常,可以在方法声明中使用throws关键字声明该异常。throws关键字后面跟着异常类的名称,多个异常类之间用逗号分隔。
示例代码:
public void readFile(String filePath) throws IOException {
// 可能会抛出异常的代码
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
}
在这个示例中,readFile方法可能会抛出IOException异常,因此在方法声明中使用了throws关键字声明该异常。
骚戴理解:Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常或者在方法内部通过 throw 拋出异常对象。throws可以声明多个异常,throw只能抛出一个具体的异常实例
NoClassDefFoundError 和 ClassNotFoundException 区别?
- ClassNotFoundException是异常,NoClassDefFoundError 是错误
- ClassNotFoundException异常是指使用类加载器就加载某个类的时候,发现所有的path下面都没有找到,从引导类路径,扩展类路径到当前的classpath下全部没有找到那个类,就会抛出ClassNotFoundException异常
- NoClassDefFoundError错误是指编译时存在某个类,但是运行时却找不到或者是使用了一个初始化失败的类就会直接抛出NoClassDefFoundError错误
ClassNotFoundException
使用类加载器就加载某个类的时候,发现所有的path下面都没有找到,从引导类路径,扩展类路径到当前的classpath下全部没有找到,就会抛出ClassNotFoundException异常
常见的例子就是加载JDBC驱动包的时候,它的依赖jar并不在classpath里面,如下:
package class_loader.exception;
public class ExceptionTest {
public static void main(String[] args)throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
}
}
就会抛出异常ClassNotFoundException:
Exception in thread "main" java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at class_loader.exception.ExceptionTest.main(ExceptionTest.java:8)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
NoClassDefFoundError是错误,不是异常!
这个错误,主要有两种情况:
(1)编译时存在某个类,但是运行时却找不到,如下:
public class A {
public void hello(){
System.out.println("A hello");
}
}
class B {
public static void main(String[] args) {
A a=new A();
}
}
上面的Java类编译后会生成两个类文件,一个A.class,一个B.class,现在我在编译后,删掉了A的class文件,然后直接执行B的main方法,就会抛出 NoClassDefFoundError错误,因为当执行到 A a=new A();这一步的时候,jvm认为这个类肯定在当前的classpath里面的,要不然编译都不会通过,更不用提执行了。既然它存在,那么在jvm里面一定能找到,如果不能找到,那就说明出大事了,因为编译和运行不一致,所以直接抛出这个ERROR,代表问题很严重。
(2)第二种情况,类根本就没有初始化成功,结果你还把它当做正常类使用,所以这事也不小,必须抛出ERROR告诉你不能再使用了。
看下面的一段代码:
public class Loading {
static double i=1/0;//故意使得类初始化失败.
public static void print(){
System.out.println("123");
}
}
调用如下:
public static void main(String[] args) {
try {
double i=Loading.i;
}catch (Throwable e){
//此处,必须用Throwable,用Exception会直接退出.
System.out.println(e);
}
//继续使用.
Loading.print();
}
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class class_loader.exception.Loading
java.lang.ExceptionInInitializerError
at class_loader.exception.NoClassFoundErrorTest.main(NoClassFoundErrorTest.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
注意这种情况比较特殊,并不是因为编译时和运行时环境不一致导致的,而是对于一个类如果初始化失败后,你还继续使用,那么JVM会认为是不正常的,由于它第一次调用已经失败,JVM就会假设后面继续调用肯定仍然会失败,所以直接抛ERROR给客户端。
这里需要注意,类初始化失败的异常是:
java.lang.ExceptionInInitializerError
也是一个严重级别的错误。
骚戴理解:直接采用反射或者类加载器的loadClass方法去动态加载一个所有classpath里面的都不存在的类,类加载器在运行时的load阶段就会直接抛出ClassNotFoundException异常。此外jvm认为这个异常是可以被预知的需要提前被check。对于另一种请情况,如果在编译时候正常,但在运行时执行new关键词的时候,发现依赖类找不到,或者是对于初始化失败的一个类,再次访问其静态成员或者方法,那么会直接抛出NoClassDefFoundError错误。这两种异常本质上的侧重点还是不一样的,前者侧重在类加载器加载阶段找不到类信息,后者则侧重在使用阶段时却出现了问题比如实例化依赖类找不到或者类本身就初始化失败了。
try-catch-finally 中哪个部分可以省略?
以下三种情况都是可以的:
try-catch
try-finally
try-catch-finally
可以省略catch或者finally。catch和finally不可以同时省略。
try-catch-finally 中,return的执行顺序到底是怎样的?
return的执行顺序首先要看finally到底有没有return?不同情况不同结果
- 如果有return肯定会覆盖try或者catch中的return。
- 如果没有return的语句,那就看返回值是什么类型的数据
-
- 如果返回值是基本数据类型,finally块中对返回值的改变不会影响返回值。因为在return之前已经将返回值的内容存储在栈中了。
- 如果返回值是引用数据类型,finally块中对返回值的改变会影响返回值。因为在return之前已经将引用对象的地址存储在栈中,finally块中对于引用对象值的改变会影响到返回值。
有以下情况
return的执行顺序首先要看finally到底有没有return?不同情况不同结果
如果有return肯定会覆盖try或者catch中的return。
finally中的return覆盖catch中的return。
public static int testTryCatch5() {
int result = 0;
try {
result = 1/0;
return result;
} catch (Exception e) {
return result;
} finally {
result=2;
return result;
}
}
//返回2
finally中的return覆盖try中的return
public static int testTryCatch1() {
int result = 0;
try {
result = 1;
return result;
} catch (Exception e) {
return result;
} finally {
result=2;
return result;
}
}
//返回2
如果没有return的语句,那就看返回值是什么类型的数据
如果方法返回的是基本数据类型,那么finally块中对返回值的改变不会影响返回值。因为在方法执行完毕之前,返回值已经被保存在栈中了,finally块中对返回值的改变只会改变变量的值,而不会影响已经保存在栈中的返回值。
public static int test() {
int result = 0;
try {
result = 1;
System.out.println("执行try块中的代码");
return result;
} catch (Exception e) {
System.out.println("执行catch块中的代码");
} finally {
result = 2;
System.out.println("执行finally块中的代码");
}
return result;
}
//返回1
如果方法返回的是引用类型,那么finally块中对返回值的改变会影响返回值。因为在方法执行完毕之前,返回值只是一个引用,保存的是对象的地址,而不是对象本身。如果在finally块中改变了对象的状态,那么返回值也会受到影响。
public static List<String> test() {
List<String> list = new ArrayList<>();
try {
list.add("a");
System.out.println("执行try块中的代码");
return list;
} catch (Exception e) {
System.out.println("执行catch块中的代码");
} finally {
list.add("b");
System.out.println("执行finally块中的代码");
}
return list;
}
//返回a,b
如果 catch 中 return 了,finally 还会执行吗?
会,如果在catch中return了,也会在return之前,先执行finally代码块,finally的作用就是无论出现什么状况,finally里的代码一定会被执行(理论上一定执行,实际上finally也不是一定会被执行)。而且如果finally代码块中含有return语句,会覆盖其他地方的return。如果finally代码块中不含有return语句,对于基本数据类型的数据,在finally块中改变return的值对返回值没有影响,而对引用数据类型的数据会有影响。
什么情形下finally代码块不会执行?
在以下情况下,finally代码块可能不会执行:
1. 在执行try块中的代码时,程序遇到了System.exit()方法,该方法会立即终止程序的执行,finally块中的代码不会被执行。
2. 在执行try块中的代码时,程序遇到了Thread.stop()方法,该方法会立即停止线程的执行,finally块中的代码不会被执行。
3. 在执行try块或catch块中的代码时,程序遇到了无限循环或死循环,finally块中的代码不会被执行。
4. 在执行try块或catch块中的代码时,程序遇到了JVM崩溃或断电等不可预知的情况,finally块中的代码不会被执行。
5.在守护线程里的finally可能不会一定执行,因为守护线程是依赖用户线程的,如果用户线程结束不管你守护线程执行完没都会结束守护线程,所以这个时候里面的finally的代码是不会执行了
在以上情况下,finally块中的代码不会被执行,因为程序已经无法正常执行下去了。在其他情况下,finally块中的代码都会被执行,无论try块中是否发生了异常,都可以保证finally块中的代码被执行。
常见的 RuntimeException 有哪些?
- ClassCastException( 类 转 换 异 常 )
- IndexOutOfBoundsException(数组下标越界)
- NullPointerException(空指针)
- ArrayStoreException(数据存储异常,操作数组时类型不一致)
- 还有IO操作的BufferOverflowException异常
- ArithmeticExecption 算术异常
Java常见异常有哪些
Java中常见的异常有以下几种:
1. NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
2. ArrayIndexOutOfBoundsException:当应用程序试图访问数组中不存在的元素时,抛出该异常。
3. ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
4. IllegalArgumentException:当向方法传递了一个不合法或不正确的参数时,抛出该异常。
5. NumberFormatException:当应用程序试图将字符串转换为数字时,但该字符串不具有适当的数字格式时,抛出该异常。
6. ArithmeticException:当发生异常条件,导致算术运算无法完成(如除以零)时,抛出该异常。
7. IOException:当发生IO操作异常时,如读写文件或网络连接时,抛出该异常。
8. SQLException:当操作数据库时发生异常时,抛出该异常。
9. InterruptedException:当线程在等待某个操作完成时被中断时,抛出该异常。
10. RuntimeException:当程序在运行时出现异常时,抛出该异常,如数组越界、空指针等。
以上是Java中常见的异常,程序员在编写程序时需要注意捕获这些异常,并进行相应的处理,以保证程序的正确性和健壮性。
Java异常使用注意事项
在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范进行异常处理的原因。而团队之间的这些规范往往是截然不同的。
对异常进行文档说明
当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。
在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。
public void doSomething(String input) throws MyBusinessException {...}
使用描述性消息抛出异常
在Java中,抛出异常时应该提供描述性消息,以便于调试和定位问题。描述性消息应该包含异常的原因、位置和可能的解决方法等信息,可以帮助程序员更好地理解和处理异常。以下是使用描述性消息抛出异常的示例代码:
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
public class Test {
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println(result);
} catch (MyException e) {
System.out.println(e.getMessage());
}
}
public static int divide(int a, int b) throws MyException {
if (b == 0) {
throw new MyException("除数不能为0");
}
return a / b;
}
}
在这个示例中,我们定义了一个自定义异常MyException,并在其中提供了描述性消息。在divide方法中,当除数为0时,我们抛出了该异常,并提供了描述性消息。在main方法中,我们捕获了该异常,并输出了异常的描述性消息。
使用描述性消息抛出异常可以帮助程序员更好地理解和处理异常,同时也可以提高程序的可读性和可维护性。在编写程序时,应该尽可能地提供描述性消息,以便于调试和定位问题。
优先捕获最具体的异常
在Java中,当程序抛出异常时,应该优先捕获最具体的异常,而不是使用过于宽泛的异常类型。最具体的异常指的是与具体问题相关的异常,例如NullPointerException、ArrayIndexOutOfBoundsException、FileNotFoundException等。这样可以更好地定位问题,并且可以更好地处理异常。
示例代码:
public void catchMostSpecificExceptionFirst(){
try{
doSomething("A message");
}catch(NumberFormatException e){
log.error(e);
}catch(IllegalArgumentException e){
log.error(e)
}
}
上面的代码片断中看到这样一个 try-catch 语句的例子。 第一个 catch 块处理所有NumberFormatException 异常,第二个处理所有非 NumberFormatException 异常的IllegalArgumentException 异常。这里的NumberFormatException 是 IllegalArgumentException 的子类,所以要优先去捕获,即优先捕获最具体的异常
因此,如果首先捕获IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的catch 块,因为它是 IllegalArgumentException 的子类。
总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。
不要捕获 Throwable 类
Throwable 是所有异常和错误的超类。你可以在 catch 子句中使用它,但是你永远不应该这样做!不要捕获 Throwable 类!
典型的例子是 OutOfMemoryError 或者StackOverflowError 。两者都是由应用程序控制之外的情况引起的,无法处理。
所以,最好不要捕获 Throwable ,除非你确定自己处于一种特殊的情况下能够处理错误。
public void doNotCatchThrowable(){
try{
// do something
}catch(Throwable t){
// don't do this!
}
}
其实就是java语法支持你像上面这样写,但是建议最好别这样写!如果在 catch 子句中使用 Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应用程序处理的严重问题。
捕获异常要处理异常
很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。
public void doNotIgnoreExceptions(){
try{
// do something
}catch(NumberFormatException e){
// this will never happen
}
}
但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。
合理的做法是至少要记录异常的信息,捕获异常要处理异常。
public void logAnException(){
try{
// do something
}catch(NumberFormatException e){
log.error("This should never happen: "+ e);
}
}
包装异常时不要抛弃原始的异常
在Java中,当需要包装异常时,应该避免抛弃原始的异常,而应该将原始的异常信息保留下来,以便于调试和定位问题。抛弃原始的异常会导致异常信息丢失,使得调试和定位问题变得困难。
以下是包装异常时不要抛弃原始的异常的示例代码:
public static void main(String[] args) {
try {
readFile("test.txt");
} catch (IOException e) {
System.out.println("读取文件出现异常:" + e.getMessage());
System.out.println("原始异常信息:" + e.getCause().getMessage());
}
}
public static void readFile(String fileName) throws IOException {
try {
FileInputStream fis = new FileInputStream(fileName);
// 读取文件内容
} catch (FileNotFoundException e) {
throw new IOException("文件不存在", e);
}
}
在这个示例中,我们定义了一个readFile方法,该方法用于读取文件内容。在该方法中,我们捕获了FileNotFoundException异常,并将其包装成IOException异常,并将原始的FileNotFoundException异常作为参数传递给IOException异常。在main方法中,我们调用了readFile方法,并捕获了IOException异常,并输出了异常信息和原始异常信息。
使用标准异常
如果使用标准的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。
异常会影响性能
在Java中,异常会对程序的性能产生一定的影响。异常的产生和处理都需要一定的时间和资源,如果异常发生的频率过高,就会导致程序的性能下降。
以下是异常会影响性能的示例代码:
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
try {
int result = divide(10, 0);
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("出现了算术异常:" + e.getMessage());
}
}
long endTime = System.currentTimeMillis();
System.out.println("程序执行时间:" + (endTime - startTime) + "ms");
}
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("除数不能为0");
}
return a / b;
}
在这个示例中,我们定义了一个divide方法,在该方法中,当除数为0时,我们抛出了ArithmeticException异常。在main方法中,我们调用了divide方法100000次,并捕获了该异常,并输出了异常信息。在程序执行完毕后,我们输出了程序的执行时间。
在这个示例中,由于异常的产生和处理,程序的执行时间明显增加。如果异常发生的频率过高,就会导致程序的性能下降,甚至影响程序的稳定性和可靠性。因此,在编写程序时,应该尽可能地避免异常的发生,同时合理地处理异常,以保证程序的性能和稳定性。
阿里巴巴Java开发手册中关于异常处理的要求如下:
1. 不要捕获不需要处理的异常。不需要处理的异常包括RuntimeException及其子类、Error及其子类、以及自定义异常中不需要捕获的异常。
2. 不要在finally块中使用return语句。finally块中的return语句会覆盖try块中的return语句,导致程序出现意外的结果。
3. 不要在finally块中关闭资源。关闭资源应该在try块中完成,并且应该使用try-with-resources语句来确保资源的正确关闭。
4. 不要使用异常做流程控制。异常应该用于处理异常情况,而不应该用于控制程序的流程。
5. 不要在catch块中使用空的异常处理语句。空的异常处理语句会导致异常信息被忽略,使得程序难以调试和定位问题。
6. 不要在catch块中使用System.out.println()等语句输出异常信息。应该使用日志记录器来记录异常信息,以便于调试和定位问题。
7. 不要忽略异常。捕获异常后应该对异常进行处理,而不是简单地将异常向上抛出。
总之,异常处理应该遵循“谨慎、简洁、明了”的原则,避免过度捕获异常、过度使用异常、过度抑制异常等不良习惯,以保证程序的健壮性和可维护性。