1. 异常体系
1.1 异常的分类
所有异常(Exception)和错误(Error)的超类是问题类(Throwable,有些人也管它叫做异常)。
问题(Throwable)主要分为两种:
错误(Error)
错误的类名以Error作为后缀,比如:StackOverflowError(栈溢出)。一般情况下认为Error是“硬伤”,由于硬件原因造成的程序的出错。物理上的问题是无法通过软件进行修复。异常(Exception)
异常的类名以Exception作为后缀,一般情况下认为Exception是软件上的逻辑错误或者设计不周,是可以通过程序进行恢复的。管理异常其实指的是管理这部分内容。异常(Exception)内部的分类
- 运行时异常(非检查性异常) - RuntimeException
运行时异常:编译时不报错,无需进行异常处理,但运行时报错。
编写时不会发生错误,但运行时数组下标越界
int[] a = {1,2,3};
a[33] = 12;
常见的运行时异常
- java.lang.ArrayIndexOutOfBoundsException 数组下标越界
- java.lang.IndexOutOfBoundsException 下标越界
- java.lang.NullPointerException 空指针异常
- java.lang.ArithmeticException 除数为0异常
- java.lang.ClassCastException 引用数据类型类型转换异常(下溯造型中)
运行时异常主要通过正确地编写程序逻辑来进行规避。
- 检查性异常 - 非Runtime继承体系下的异常,比如IOException...
检查性异常:要求程序必须给出异常的处理办法(可以上报也可以处理),否则程序编译不通过无法执行。(虽然要求强制处理异常,但异常不一定发生)
常见的的检查性异常
- IOException 输入输出的异常
- DataFormatException 数据格式异常
- ParseException 转换异常(日期类型与字符串类型)
- SQLException SQL语句和数据库操作异常
笔试题1:异常的分类?
主要是错误(Error)和异常(Exception),错误指的是硬件问题,通过代码无法修复。异常指的是逻辑问题,可以通过代码进行修复。
笔试题2:Error和Exception的区别?
错误指的是硬件问题,通过代码无法修复。异常指的是逻辑问题,可以通过代码进行修复。
笔试题3:运行时异常和检查性异常的区别?
运行时异常(RuntimeException)在编译时不报错,可以不进行处理,但运行时可能报错。
检查性异常在编译时报错,要求必须进行异常处理,否则程序不能正常执行,但实际运行时可能不出错。
笔试题4:写出了解的四种运行时异常
- java.lang.IndexOutOfBoundsException 下标越界
- java.lang.NullPointerException 空指针异常
- java.lang.ArithmeticException 除数为0异常
- java.lang.ClassCastException 引用数据类型类型转换异常(下溯造型中)
2. 异常处理
Java中的对于异常态度:管理异常。异常被作为类来看待
2.1 上报(抛出)
对于检查性异常,可以在方法中不进行处理,通过在方法上声明throws表示上报某个异常类型,一个方法可以上报多种异常。
public void test() throws InterruptedException, FileNotFoundException {
Thread.sleep(1000);
InputStream is = new FileInputStream("c:/1.txt");
}
InputStream is = new FileInputStream("c:/1.txt");会上报一个FileNotFoundException
is.read();会上报一个IOException
IOException是FileNotFoundException的父类,所以两种异常合并成一种
public void test2() throws IOException {
InputStream is = new FileInputStream("c:/1.txt");
is.read();
}
也可以通过throw的关键字,人为制造异常。
示例1:add方法完成的是两个正整数相加,如果参数为负数,人为制造异常抛出
public int add(int a, int b) throws Exception {
if(a < 0 || b < 0) {
Exception e = new Exception();
throw e;
}
return a+b;
}
上报异常的目的是让方法的调用者了解方法可能出现的问题,进而有调用者决定是否处理异常。
如果调用者继续上报,直至最终处理为止。如果整个调用环节所有的方法对异常都上报而不处理,最终异常会被抛给Java虚拟机,当异常发生时无人进行程序恢复操作,程序会在控制台报错。
正常编写代码时,应该有一段程序完成异常的处理。
笔试题/面试题:throw和throws的区别
throw是人为制造异常的关键字
throws是声明方法抛出异常类型的关键字
2.2 处理
处理异常的目的是当异常发生时,程序给予一些处理异常的代码,恢复程序的正常执行。
处理异常的方式是try...catch...两个代码块
语法:
try{
可能发生异常的代码
}catch(异常类型 对象){
当发生异常时的处理代码
}
当代码行B发生异常时,程序执行ABDE
当try中的代码没有发生异常时,程序执行ABC,不会执行catch中的代码
try{
A
B
C
}catch(异常类型 对象){
D
E
}
一个try块可以与多个catch块进行结合
public class Test2 {
public static void main(String[] args) {
try {
test();
} catch (FileNotFoundException e) {
System.out.println("所选文件没有找到");
} catch (InterruptedException e) {
System.out.println("线程运行失败");
}
}
public static void test() throws InterruptedException, FileNotFoundException {
Thread.sleep(1000);
InputStream is = new FileInputStream("c:/1.txt");
}
}
但需要注意的是:如果多个异常存在着继承关系,catch的顺序是"由小到大"排列的
public class Test2 {
public static void main(String[] args) {
try {
test2();
} catch (FileNotFoundException e) {
System.out.println("文件没有找到");
} catch (IOException e) {
System.out.println("文件读写失败");
} catch (Exception e) {
System.out.println("发生了严重的问题");
}
}
public static void test2() throws FileNotFoundException, IOException, Exception {
InputStream is = new FileInputStream("c:/1.txt");
is.read();
throw new Exception();
}
}
catch的顺序不能调整,因为三者存在继承关系:
FileNotFoundException -> IOException -> Exception
越特殊的异常应该放在上面,否则捕获不到特殊的异常,程序会报错。
多个异常也可以通过同一个catch进行处理
public class Test2 {
public static void main(String[] args) {
try {
test();
} catch (FileNotFoundException | InterruptedException e) {
System.out.println("程序发生了问题");
}
}
public static void test() throws InterruptedException, FileNotFoundException {
Thread.sleep(1000);
InputStream is = new FileInputStream("c:/1.txt");
}
}
2.3 finally块
finally块表示异常处理中的最终块,其体现的意义是:无论程序是否发生异常,是否遇到了return语句,这部分代码都必须执行。除非在try中或者catch中执行了关闭Java虚拟机的代码(System.exit(0)或System.exit(-1))
语法:
try{
}
可能有n个catch... (n >= 0)
finally{
}
try...catch...finally的组合
- try...catch...
- try...catch...finally...
- try...finally...
当代码行B发生异常时,代码执行顺序:ABDEF
当try中的代码没有发生异常时,代码执行顺序:ABCF
try{
A
B
C
}catch(Exception e){
D
E
}finally{
F
}
在finally中完成资源回收,关闭数据库连接等收尾工作。
如果不完成类似的操作,比如在try中打开了数据库连接,没有在finally中进行关闭连接操作,一旦try中发生异常,连接不能关闭就被浪费。随着这段程序被多次运行,每次都会打开新连接,直至数据库连接耗尽。
public class Test1 {
public static void main(String[] args) {
int x = haha();
System.out.println(x);
}
public static int haha() {
try {
System.out.println(5/0);
System.out.println("执行了try");
return 1;
}catch (Exception e) {
System.out.println("执行了catch");
return 2;
}finally {
System.out.println("执行了finally");
}
}
}
代码的执行结果
System.out.println(5/0);会发生异常,程序转到catch块中执行,在catch块return前,虚拟机会将finally中的代码放在return前执行,所以得到了上面的结果。
如果在上述代码的finally中添加“return 3”,程序的运行结果是3,finally中的代码先完成return,其余块中return语句相当于无效了
3. 发生异常时的堆栈信息
默认在生成的catch块中有如下一句代码
e.printStackTrace();
这句代码的作用是打印方法栈的当前信息
示例2:打印方法栈中的出错信息
public class Test2 {
public static void main(String[] args) {
a();
}
public static void a() {
b();
}
public static void b() {
c();
}
public static void c() {
try {
InputStream is = new FileInputStream("c:/abcsss.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果
找到方法栈信息中第一行你自己编写代码,问题一般就在这个位置。
这个示例中,出错的就是Test2类的c方法,问题是在Test2.java文件的24行
定位到位置后发现其实"c:/abcsss.txt"文件不存在
4. 自定义异常
编写一个类,继承java.lang.Exception或者它的子类。
示例3:制作一个参数为负数时的异常
异常类
public class NegativeNumberException extends Exception {
}
测试类
public class Test {
public static int add(int a, int b) throws NegativeNumberException {
if(a < 0 || b < 0) {
throw new NegativeNumberException();
}
return a+b;
}
public static void main(String[] args) {
try {
int x = add(-3, 5);
System.out.println(x);
} catch (NegativeNumberException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}