一 什么是异常?
- 异常模拟的是现实世界中“不正常”的事件。Java中采用类的方式去模拟现实世界的异常事件
- 类是可以创建对象的,在Java中因为一类异常是用一个异常类表示的,所以异常类也可以创建对象。比如:NullPointerException npe=0x1234;其中e是一个引用类型的变量,保存的是NullPointerException异常对象的内存地址。
- 异常处理机制的作用:Java为我们提供了套完善的异常处理机制,作用是【程序发生异常事件后,为我们输出详细的关于该异常的信息,程序员可以通过这些信息,对程序做一些处理,增强程序的健壮性。】
- 子类中处理的异常范围不能大于父类
二 异常的分类和层次结构
1. 异常的分类
- 异常可以分为编译时异常和运行时异常
- 常见的运行时异常:ClassCastException、ArithmeticException、EmptyStackException、IndexOutOfBoundsException、NullPointerException
- 常见的编译时异常:IOException、InterruptedException
2. 编译时异常和运行时异常怎么区分?
- 如果一个异常是RuntimeException或者是RuntimeException的子类,那么这个异常是运行时异常。
- 如果一个异常是Exception的直接子类,且不是RuntimeException,那么这个异常是编译时异常。
3. Sun是怎么定义一个异常是编译时异常还是运行时异常的?
- 编译时就会出现的异常是编译时异常,运行时才会出现的异常是运行时异常。
- 编译时异常是在程序中出现的概率较大的异常,而运行时异常是程序中出现的概率较小的异常。
- 编译时异常程序员必须处理(未雨绸缪),程序员不处理的话,程序无法通过编译,更不可能运行;在编写程序时,程序员不需要处理运行时异常。
- Sun是怎么强制让开发人员处理编译时异常的呢?比如说,FileInputStream fis=new FileInputStream("文件路径");是我们很常见的一段代码,这段代码有可能会出现FileNotFoundException,并且Sun把FileNotFoundException定义成了编译时异常,如果我们看FileInputStrea的构造函数就会发现,它的构造函数抛出了FileNotFoundException,也就是说只要我们调用FileInputStream的构造函数,这个构造函数就会抛出一个FileNotFoundException,既然抛出了异常,我们就必须处理(向上抛或者try...catch),如果不处理,就无法通过编译,这样就保证了让程序员去处理编译时异常。
三 案例说明Java中的异常处理机制
public class ExceptionDemo {
public static void main(String[] args) {
int num1=3;
int num2=0;
/*
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.donglijiedian.ExceptionDemo.ExceptionDemo.main(ExceptionDemo.java:7)
*/
int res=num1/num2;
System.out.println(res);
}
}
以上程序编译通过了,但是运行时出现了java.lang.ArithmeticException异常,JVM创建了一个ArithmeticException类型的对象,这个对象中包含了关于这个异常的详细信息,并且JVM将这个对象中的信息输出到控制台
四 异常处理方式之throws
1. throws关键字
- 异常处理的第一种方式是:声明抛出!在方法声明的位置上使用throws关键字,将异常向上抛出。
2. 案例说明怎么使用throws
下面这段代码是无法编译通过的,因为当我们创建FileInputStream对象时,FileInputStream的构造方法抛出了一个FileNotFoundException异常,我们的程序中并没有对该异常做任何处理,因此无法通过编译。
public class ExceptionDemo {
public static void main(String[] args) { m1(); }
public static void m1() { m2(); }
public static void m2() { m3();}
public static void m3() {
FileInputStream fis=new FileInputStream("文件路径");
}
}
上述问题的一个解决方案就是将异常向上抛出。比如在ExceptionDemo2中,我们将异常一层层向上抛出,直到最后抛给了JVM。这种解决方案是能通过编译了,但是是一种"不负责任"的表现,并没有真正地处理异常,违反了Sun设计异常处理机制的初衷。在这个案例中,最初是FileInputStream的构造函数抛出了一个FileNotFoundException异常,m3()去调用FileInputStream的构造函数,但是m3()没有给出解决方案,抛给了m2();m2()抛给了m1();m1()抛给了main();main()也没有给出解决方案,抛给了JVM。通过编译后,如果在运行过程中,真的出现了FileNotFoundException,那么JVM会创建一个FileNotFoundException对象,并将该对象保存的信息输出到控制台,该程序退出JVM。
public class ExceptionDemo2 {
public static void main(String[] args) throws FileNotFoundException { m1(); }
public static void m1() throws FileNotFoundException { m2(); }
public static void m2() throws FileNotFoundException{ m3();}
public static void m3() throws FileNotFoundException{
FileInputStream fis=new FileInputStream("文件路径");
}
}
如果想对异常给出解决方案,那么可以使用try...catch的方式
五 异常处理方式之try...catch
1. 捕捉异常
- 处理异常的第二种方式是使用try...catch捕捉异常并处理。
- catch语句块可以写多个,但是捕捉异常时,必须从小类型到大类型进行捕捉。
- try...catch中最多执行一个catch分支
语法如下:
try {
//可能出现异常的代码块
} catch (异常类型1 e) {
//处理异常的代码
}catch (异常类型2 e) {
// 处理异常的代码
}....
2. 异常类型的对象中常用的方法
- getMassage():输出关于异常的一些信息,但是信息较少
- printStackTrace:输出异常的一些信息,信息较多,便于程序员处理异常
3. 案例说明try...catch处理异常
【方式一】可以处理FileNotFoundException异常,即使在运行的过程中出现了异常,那么【System.out.println("异常处理机制");】也能被执行。
//方式一
public class ExceptionDemo {
public static void main(String[] args){
try {
FileInputStream fis=new FileInputStream("C:/abc.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("异常处理机制");
}
}
【方式二】也可以处理FileInputStream抛出的FileNotFoundException异常。FileNotFoundException继承自IOException,运用了多态机制
//方式二
public class ExceptionDemo {
public static void main(String[] args){
try {
FileInputStream fis=new FileInputStream("C:/abc.txt");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("异常处理机制");
}
}
【方式三】编译不能通过,因为catch语句块虽然可以写多个,但是必须从上到下,从小到大,而这段代码中IOException比FileNotFoundException的处理范围要大
//方式三
public class ExceptionDemo {
public static void main(String[] args){
try {
FileInputStream fis=new FileInputStream("C:/abc.txt");
} catch (IOException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("异常处理机制");
}
}
4. finally语句块
- finally语句块可以直接跟在try后面,也可以跟在catch后面
- 除非编译不通过,或者在try中执行 System.exit(...);finally都会执行
public class ExceptionDemo {
public static void main(String[] args){
try {
int res=2/0;//该语句会抛出ArithmeticException异常
System.out.println("try中输出");//不会执行
} catch (ArithmeticException e) {
//捕捉异常并处理
e.printStackTrace();
}finally {
System.out.println("finally中输出");//会执行
}
System.out.println("异常处理机制");//会执行
}
}
下面这段代码说明如果在try中执行System.exit(0),那么在finally中语句不会再执行
public class ExceptionDemo {
public static void main(String[] args){
try {
System.out.println("try中语句");//执行
System.exit(0);
} finally {
System.out.println("finally中语句块");//不执行
}
}
}
下面这段代码我不是很理解
public class ExceptionDemo {
public static void main(String[] args){
int res=m();
System.out.println("res="+res);//输出10
}
public static int m() {
int i=10;
try {
return i;
} finally {
i++;
System.out.println(i);//输出11
}
}
}
六 自定义异常和手动抛出异常
自定义编译时异常继承Exception,自定义运行时异常继承RunTimeException
下面这个案例是判断用户输入的用户名是否符合规范(长度大于6),如果不符合规范就抛出一个自定义的IlleagleNameException.
class IlleagleNameException extends Exception{
public IlleagleNameException() {}
public IlleagleNameException(String message) {
super(message);
}
}
class CustomerService {
public void register(String name) throws IlleagleNameException {
if (name.length()<6) {
//手动抛出异常
throw new IlleagleNameException("用户名长度不能少于6位");
}
}
}
class Test {
public static void main(String[] args) {
String name="jack";
CustomerService cs=new CustomerService();
try {
cs.register(name);
} catch (IlleagleNameException e) {
System.out.println(e.getMessage());
}
//控制台会输出“用户名不能少于6位”
}
}