程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。Java提供了优秀的解决办法:异常处理机制。异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Java异常机制用到的几个关键字:try、catch、finally、throw、throws。
try-- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
catch-- 用于捕获异常。catch用来捕获try语句块中发生的异常。
finally-- finally语句块总是会被执行。
它主要用于回收在try块里打开的物理资源(如数据库连接、网络连接和磁盘文件)。
只有finally块执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语
句,则就不会跳回执行,直接停止。
throw-- 用于抛出异常。
throws-- 用在方法签名中,用于声明该方法可能抛出的异常。主方法上也可以使用throws抛出。如果在主方法上使用了throws抛出,就表示在主方法里面可以不用强制性进行异常处理,如果出现了异常,就交给JVM进行默认处理,则此时会导致程序中断执行。
产生异常的原因:
1、用户输入了非法数据。
2、要打开的文件不存在。
3、网络通信时连接中断,或者JVM内存溢出。
这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
Throwable 是 Java 语言中所有错误与异常的超类。Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。Java 编译器不会检查它,也就是说:当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。
RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
Exception 中除 RuntimeException 及其子类之外的异常。Java 编译器会检查它,如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)、 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。这些错误是不受检异常,非代码性错误。
1、检查性异常:是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。
编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。**除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常。**编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。
1、在正常执行的情况下,先执行try中的代码再执行finally中的代码。
package com.zs.thread;
public class TestThrow {
public static void main(String[] args) {
try {
System.out.println("try");
int a = 1 / 1;
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
}
System.out.println("end");
}
}
2、如果try中的代码触发异常,并且异常没有被捕获,finally中代码会被直接执行,并且在执行之后重新抛出该异常;
package com.zs.thread;
public class TestThrow {
public static void main(String[] args) {
try {
System.out.println("try");
int a = 1 / 0;
} finally {
System.out.println("finally");
}
System.out.println("end"); // 不会被执行
}
}
3、如果异常被catch捕获,先执行catch中的代码再执行finally中的代码。
package com.zs.thread;
public class TestThrow {
public static void main(String[] args) {
try {
System.out.println("try");
int a = 1 / 0;
} catch (ArithmeticException e) {
System.out.println("catch");
} finally {
System.out.println("finally");
}
System.out.println("end");
}
}
4、如果catch中的代码也触发了异常,那么finally中代码同样会被执行,并会抛出catch代码触发的那个异常。
package com.zs.thread;
public class TestThrow {
public static void main(String[] args) {
try {
System.out.println("try");
int a = 1 / 0;
} catch (ArithmeticException e) {
int a = 1 / 0;
System.out.println("catch");
} finally {
System.out.println("finally");
}
System.out.println("end"); // 不会被执行
}
}
5、如果finally中的代码也触发了异常,那么会中断当前finally 代码的执行,并抛出异常。
package com.zs.thread;
public class TestThrow {
public static void main(String[] args) {
try {
System.out.println("try");
int a = 1 / 0;
} catch (ArithmeticException e) {
int a = 1 / 0;
System.out.println("catch");
} finally {
int a = 1 / 0;
System.out.println("finally");
}
System.out.println("end");
}
}
主要是通过异常表。在编译生成的字节码中,每个方法都附带一个异常表。异常表中可能有多条记录,每一条记录都包括from指针、to 指针、target指针和所捕获的异常类型。这些指针的值是字节码索引(bytecode index),用于定位字节码。其中,from指针和to指针表示异常处理监控的范围,比如: try所覆盖的范围。target指针指向异常处理代码的起始位置,比如:catch代码的起始位置。
如果有异常触发时,Java虚拟机会从上至下遍历异常表中的所有记录。当触发异常的字节码的索引值在某个异常表记录的监控范围内,Java虚拟机会判断所抛出的异常和该记录想要捕获的异常是否匹配。如果匹配,控制流转将会移至该记录 target 指针指向的字节码。
public class OneMore {
public static void main(String[] args) {
String str = "哈尔滨理工大学";
try {
str = "try";
} catch (Exception e) {
str = "catch";
}
}
}
这段代码的 main 方法中,我定义了一段try 和catch 代码。编译过后的字节码中,这个方法的异常表拥有一个记录:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: ldc #2 // String 哈尔滨理工大学
2: astore_1
3: ldc #3 // String try
5: astore_1
6: goto 13
9: astore_2
10: ldc #5 // String catch
12: astore_1
13: return
Exception table:
from to target type
3 6 9 Class java/lang/Exception
其from指针和to指针分别为 3 和 6,代表它的监控范围从索引为 3 的字节码开始,到索引为 6 的字节码结束(不包括 6)。该记录的 target 指针是 9,代表这个异常处理从索引为 9 的字节码开始。该记录的最后一列,代表该异常处理所捕获的异常类型是Exception。
当触发异常的字节码的索引值在 3 和 6 之间时,Java虚拟机会判断所抛出的异常是否时Exception。如果是,控制流转将会移至索引为 9 的字节码开始执行。