java程序异常,指的是程序出现了预料之外的事情。
- 编译期异常:也称为检测异常,不符合编译规则,无法转换为字节码文件
- 运行期异常:在程序运行期间出现了异常。
java中的异常是类,异常的根类是java.lang.Throwable继承于Object,子类有:
- Error:严重的异常,也表示错误
- Exception:一般的异常
- RuntimeException:运行期异常
- 其他Exception
异常处理机制
对于RuntimeException及其子类,是运行期异常,也就是在运行中才可能出现,所以你可以不处理,一旦发生异常JVM会捕获,并终止程序执行。当然你也可以手动捕获异常,编写处理方式,这样它不会终止程序而是继续运行。
对于“其他Exception”,这些异常是编译期异常,你必须处理,否则无法通过编译,对此你有2种处理方式:
- 捕获异常对象并处理
- 声明抛出异常对象
编译通过后,程序出现异常根据处理代码进行处理,并继续运行。
对于“Error”,一定会导致程序终止,只能修改代码。
详细处理流程
程序出现异常后,JVM会创建一个异常对象,该对象中保存了异常的相关信息。
JVM判断该异常代码是否有捕获处理机制,如果该机制捕获了异常,则由它进行处理,否则判断调用它的上一级是否有处理机制,不断向上传递,最终到main方法,如果main中还未处理,则交给JVM处理
JVM对异常的处理非常简单,就是打印错误信息,终止程序。
throw关键字
throw用于“创建异常”,为什么要创建异常??异常本身就是为了辅助程序正常运行的、认为制造的,所以首先要创建异常。也有的称之为“抛出异常”,不过这种说法容易产生歧义。
语法格式:
throw new xxxException("异常产生的原因");
- throw 必须写在方法体内
- throw new 后面的对象应该是 Exception 或它的子类(最大Throwable)
- 如果是 RuntimeException 可以不手动处理,JVM 也会处理
- 对于其他的 Exception 子类,都是编译期异常,必须手动处理
例子:
public static void main(String[] args) {
int[] a = null;
throwException(a);
}
private static void throwException(int[] a) {
if(a==null) {
throw new NullPointerException("数组为 null");
}
}
注释:一旦 throw 了异常,无论它是什么异常,只要你没有立刻捕获它,后续就不能再写代码了,返回值也不能再写了,因为异常了就要走异常处理机制,该代码无法按照正常的流程执行。
throws关键字
和throw很像,但完全不同,throws用于“声明抛出异常”,声明两字很重要,只使用“抛出异常”容易和throw产生歧义。
声明抛出异常,指的是不处理异常,让调用它的人去处理,当然调用它的人也可以继续声明抛出。不过异常对象就只有一个,无论谁抓住了它就不会再往上抛出了。
基本语法:
修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{
throw new AAAException("抛出原因");
method(); // 抛出BBBException
...
}
- throws 必须写在方法声明处
- throws 抛出的应该是 Exception 或其子类(最大Throwable)
- RuntimeException异常可以不抛出,也不处理
- 如果声明抛出的多个 Exception 有父子关系,只需要声明父Exception
- 子类方法覆盖无法声明抛出父类方法不存在的异常
更通俗的说,子类异常比父类异常更详细,范围更小,那么throws抛出异常时,抛出一个大异常,显然就包括了小异常。而对于覆盖的方法,你本应该进一步处理它,所以你若再抛出,就不能抛出更大的异常。
public static void main(String[] args) throws IOException {
check("c:\\a.txt");
}
public static void check(String fileName) throws IOException {
if(fileName.equals("d:\\a.txt")) {
throw new FileNotFoundException();
}
File f = new File(fileName);
f.createNewFile(); //throw IOException
}
try-catch-finally
这是java捕获异常,并处理异常的机制。
try{
//可能出现异常的代码
}catch(异常对象){
//处理异常对象
}finally{
//无论是否出现异常,都要执行的代码块;
}
主要注意的是,try可以随意与catch或finally搭配,这意味着try可以与catch、finally中随意的一个,或2个一起搭配使用。
多个catch模块
try{
//可能产生异常的语句;
}catch(异常对象){
//处理异常对象的语句
}catch(异常对象2){
//处理异常对象2的语句
}...
一但try中出现异常,那么跳出try后续内容,直接到catch,在捕获时,可以捕获多个异常对象,同级异常没有顺序,否则你应该先捕获小异常,再捕获大异常,或者一次抓一个大的就包括了所有。
由于异常对象只有一个,那么也就说明了异常对象只会被一个catch模块所捕获,无论有多少个catch,因为try只会产生一个异常的,导致了只有一个catch模块生效。
finally的存在是必要的,如果try中没有异常,那么catch模块中的内容就不会被执行,那么对于一些释放资源操作,无论是catch处理,还是不处理,它都应该如期释放,以免浪费内存,我们不应该在try中写一次,而catch中也写一次,我们知道try和catch处于两个不同的作用域。所以对于这样的操作,直接放在finally中即可。
public static void main(String[] args) {
try{
int[] a = new int[3];
System.out.println(a[4]); //出现异常的语句
System.out.println("后续代码");
}catch(Exception e) {
System.out.println(e);
}finally {
System.out.println("finally 中的后续代码");
}
}
throwable类
作为异常的根类,它给我们什么可继承方法?
- public String getMessage : 获得异常消息字符串
- public String toString() : 获得异常对象名和异常消息
- public void printStrackTrace() : 打印最详细的错误信息
最简单的getMessage,可以得到异常消息的字符串描述。
如果直接打印异常对象,因为继承于Object,那么会触发toString方法。
而printStrackTrace,则表示“打印异常跟踪的堆栈信息”,会打印出初始错误,以及一连串的调用关系,这是最详细的错误信息。
public static void main(String[] args) {
try {
check("d:\\b.txt");
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
System.out.println(e);
e.printStackTrace();
}
}
public static void check(String fileName) throws FileNotFoundException {
if(!fileName.equals("d:\\a.txt")) {
throw new FileNotFoundException("文件位置不存在");
}
File f = new File(fileName);
try {
f.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
对于输出结果:
文件位置不存在
//getMessage
java.io.FileNotFoundException: 文件位置不存在
//toString
java.io.FileNotFoundException: 文件位置不存在
//printStackTrace
at test.Test.check(Test.java:22)
at test.Test.main(Test.java:13)
自定义异常类
现有的异常不够我们使用,可以手动自定义
1.如何自定义?
创建一个类,类名 XXXException
继承 Exception 或 RuntimeException
创建一个空参构造
创建一个有参构造,String 类的 message ,直接传给父类构造方法处理
2.例子
下面定义了一个 MxException,有一个空参,和一个有参构造
public class MxException extends Exception {
public MxException() {
super();
}
public MxException(String message) {
super(message);
}
}
JDK新特性
jdk7新特性
把可能出异常的变量,定义在 try() 的小括号中,作用域只在 try 中, 不需要finally
try(FileWriter fw = new FileWriter("D:\\a.txt",true);){
//可能会产生异常的代码
for(int i=0;i<10;i++){
fw.write("java教程"+i+"\r\n");
}
}catch(IOException e){
//处理异常
System.out.println(e);
}
jdk9新特性
在 try() 的小括号中,引入外部定义的变量,它的作用域也会限定在 try 中
FileWriter fw = new FileWriter("D:\\a.txt",true); //定义抛出异常
try(fw){
//多个变量用 分号; 隔开
//可能会产生异常的代码
for(int i=0;i<10;i++){
fw.write("java教程"+i+"\r\n");
}
}catch(IOException e){
//处理异常
System.out.println(e);
}
//一旦超出了 try 定义域,流会自动关闭,
//虽然 fw 作用域在外部,但使用会报错
常见的异常
java的异常非常多,所幸你不需要记住它们,这里仅给新手一些异常查阅
异常名称 | 异常描述 |
---|---|
ParseException | 转换异常 |
FileNotFoundException | 文件未找到异常 |
IOException | IO流异常 |
ClassNotFoundException | 类未找到异常 |
NullPointerException | 空指针异常 |
NumberFormatException | 数值转换失败异常 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
StringIndexOutOfBoundsException | 字符串下标越界异常 |
ArithmeticException | 除数为0异常 |
RuntimeException | 运行期异常 |
ClassCastException | 类实例化异常 |
IllegalArgumentException | 参数异常 |
异常自测题
1.throw和throws有什么区别?
2.发生异常后,如何跟踪堆栈异常信息?
3.一个方法声明抛出异常,调用者如果不进行任何处理,程序能否运行?
4.在一个方法中,能否既throw抛出异常,又把它捕获?如果可以,方法调用者是否需要处理该异常?如果不可以,为什么不可以?
5.以下代码能否运行?如果错了,哪行错了?为什么错?
//import java.io.*; public static void main(String[] args){ try(FileWriter fw = new FileWriter("D:\\a.txt",true);){ for(int i=0;i<10;i++){ fw.write("abc"+i+"\r\n"); } }catch(IOException e){ System.out.println(e); }finally{ fw.close(); } }
6.以下checkPassWord方法代码是否错误?如果错了,哪行错了?为什么错?如果正确,运行结果是什么?
public static void main(String[] args){ String pwd = "12345678"; System.out.println("密码"+pwd+"是否符合要求?"+checkPassWord(pwd)); } public static boolean checkPassWord(String pwd){ // 检测pwd字符串,至少包含8个字符,如果不符合则抛出一个java.lang.NumberFormatException if(pwd.length()<8){ throw new NumberFormatException("密码错误"); return false; } else { return true; } }
7.以下代码执行结果是什么?
public static void main(String[] args){
int[] a = new int[10];
try{
System.out.println(a[10]);
System.out.println(a[1]);
}catch (Exception e){
System.out.println(1);
}
try{
System.out.println(a[10]);
}catch (Exception e){
System.out.println(2);
}
System.out.println(a[1]);
}
8.如果在main中声明抛出异常,一旦发生异常,main会将异常抛给谁?程序是否会被终止?这说明了什么?
9.在使用try-catch时,能否捕获编译期异常?能否捕获运行期异常?能否捕获Error?能否捕获Throwable?能否捕获Object?
10.方法覆盖后,能否抛出抛出(throw)更大的异常?能否声明抛出(throws)更大的异常?
11.调用方法时,不捕获异常,能否抛出(throw)更小的异常?能否声明抛出(throws)更小的异常?
12.方法中抛出(throw)异常时,本身也不捕获,是否需要声明抛出(throws)异常?
本篇完,还有疑问?留下评论吧