一、Java异常概述
在Java中,所有的事件都能由类描述,Java中的异常就是由java.lang包下的异常类描述的。
Throwable(可抛出):异常类的最终父类,它有两个子类,Error与Exception。
Throwable中常用方法有:
getCause():返回抛出异常的原因。如果 cause 不存在或未知,则返回 null。
getMeage():返回异常的消息信息。
printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值。
1、Error(错误):表示程序无法处理的错误,一般与程序员的执行操作无关。理论上这些错误是不允许发生的,如果发生,也不应该试图通过程序去处理,所以Error不是try-catch的处理对象,而JVM一般的处理方式是终止发生错误的线程。Error类常见子类有VirtualMachineError与AWTError。
1-1、VirtualMachineError(虚拟机错误):表示虚拟机出现错误。 在Java运行时内存中,除程序计数器外的虚拟机栈、堆、方法区在请求的内存无法被满足时都会抛出OutOfMemoryError; 而如果线程请求的栈深度超出虚拟机允许的深度时,就会抛出StackOverFlowError。
1-2、AWTError(AWT组件出错):这个错误并不是很常用。但是提一下AWT与Swing的区别,AWT是使用操作系统中的图形函数的抽象窗口工具,用CC++编写,为了实现Java“一次编译,处处运行”的理念,AWT使用各个操作系统图形函数的交集,所以功能较差,但运行速度较快,适合嵌入式Java; 而Swing组件是基于AWT编写的图形界面系统,它用纯Java编写,所以必然实现了“一次编译,处处运行”,但相较于AWT运行速度较慢,适合PC使用。
2、Exception(异常):出现原因取决于程序,所以程序也理应通过try-catch处理。 异常分为两类:可查异常(checked)与不可查异常(unchecked)。
2-1、可查异常:编译器要求必须处理,否则不能通过编译,使用try-catch捕获或者throws抛出。常见的可查异常有IOException(IO错误)及其子类EOFExcption(文件已结束异常)、FileNotFound(文件未找到异常)。
2-2、不可查异常(也叫运行时异常):编译期不会检查,所以在程序中可不处理,但如果发生,会在运行时抛出。所以这类异常要尽量避免!常见的不可查异常都是RuntimeException类及其子类。
2-2-1、NullPointerException:空指针异常。调用了不存在的对象或未经实例化或初始化的对象时会抛出,如当试图操作一个空对象(赋值为null)的属性、方法时就会抛出。
(实例化:通俗的理解就是为对象开辟空间,使其可在规定范围内被调用。注意:User u;这只是一个对象声明,并没有进行实例化或初始化。
初始化:就是把实例化后的对象中的基本数据类型字段赋默认值或设定值,为非基本类型赋值null,对于static字段只会初始化一次。)
2-2-2、ArithmeticException:算术条件异常。最常见的就是0作除数时会抛出。
2-2-3、ClassNotFoundException:类未找到异常。在通过反射Class.forName(“类名”)来获取类时,如果未找到则会抛出异常。
2-2-4、ArrayIndexOutOfBoundsException:数组索引越界异常。当试图操作数组的索引值为负数或大于等于数组大小时会抛出。
2-2-5、NegativeArraySizeException:数组长度为负值异常。一般在初始化数组大小为负值时抛出。
2-2-6、ArrayStoreException:数组类型不匹配值异常。例如将一个Object数组中加入一个Integer对象与一个String对象时,类型不匹配就会抛出。
2-2-7、IllegalArgumentException:非法参数异常。会在使用Java类库方法时传入参数值越界时抛出。
二、unchecked异常和checked异常的区别
Java的异常被分为两大类:Checked异常和Unchecked异常(Runtime异常)。
所有RuntimeException类及其子类的实例被称为Unchecked异常;
不是RuntimeException类及其子类的异常实例则被称为Checked异常。
checked异常:
- 表示无效,不是程序中可以预测的。比如无效的用户输入,文件不存在,网络或者数据库链接错误。这些都是外在的原因,都不是程序内部可以控制的。
- 必须在代码中显式地处理。比如try-catch块处理,或者给所在的方法加上throws说明,将异常抛到调用栈的上一层。
- 继承自java.lang.Exception(java.lang.RuntimeException除外)。
unchecked异常:
- 表示错误,程序的逻辑错误。是RuntimeException的子类,比如IllegalArgumentException, NullPointerException和IllegalStateException。
- 不需要在代码中显式地捕获unchecked异常做处理。
- 继承自java.lang.RuntimeException(而java.lang.RuntimeException继承自java.lang.Exception)。
Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显示处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。
对于Checked异常处理方式有如下两种:
(1)当前方法明确知道如何处理该异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch块中修改该异常。
(2)当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
Runtime异常无需显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try...catch块来捕捉Runtime异常。
Java异常之所以会分为这两种,应该是出于如下考虑:checked异常可以帮助开发人员意识到哪一行有可能会出现异常,因为Java的API已经说明了调用哪些方法可能会抛出异常。如果不做处理编译就不能通过,从某种程度上说,这种做法可以避免程序的一些错误。
三、使用throws声明抛出异常
1、throws声明抛出异常的思路是:当前方法不知道应该如何这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道应该如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方法是:打印异常跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
2、throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间以逗号隔开。throws声明抛出的语法格式如下:throws ExceptionClass1 , ExceptionClass2...
下面给出几个关于throw的例子
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
例1
1、代码示例
import java.io.*; public class ThrowsTest { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("a.txt"); } }
2、 运行结果
Exception in thread "main" java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:106)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at ThrowsTest.main(ThrowsTest.java:9)
3 、结果分析
该程序不处理IOException异常,将该异常交给JVM处理,所以程序一旦遇到异常,JVM就会打印该异常的跟踪栈信息,并结束程序。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
例2
1、代码示例
import java.io.*; public class ThrowsTest2 { public static void main(String[] args) throws Exception { // 因为test()方法声明抛出IOException异常,所以调用该方法的代码要么处于try...catch块中, // 要么处于另一个带throws声明抛出的方法中。 test(); } public static void test()throws IOException { // 因为FileInputStream的构造器声明抛出IOException异常, // 所以调用FileInputStream的代码要么处于try...catch块中, // 要么处于另一个带throws声明抛出的方法中。 FileInputStream fis = new FileInputStream("a.txt"); } }
2、 运行结果
Exception in thread "main" java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:106)
at java.io.FileInputStream.<init>(FileInputStream.java:66)
at ThrowsTest2.test(ThrowsTest2.java:19)
at ThrowsTest2.main(ThrowsTest2.java:12)
3 、结果分析
test方法声明抛出了Checked异常,则表明该方法希望它的调用者来处理该异常。main方法要么放在try块中显示捕获该异常,要么带throws声明抛出让JVM处理。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
例3
1 、代码示例
import java.io.*; public class OverrideThrows { public void test()throws IOException { FileInputStream fis = new FileInputStream("a.txt"); } } class Sub extends OverrideThrows { // 子类方法声明抛出了比父类方法更大的异常,所以下面方法出错 public void test()throws Exception{} }
2 、运行结果
编译无法通过。
3 、结果分析
throws声明抛出时有一个限制:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类声明抛出异常多。
上面程序中Sub子类中的test()方法声明抛出Exception,该Exception是父类声明抛出异常IOException类的父类,导致程序无法通过编译
四、使用try-catch-finally捕捉异常
1、捕获语句
try中是可能发生异常的程序段;
catch中依次编写对应的异常处理器方法,当抛出异常后,由运行时系统在栈中从当前位置开始依次回查方法,直到找到合适的异常处理方法,如果未找到,则执行finally或直接结束程序运行。
finally :无论是否捕获或处理异常,finally块里的语句都会被执行。
注意(很重要,面试常问):当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。
在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中抛出了异常且未处理。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)CPU出现异常被关闭。
2、执行顺序
1)当没有异常捕获时,会跳过catch,直接执行finally块。
2)当抛出运行时异常且没有定义相应的异常处理方法,就会由JVM抛出异常。
3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,最后执行finally语句块后的语句。