第二十一章 异常
1 异常
1.1 异常的概述
package com.bjpowernode.javase.exception;
/*
1、什么是异常,异常机制有什么用?
以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常
java语言是很完美的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常的情况,
java把异常信息打印输出到控制台,供程序员参考,程序员看到异常信息之后,可以对程序进制修改,让程序更加的健壮。
什么是异常:程序执行过程中的不正常情况。
异常的作用:增强程序的健壮性
2、以下程序执行控制台出现了:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.bjpowernode.javase.exception.ExceptionTest01.main(ExceptionTest01.java:17)
这个信息被我们称为:异常信息,这个信息是JVM打印的
*/
public class ExceptionTest01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
// 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
// 并且JVM将new的异常对象抛出,打印输出信息到控制台了。
int c = a / b;
System.out.println(a + "/" + b + "=" + c); // java.lang.ArithmeticException: / by zero
// 我观察到异常信息之后,对程序进制修改,更加健壮
/*
int a = 10;
int b = 0;
if(b == 0){
System.out.println("除数不能为0");
return;
}
// 程序执行到这里表示除数一定不是0
int c = a / b;
System.out.println(a + "/" + b + "=" + c); // java.lang.ArithmeticException: / by zero
*/
}
}
1.2 异常以类的形式出现
package com.bjpowernode.javase.exception;
/*
java语言中异常是以什么形式存在的呢?
1、异常在java中以类的形式存在,每一个异常类都可以创建异常对象。
2、异常对应的现实生活中是怎样的?
火灾(异常类):
2008年8月8日,小明家着火了(异常对象)
2008年10月8日,小刚家着火了(异常对象)
2008年11月8日,小美家着火了(异常对象)
钱包丢了(异常类):
2008年1月9日,小明的钱包丢了(异常对象)
2008年2月9日,小华的钱包丢了(异常对象)
...
*/
public class ExceptionTest02 {
public static void main(String[] args) {
// 通过“异常类”实例化“异常对象”
NumberFormatException nfe = new NumberFormatException("数字格式化异常");
System.out.println(nfe);
// 通过“异常类”实例化“异常对象”
NullPointerException npe = new NullPointerException("空指针异常发生了");
System.out.println(npe);
}
}
1.1 异常在java中以类和对象的形式存在,那么异常的继承结构是怎样的?
1 我们可以使用UML图来描述一下继承结构。
画UML图有很多工具,例如:Rational Rose(收费的)、startUML等...
Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段预先对这些异常进制处理,如果不处理编译器报错)
RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管都行)
package com.bjpowernode.javase.exception;
public class ExceptionTest03 {
public static void main(String[] args) {
/*
程序执行到此处发生了ArithmeticException异常。
底层new了一个ArithmeticException异常对象,
然后抛出了,由于是main方法调用了100 / 0,
所以这个异常ArithmeticException抛给了main方法,
main方法没有处理,将这个异常自动抛给了
ArithmeticException继承RuntimeException,属于运行时异常。
在编写程序阶段不需要对异常进行预先的处理。
*/
System.out.println(100 / 0);
// 这里的hello world没有输出,没有执行。
System.out.println("hello world!");
}
}
2.2 什么是UML?有什么用?
UML是一种统一建模语言。
一种图标式语言(画图的)
UML不是只有java中使用,只要是面向对象的编程语言,都有UML。
一般画UML图的都是软件架构师或者说是系统分析师。这些级别的人员使用的。
软件设计人员使用UML。
在UML图中可以描述类和类之间的关系,程序执行的流程图,对象的状态等
在java软件开发当中,软件分析师/设计师负责设计类,java软件开发人员必须要能看懂。
2.3 编译时异常和运行时异常
1 编译时异常和运行时异常,都是发生在运行阶段,编译阶段异常是不会发生的。编译时异常因为什么而得名?
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序在运行阶段才可以new对象。
因为异常的发生就是new异常对象。
2 编译时异常和运行时异常的区别?
编译时异常一般发生的概率比较高。
举个例子:
你看到外面下雨了,倾盆大雨的。
你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。
而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。
"拿一把伞"就是对"生病异常"发生之前的一种处理方式。
对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
运行时异常一般发生的概论比较低。
举个例子:
小明走在大街上,可能会天上的飞机轮子砸到。
被飞机轮子砸到也算一种异常。
但是这种异常发生的概率较低。
在出来之前你没必要提前对这种发生概率较低的异常进制预处理。
如果你预处理这种异常,你将活的很累。
假设java中没有对异常进制划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进制预处理,将是怎样的效果呢?
首先,如果这样的话,程序肯定是绝对的安全的。
但是程序员编写程序太累,代码到处都是处理异常的代码。
3 编译时异常还有其他名字:
受检异常:CheckedException
受控异常
4 运行时异常还有其它名字:
未受检异常:UnCheckedExcetion
非受控异常
5 再次强调:所有异常都是发生在运行阶段的。
6 Java语言中对异常的处理包括两种方式:
第一种方式:在方法声明的位置上,使用throws关键字。抛给上一极。
谁调用我,我就抛给谁,抛给上一级。
第二种方式:使用try...catch语句进制异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。
举个例子:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,“损失1000元”这可以看做一个异常发生了,我有两种处理方式
第一种方式:我把这件事告诉我的领导(异常上抛)
第二种方式:我自己掏腰包把这个钱补上(异常的捕捉)
7 注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果终止程序的执行。
package com.bjpowernode.javase.exception;
/*
以下代码报错的原因是什么?
因为doSome()方法声明位置上使用了:throws ClassNotFoundException
而ClassNotFoundException是编译时异常。必须编写代码时处理,没有处理编译器报错。
*/
public class ExceptionTest04 {
public static void main(String[] args) {
// main方法中调用doSome()方法
// 因为doSome()方法声明位置上有:throws ClassNotFoundException
// 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
// 如果不处理,编译器就报错
// 编译器报错信息:Unhandled exception: java.lang.ClassNotFoundException
// doSome();
}
/**
* doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
* 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
* 叫做类没有找到异常,这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。
* @throws ClassNotFoundException
*/
public void doSome() throws ClassNotFoundException{
}
}
2.4 异常的两种处理方式
package com.bjpowernode.javase.exception;
/*
以下代码报错的原因是什么?
因为doSome()方法声明位置上使用了:throws ClassNotFoundException
而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();
}
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome");
}
}
2.5 异常捕捉和上报的联合使用
package com.bjpowernode.javase.exception;
/*
处理异常的第一种方式:
在方法声明的位置上使用throws关键字抛出,谁调用我这个方法我就抛给谁,抛给调用者来处理。
这种处理异常的态度:上报。
处理异常的第二种方式:
使用try...catch语句对异常进行捕捉
这个异常不会上报,自己把这个事处理了
异常抛到这里就不会继续上抛了
注意:
只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
另外需要注意,try语句块中某一行出现异常,该行后面的代码不会执行。
try...catch捕捉之后后续代码可以执行。
*/
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest06 {
// 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM,JVM只有终止。
// 异常处理机制的作用就是增强程序的健壮性,怎么能做到,异常发生了也不影响程序的执行。所以
// 一般main方法中的异常建议使用try...catch进行捕捉,main就不要继续上抛了。
/*
public static void main(String[] args) throws FileNotFoundException {
System.out.println("main begin");
m1();
System.out.println("main over");
}
*/
public static void main(String[] args) {
// 100 / 0 这是算术异常,这个异常是运行时异常,你在编译阶段可以处理,也可以不处理。编译器不管。
// System.out.println(100 / 0);
System.out.println("main begin");
try {
// try尝试
m1();
// 以上代码出现异常,直接进入catch语句块中执行。
System.out.println("hello World");
} catch(FileNotFoundException e) {
// 这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常的内存地址。
// 捕捉异常之后走的分支
System.out.println("文件不存在,可能路径错误,也可能该文件被删除了");
System.out.println(e); // java.io.FileNotFoundException: C:\Users\xiaoh寺ong\Desktop\学习方法.txt (系统找不到指定的路径。)
}
// try...catch把异常抓住之后,这里的代码会继续执行。
System.out.println("main over");
}
private static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
// 抛别的异常不行,抛ClassCastException说明你还是没有对FileNotFoundException进行处理
// private static void m2() throws ClassCastException{
// 抛FileNotFoundException的父对象IOException,这样是可以的。因为IOException包括FileNotFoundException
// private static void m2() throws IOException {
// 这样也可以,因为Exception包括所有的异常。
// private static void m2() throws Exception{
// throws后面也可以写多个异常,用逗号隔开。
// private static void m2() throws ClassCastException, FileNotFoundException{
private static void m2() throws FileNotFoundException{
System.out.println("m2 begin");
// 编译器报错原因是:m3()方法声明位置上有:throws FileNotFoundException
// 我们在这里调用m3()没有对异常进行预处理,所以编译报错。
m3();
System.out.println("m2 over");
}
private static void m3() throws FileNotFoundException {
// 调用SUN JDK中某个类的构造方法
// 这个类没有接触过,后期IO流的时候就知道了
// 创建一个输入流对象,该流指向一个文件
/*
编译报错的原因是什么?
第一:这里调用了一个构造方法:FileInputStream(String name)
第二:这个构造方法的声明位置上有:throws FileNotFoundException
第三:通过类的继续结构看到:FileNotFoundException父类是IOException,IOException的父类是Exception,最终得知,FileNotFoundException是编译时异常。
错误原因?编译时异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就会报错。
*/