一 基本概念
1.什么是异常,java提供异常处理机制有什么用
以下程序执行过程中出现了不正常情况,而这种不正常情况叫做:异常
java把异常信息打印输出到控制台,供程序员参考,程序员可以根据异常信息对程序进行修改,让程序更加健壮。
public class ExceptionTest01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println(a / b);
//实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
//并且JVM将new的异常对象抛出,打印输出到控制台。
}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.text02.ExceptionTest01.main(ExceptionTest01.java:9)
这个信息我们称为异常信息,由JVM打印的。
2.java语言中异常以什么形式存在
1.异常在java中以类的形式存在,每一个异常类都可以创建异常对象。
public class ExceptionTest02 {
public static void main(String[] args) {
//通过异常类 实例化 异常对象
NullPointerException n1 = new NullPointerException("空指针异常");
System.out.println(n1);
NumberFormatException n2 = new NumberFormatException("数字格式化异常");
System.out.println(n2);
}
}
运行结果:
java.lang.NullPointerException: 空指针异常
java.lang.NumberFormatException: 数字格式化异常
2.异常对应的现实生活(帮助理解)
火灾(异常类):
2008年8月7日,小明家着火了(异常对象)
2008年8月8日,小刚家着火了(异常对象)
2008年8月9日,小红家着火了(异常对象)
类:模板
对象:实际存在的个体
二 异常处理机制
1.异常在java中以类和对象的形式存在。那么异常的继承结果是怎样的呢?
Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须对这些异常进行预处理)
RuntimeException:运行时异常。(在编写程序阶段可以预先处理,也可以不管)
(图片来自网络)
2.编译时异常(受检异常)和运行时异常
- 都是发生在运行阶段,编译阶段异常是不会发生的,因为异常的发生就是new对象,只有程序运行阶段才可以创建对象。
- 编译异常一般发生的概率比较高(对于发生概率较高的异常,需在运行前对其进行预处理)
举个例子:
你看到外面下雨了,倾盆大雨。
你出门前会预料到:如果不打伞,我可能生病(异常)。
而且这个异常发生的概率很高,所以我会在出门前拿一把伞。
“拿一把伞”就是对“生病异常”发生之前的一种处理。 - 运行时异常一般发生的概率比较低
举个例子:
小明走在大街上,可能被天上飞机的轮子砸到。
被天上飞机的轮子砸到是一种异常。
但这个异常发生的概率极低。
出门前没必要对这种发生概率极低的异常进行预处理。
如果处理这种异常,你将活得很累。
3. java语言中对异常的处理包括两种方式:
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
第二种方式:使用try···catch语句进行异常的捕捉。
举个例子:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元。‘
损失1000元可以看作是一个异常发生了。我有两种处理方式:
第一种:我把这件事告诉我的领导【异常上抛】
第二种:我自己掏腰包把这个钱补上。【异常的捕捉】
注意:异常发生之后,如果选择上抛,抛给了我的调用者,调用者需要对这个异常继续处理,同样有两种方式。如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,终止java程序的执行。
package exception.text02;
import java.util.Arrays;
public class ExceptionTest03 {
public static void main(String[] args) {
/*
程序执行到此处发生了ArithmeticException异常,
底层new了一个ArithmeticException异常对象,
然后抛出了,由于是main方法调用了100 / 0,
所以这个异常ArithmeticException抛给了main方法,
main方法没有处理,将这个异常抛给了JVM。
JVM终止程序的执行。
*/
System.out.println(100 / 0);
System.out.println("Hello World!");//没有执行
}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.text02.ExceptionTest03.main(ExceptionTest03.java:15)
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
public class ExceptionTest04 {
public static void main(String[] args) {
/*
main方法中调用了doSome()方法,
而doSome()方法声明位置上有throws ClassNotFoundException
我们调用doSome()方法的时候必须对这种异常进行预先的处理
如果不处理,编译器报错
*/
doSome();
}
/**
* doSome()方法声明位置上有throws ClassNotFoundException
* 这个代码表示doSome()方法在执行过程中,有可能出现ClassNotFoundException异常
* 叫做类没找到异常。这个异常的直接父类是:Exception,所以ClassNotFoundException属于编译时异常
* @throws ClassNotFoundException
*/
private static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}
"运行"结果:
java: 未报告的异常错误java.lang.ClassNotFoundException; 必须对其进行捕获或声明以便抛出
public class ExceptionTest05 {
//第一种处理方式:在方法声明的位置上使用:throws,来完成异常的继续上抛,抛给调用者。
//上抛相当于推卸责任。(继续把异常传递给调用者)
/*
public static void main(String[] args) throws ClassNotFoundException {
doSome();
}
*/
//第二种处理方式:try ··· catch进行捕捉。
//捕捉相当于把异常拦下来了,异常得到真正的解决(调用者是不知道的)
public static void main(String[] args) {
try {
doSome();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}
运行结果:
doSome!!!
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest06 {
/*
一般不建议在main方法上使用throws,因为这个异常如果真的发生了,一定会抛给JVM,JVM只有终止。
main方法中的异常建议使用try···catch进行捕捉,不要继续上抛。
*/
public static void main(String[] args) {
System.out.println("main begin");
//try尝试
try {
m1();
//以上代码出现问题,直接进入catch语块中执行。
} catch (FileNotFoundException e) {
//这个分支可以使用e引用,e引用保存的内存地址是那个new出来异常对象的地址
System.out.println("文件不存在");
}
System.out.println("main over");
}
//throws后面可以写多个异常,用逗号隔开
private static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
private static void m2() throws FileNotFoundException {
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
private static void m3() throws FileNotFoundException {
/*
这里我们调用了一个构造方法:FileInputStream(String name)
这个构造方法的声明位置上有:throws FileNotFoundException
通过类的继承结构可以看到:FileNotFoundException的父类是IOException,IOException的父类是Exception
*/
new FileInputStream("D:\\123.txt");
}
}
运行结果:
未出现异常:
main begin
m1 begin
m2 begin
m2 over
m1 over
main over
出现异常:
main begin
m1 begin
m2 begin
文件不存在
main over
注意:只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
另外try语句中的某一行出现异常,该行后面的代码不会执行。
try···catch捕捉异常后,后续代码可以执行。
第二种方式:使用try···catch语句进行异常的捕捉。
- catch后面的小括号中可以是具体的异常类型,也可以是异常型的父类型。
- catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
- catch写多个的时候,从上到下,必须遵守从小到大。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest07 {
public static void main(String[] args) {
try{
FileInputStream fileInputStream = new FileInputStream("D:\\123.txt");
} catch (FileNotFoundException e) {
System.out.println("文件不存在!");
}
System.out.println("Hello World!");
try{
FileInputStream fileInputStream = new FileInputStream("D:\\123.txt");
} catch (IOException e) {
System.out.println("文件不存在!");
}
}
}
运行结果:
未出现异常:
Hello World!
出现异常:
文件不存在!
Hello World!
文件不存在!
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest07 {
public static void main(String[] args) {
try{
FileInputStream fis = new FileInputStream("D:\\123.txt");
fis.read();//仅举例
} catch (FileNotFoundException e){
System.out.println("文件不存在");
} catch (IOException e){
System.out.println("读文件报错了");
}
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest07 {
public static void main(String[] args) {
try{
// 创建输入流
FileInputStream fis = new FileInputStream("D:\\123.txt");
System.out.println(100 / 10);//运行时异常
} catch (FileNotFoundException | ArithmeticException |NullPointerException e){
//JDK8
System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
}
}
}
运行结果:
未出现异常:
10
出现异常:
文件不存在?数学异常?空指针异常?都有可能!
注:在以后的开发中,处理编译时异常,应该上报还是捕捉,怎么选?
如果希望调用者来处理,选择throws上报。
其它情况使用捕捉的方式。
4.异常对象的两个重要方法
- 获取异常简单的描述信息
String msg = exception.getMessage(); - 打印异常追踪的堆栈信息
exception.printStackTrace();
public class ExceptionTest08 {
public static void main(String[] args) {
//这里只是为了测试getMessage()方法和printStackTrace()方法。
//这里只是new了异常对象,但没有将对象抛出,JVM会认为这是一个普通的java对象。
NullPointerException e = new NullPointerException("空指针异常");
//获取异常简单描述信息:这个信息实际上就是构造方法上面的String参数。
String msg = e.getMessage();
System.out.println(msg);
//打印异常堆栈信息
//java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印
e.printStackTrace();
}
}
运行结果:
空指针异常
java.lang.NullPointerException: 空指针异常
at exception.text02.ExceptionTest08.main(ExceptionTest08.java:7)
我们查看异常的追踪信息,应怎么看,可以快速调试程序:
异常的追踪信息,从上往下一行一行看。
但需要注意的是:SUN写的代码就不用看了(看包名就知道是自己的还是SUN的)
主要的问题是出现在自己编写的代码上。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest09 {
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
e.printStackTrace();//实际开发建议使用这个
String msg = e.getMessage();//获取异常简单的描述信息
System.out.println(msg);
}
}
private static void m1() throws FileNotFoundException {
m2();
}
private static void m2() throws FileNotFoundException {
m3();
}
private static void m3() throws FileNotFoundException {
new FileInputStream("D:\\23.txt");//该文件不存在
}
}
运行结果:
java.io.FileNotFoundException: D:\23.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:108)
at exception.text02.ExceptionTest09.m3(ExceptionTest09.java:26)
at exception.text02.ExceptionTest09.m2(ExceptionTest09.java:22)
at exception.text02.ExceptionTest09.m1(ExceptionTest09.java:18)
at exception.text02.ExceptionTest09.main(ExceptionTest09.java:9)
D:\23.txt (系统找不到指定的文件。)
5. finally
关于try···catch中的finally子句:
- 在finally中的代码是最后执行的,并且一定执行,即使try语块中的代码出现了异常。
- finally语句通常使用在哪些情况呢?
通常在finally语块中完成资源的释放/关闭,
因为finally中的代码比较有保障。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest10 {
public static void main(String[] args) {
FileInputStream fis = null;//声明放到try外面,这样在finally中才能使用。
try{
//流使用完需要关闭
fis = new FileInputStream("D:\\23.txt");//该文件不存在
String s = null;//这里一定会出现空指针异常!
s.toString();
System.out.println("Hello World!");
//fis.close();//放在这里可能关闭不了
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} finally {
//流的关闭放在这里比较保险,
//finally中的代码一定会执行
//即使try中出现了异常
if(fis != null){//避免空指针异常
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("Hello Over");
}
}
}
运行结果:
java.io.FileNotFoundException: D:\23.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:108)
at exception.text02.ExceptionTest10.main(ExceptionTest10.java:11)
Hello Over
- try和finally,没有catch可以吗?可以。
try不能单独使用,try finally可以联合使用。
以下代码的执行顺序:
先执行try···
再执行finally···
最后执行 return(return语句只要执行方法必然结束)
public class ExceptionTest11 {
public static void main(String[] args) {
try{
System.out.println("try ~");
return;
} finally{
System.out.println("finally ~");
}
}
}
运行结果:
try ~
finally ~
若在try中退出JVM,finally语句中的代码就不会执行
public class ExceptionTest12 {
public static void main(String[] args) {
try{
System.out.println("try ~");
System.exit(0);
}finally {
System.out.println("finally ~");
}
}
}
运行结果:
try ~
java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!)
java中有这样两条规则:
- 方法体中的代码必须遵循自上而下顺序依次逐行执行
- return语句一旦执行,整个方法必须结束。
public class ExceptionTest13 {
public static void main(String[] args) {
int result = m();
System.out.println("result = " + result);
}
private static int m() {
int i = 100;
try{
//这行代码在int i = 100;的下面,所以最终返回的结果必须是100
return i;
}finally {
i++;
System.out.println("i = " + i);
}
}
}
/*
反编译之后的结果(省略了一部分)
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
*/
运行结果:
i = 101
result = 100
final finally finalize的区别:
- final 关键字
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值 - finally 关键字
和try一起联合使用。
finally语句块中的代码是必须执行的。 - finalize 标识符
是一个Object类的方法名。
这个方法由垃圾回收器GC负责调用。
6.自定义异常
- SUN提供的JDK内置的异常是不够用的。在实际开发中,有很多业务,
这些业务出现异常之后,JDK中是没有的。这些异常需由程序员自定义。 - java中怎么自定义异常呢?
两步:
第一步:编写一个类继承Exception或者RuntimeException。
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
package exception.text02;
public class MyException extends Exception{
public MyException() {
}
public MyException(String message) {
super(message);
}
}
package exception.text02;
public class MyExceptionTest15 {
public static void main(String[] args) {
//创建异常对象(只new了异常对象,并没有手动抛出)
MyException e = new MyException("用户名不能空!");
e.printStackTrace();
String msg = e.getMessage();
System.out.println(msg);
}
}
运行结果:
exception.text02.MyException: 用户名不能空!
at exception.text02.MyExceptionTest15.main(MyExceptionTest15.java:6)
用户名不能空!
7.方法重写中的异常
重写之后的方法不能比之前的方法抛出更多(更广泛)的异常,可以更少。
8.使用throw抛出异常
throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
package exception.text01;
class MyException extends Exception {
public MyException() {}
public MyException(String msg){super(msg);}
}
public class FullConstructors {
public static void f() throws MyException{
System.out.println("Throw MyException from f()");
throw new MyException();
}
public static void g() throws MyException{
System.out.println("Throw MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
}catch (MyException e){
e.printStackTrace(System.out);
}
try {
g();
}catch (MyException e){
e.printStackTrace(System.out);
}
}
}
运行结果:
Throw MyException from f()
exception.text01.MyException
at exception.text01.FullConstructors.f(FullConstructors.java:10)
at exception.text01.FullConstructors.main(FullConstructors.java:19)
Throw MyException from g()
exception.text01.MyException: Originated in g()
at exception.text01.FullConstructors.g(FullConstructors.java:14)
at exception.text01.FullConstructors.main(FullConstructors.java:24)