第三章
- 异常的基本概念
- 异常的分类
- 异常的捕捉和处理
- 自定义异常
- 方法覆盖与异常
java异常的处理机制及分类
异常在Java中以类和对象的形式存在。异常的继承结构图怎样的呢?
Object
Throwable(可抛出的)
Throwable下有两个分支Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常。要求必须在编写程序阶段对这些异常进行处理,否则会报错。
RunTimeException:运行时异常。在编写程序阶段可以预处理,也可以不管,都行。
注意:编译时异常和运行时异常,都是发生在运行阶段,编译阶段异常是不会发生的。
编译时异常必须在编译(编写)阶段预处理,如果不处理编译器会报错,因此得名。
编译时异常和运行时异常的区别?
编译时异常发生的概 率高。
运行时异常发生的概率低。
对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
编译时异常又称:
受检异常 CheckedException
受控异常
运行时异常又称:
未受检异常 UnCheckedException
非受控异常
所有异常都是发生在运行阶段。
Java异常
异常的继承结构图1:
异常的捕捉和处理
方式一:throws抛出异常,抛给上一级。
方式二:使用 try…catch语句进行异常的捕捉。
/**
* 程序执行到此处发生java.lang.ArithmeticException: / by zero异常,
* 底层new了一个ArithmeticException异常对象,然后抛出了,由于main方法
* 调用了100/0,所以这个异常ArithmeticException抛给了main方法,main
* 方法没有处理,将这个异常自动报给了JVM。
* JVM最终终止程序的执行。
*
* ArithmeticExceptionj继承RuntimeException,属于运行时异常,在编写程序阶段
* 不用对该异常进行预先处理
*/
public class ExceptionTest03 {
public static void main(String[] args) {
System.out.println(10/0);
System.out.println("hahaha");
}
}
两种处理异常方式
public class ExceptionTest05 {
//第一种方式:上抛throws ClassNotFoundException
//上抛类似于推卸责任(继续报异常传递给调调用者。)
/*
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方法执行!!!");
}
}
异常例子
/**
* 注意:只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
* 另外需要注意:try语句块某一行出现异常,该行后面的代码不会执行,try...catch捕捉后,
* 后续代码可以执行。
*/
public class ExceptionTest06 {
public static void main(String[] args) {
//一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,这个异常一定会抛给JVM,JVM只有终止。
//异常处理机制的作用就是增强程序的健壮性。
//一般main方法中的异常建议使用try...catch进行捕捉。main就不要继续上抛了。
try {
m1();
//以上代码出现异常,直接进入catch语句块。
System.out.println("aaa");
} catch (FileNotFoundException e) {
//这个分支可以使用e引用,e引用保存的内存地址是哪个new出来的异常对象的内存地址。
//catch是捕捉异常之后走的分支。
System.out.println("文件找不到");
}
//try...catch把异常抓住之后,这里的代码会继续执行。
System.out.println("111");
}
private static void m1() throws FileNotFoundException {
m2();
//以上代码出异常,下面的代码是无法执行的
}
private static void m2() throws FileNotFoundException {
m3();
//以上代码出异常,下面的代码是无法执行的
}
private static void m3() throws FileNotFoundException {
//调用SUN jdk中某个类的构造方法
//创建一个输入流,该流指向一个文件
/*
编译保存的原因:
第一:调用了一个构造方法
*/
new FileInputStream("D:\\aa.txt");
//以上代码出异常,下面的代码是无法执行的
}
}
深入try…catch
/**
* 深入try...catch
* 1、catch后面的小括号中的类型可以是具体的异常类型,也可以是该类型的父类异常类型。
* 2、catch可以写多个。建议catch的时候精确的一个一个处理,这样有利于查询的调试。
* 3、catch写多个的时候,从上到下,必须遵守从小到大的,否则会编译报错。
* 先写catch FileNotFoundException e,再catch IOException e
*/
public class ExceptionTest07 {
/*
public static void main(String[] args) throws Exception, FileNotFoundException,NullPointerException {
}
*/
/*
public static void main(String[] args) throws Exception{
}
*/
public static void main(String[] args) {
/*
try {
FileInputStream fis = new FileInputStream("F:\\研生课程\\1. 英语写译(周一56节)\\新建 文本文档.txt");
System.out.println("aaa");
} catch (FileNotFoundException e) {
System.out.println("文件不存在!");
}
System.out.println("hello Tom");
*/
//以下异常用父类引用也可以
/*
try {
FileInputStream fis = new FileInputStream("F:\\研生课程\\1. 英语写译(周一56节)\\新建 文本文档.txt");
System.out.println("aaa");
} catch (IOException e) {//多态:IOException e = new FileNotFoundException();
System.out.println("文件不存在!");
}
System.out.println("hello Tom");
*/
/*
try {
//创建输入流
FileInputStream fis = new FileInputStream("F:\\研生课程\\1. 英语写译(周一56节)\\新建 文本文档.txt");
//读文件
fis.read();
} catch (FileNotFoundException e) {
System.out.println("文件不存在!");
} catch (IOException e) {
System.out.println("读文件报错了!");
}
System.out.println("hello Tom");
*/
//JDK8的新特性!
try {
//创建输入流
FileInputStream fis = new FileInputStream("F:\\研究生课程\\1. 英语写译(周一56节)\\新建 文本文档.txt");
//进行数学运算
System.out.println(10/0);//运行时异常
} catch (FileNotFoundException | ArithmeticException | NullPointerException e) {
System.out.println("文件不存在?数学异常?空指针异常?");
}
System.out.println("hello Tom");
}
}
异常对象的两个方法
/**
* 异常对象有两个非常重要的方法:(一般使用第二个!!!)
*
* 获取异常简单的描述信息:
* String msg = exception.getMessage();
*
* 打印异常追踪的堆栈信息:
* exception.prinntStackTrace();
*/
public class ExceptionTest08 {
public static void main(String[] args) {
//这里只是为了测试getMessage()方法和printStackTrace()方法。
//这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的java对象。
NullPointerException npe = new NullPointerException("空指针异常");
//获取异常简单的描述信息
String msg =npe.getMessage();
System.out.println(msg);
//打印异常追踪的堆栈信息
npe.printStackTrace();
for(int i=0;i<1000;i++){
System.out.println("i="+i);
}
System.out.println("Hello World");
}
}
/**
* 异常对象的两个方法
* String msg = exception.getMessage();
* exception.prinntStackTrace();
* 我们以后查看异常的追踪信息,我们应该怎么看?
* 从上往下一行一行看,SUN写的代码不用看。主要的问题出现在自己编写的代码上先看33行、然后29、25、14。
*/
public class ExceptionTest09 {
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
//打印异常追踪的堆栈信息,在实际开发中建议用这个。
e.printStackTrace();
/*
java.io.FileNotFoundException: F:\研究生程\1. 英语写译(周一56节)\新建 文本文档.txt (系统找不到指定的路径。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at com.chinasoft.javase.exception.ExceptionTest09.m3(ExceptionTest09.java:33)
at com.chinasoft.javase.exception.ExceptionTest09.m2(ExceptionTest09.java:29)
at com.chinasoft.javase.exception.ExceptionTest09.m1(ExceptionTest09.java:25)
at com.chinasoft.javase.exception.ExceptionTest09.main(ExceptionTest09.java:14)
//
*/
}
//这里不耽误执行,很健壮!(服务器不会因为异常而宕机)
System.out.println("hello Tom");
}
private static void m1() throws FileNotFoundException {
m2();
}
private static void m2() throws FileNotFoundException {
m3();
}
private static void m3() throws FileNotFoundException {
new FileInputStream("F:\\研究课程\\1. 英语写译(周一56节)\\新建 文本文档.txt");
}
}
try…catch中的finally子句
/**
* 关于try..catch中的finally子句:
* 1、在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块代码中出现了异常。
* finally语句必须与try一起出现,不能单独使用。
* 2、finally语句通常使用在哪些情况呢?
* 通常在finally语句中完成资源的释放/关闭。
* 因为finally中的代码比较有保障。
* 即使try语句块出现了异常,finally中的代码也会正常执行。
*/
public class ExceptionTest10 {
public static void main(String[] args) {
FileInputStream fis = null;//声明位置放到try外面这样在finally中才能用。
try {
fis = new FileInputStream("D:\\aa.txt");
//...
String s=null;
//这里一定会出现空指针异常。
s.toString();
//流使用完需要关闭,因为流是占用资源的。
//即使以上程序出现异常,流也必须关闭。
//放在这里有可能流关不了
//fis.close();
System.out.println("aaa");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
System.out.println("空指针异常了");
} finally {
System.out.println("hello 浩克");
//流的关闭放在这里比较保险。
//finally中的代码一定会执行。
//即使try中出现了异常!
if (fis!=null){//避免空指针异常
try {
//close()方法有异常采用捕捉的方式。
fis.close();
System.out.println("fis已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("finally代码执行!");
}
}
}
try…finally联合使用
/**
* 放在finally语句中的代码一定会执行的。(再次强调!!!)
*/
public class ExceptionTest11 {
public static void main(String[] args) {
/*
try和catch,没有catch可以吗?可以。
try不能单独使用
try finally 可以联合使用。
以下代码的执行顺序:
先执行try...
在执行finally...
最后执行 return(return语句只要执执行,方法比如结束。);
*/
try {
System.out.println("try....");
return;
}finally {
System.out.println("finally...");
}
//这里的语句不能执行,因为这个代码是无法执行得到
//System.out.println("hello world");
}
}
/**
* 退出JVM之后finally语句就不执行了。
* 运行结果:
* try....
*/
public class ExceptionTest12 {
public static void main(String[] args) {
try {
System.out.println("try....");
//退出JVM finally里的代码就不能执行了
System.exit(0);
}finally {
System.out.println("finally...");
}
}
}
finally面试题
/**
* finally面试题
*/
public class ExceptionTest13 {
public static void main(String[] args) {
int ret = m();
System.out.println("ret = "+ret);
}
public static int m() {
int i=100;
try {
return i;
}
finally {
i++;
}
}
}
final、finally、finalize的区别?
/**
* final、finally、finalize的区别?
* final:
* final修饰的类无法被继承。
* final修饰的方法不能被覆盖/重写。
* final修饰的变量不能被修改。
* finally:
* finally和try联合使用。
* finally语句块中的代码是一定执行的。
* finalize:
* 是一个Object类中的方法。
* 这个方法是由垃圾回收器GC负责调用的。
*/
public class ExceptionTest14 {
public static void main(String[] args) {
//final三个关键字。表示最终的。不变的。
final int i = 100;
//i = 200;
//finally也是一个关键字,和try联合使用,使用在异常处理机制中。
//在finally语句块中的代码是一定会执行的。
try{
}finally {
System.out.println("finally...");
}
//finalize()是Object类中的一个方法。作为方法名出现
//所以finalize是标识符。
//finalize()方法是JVM的GC垃圾回收器负责调用。
Object obj;
}
}
如何自定义异常?
MyException类:
/**
* 1、SUN提供的JDK内置的异常肯定是不够用的。在实际开发中,有很多业务,
* 这些业务出现异常之后,JDK是没有的。和业务挂钩的。因此我们要会自定义异常。
* 2、Java中怎么自定义异常呢?
* 两步:
* 第一步:编写一个异常类继承Exception或者RuntimeException
* 第二步:提供两个构造方法,一个无参数的,一个有参数的。
*/
public class MyException extends Exception{//编译时异常
public MyException() {//无参
}
public MyException(String message) {//有参
super(message);
}
}
/*
public class MyException extends RuntimeException{//运行时异常
}
*/
测试类:
public class ExceptionTest15 {
public static void main(String[] args) {
//创建异常对象(并没有抛出)
MyException me = new MyException("用户名不能为空!!!");
//打印异常堆栈信息
me.printStackTrace();
//获取异常简单描述信息
String msg = me.getMessage();
System.out.println(msg);
}
}
自定义异常例子:(以栈的操作为例)重点!!!
自定义 栈操作异常类:
public class MyStackException extends Exception{
public MyStackException() {
}
public MyStackException(String message) {
super(message);
}
}
栈类:
public class Stack {
private int[] array = new int[5];
private int length;
public Stack() {
}
public Stack(int[] array, int length) {
this.array = array;
this.length = length;
}
public void push(int x) throws MyStackException {//入栈
if (length==5){
//System.out.println("栈满!!!");
//return;
throw new MyStackException("栈满");
}
array[length++]=x;
}
public int pop() throws MyStackException {
if(length==0){
//System.out.println("栈空!!!");
//return;
throw new MyStackException("栈空");
}
return array[--length];//出栈
}
public void printArray(){
for (int i=0;i<length;i++){
System.out.println(array[i]);
}
}
public int[] getArray() {
return array;
}
public void setArray(int[] array) {
this.array = array;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "Stack{" +
"array=" + Arrays.toString(array) +
", length=" + length +
'}';
}
}
测试类:
/**
* 测试改良后的Stack
* 注意:最后这个例子,是异常最重要的案例,必须掌握!!!自定义异常在开发中的应用。
*/
public class ExceptionTest16 {
public static void main(String[] args) {
Stack stack = new Stack();
try {
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
stack.printArray();
//stack.push(6);//栈满
System.out.println("========================================");
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.printArray();
stack.pop();
stack.pop();//栈空
} catch (MyStackException e) {
e.printStackTrace();
}
}
}
/*
运行结果:
1
2
3
4
5
========================================
1
com.chinasoft.javase.exception.MyStackException: 栈空
at com.chinasoft.javase.exception.Stack.pop(Stack.java:29)
at com.chinasoft.javase.exception.ExceptionTest16.main(ExceptionTest16.java:21)
*/
异常练习:
自定义异常类:
public class MyRegisterException extends Exception{
public MyRegisterException() {
}
public MyRegisterException(String message) {
super(message);
}
}
UserService类:
public class UserService {
private String userName;
private String password;
public UserService() {
}
public UserService(String userName, String password) {
this.userName = userName;
this.password = password;
}
public void register(String userName,String password) throws MyRegisterException {//注册
if (userName==null||userName.length()<6||userName.length()>14){
throw new MyRegisterException("用户名要求在[6,14]之间!!!");
}
System.out.println("注册成功!!");
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserService{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
测试类:
public class ExceptionTest18 {
public static void main(String[] args) {
java.util.Scanner sr = new java.util.Scanner(System.in);
System.out.println("请输入用户名:");
String userName = sr.next();
System.out.println("请输入密码:");
String password = sr.next();
UserService us = new UserService(userName,password);
try {
us.register(userName,password );
} catch (MyRegisterException e) {
e.printStackTrace();
}
}
}
//运行结果1
/*
请输入用户名:
wl
请输入密码:
123
com.chinasoft.javase.exception.MyRegisterException: 用户名要求在[6,14]之间!!!
at com.chinasoft.javase.exception.UserService.register(UserService.java:18)
at com.chinasoft.javase.exception.ExceptionTest18.main(ExceptionTest18.java:14)
*/
//运行结果2
/*
请输入用户名:
wanglei
请输入密码:
123
注册成功!!
*/
武器数组练习
武器父类:
public class Weapon {//武器父类
private String weaponName;
public Weapon() {
}
public Weapon(String weaponName) {
this.weaponName = weaponName;
}
public String getWeaponName() {
return weaponName;
}
public void setWeaponName(String weaponName) {
this.weaponName = weaponName;
}
@Override
public String toString() {
return "Weapon{" +
"weaponName='" + weaponName + '\'' +
'}';
}
}
可移动的(接口):
public interface Moveable {//可以动的
void move();
}
可攻击的(接口):
public interface Attainable {//可攻击的
void attack();
}
异常类:
public class WeaponNumException extends Exception{//武器数量异常
public WeaponNumException() {
}
public WeaponNumException(String message) {
super(message);
}
}
武器类:
public class Aircraft extends Weapon implements Moveable,Attainable{//飞机
@Override
public void attack() {
System.out.println("飞机正在攻击!");
}
@Override
public void move() {
System.out.println("飞机正在移动。");
}
}
public class Tank extends Weapon implements Moveable,Attainable{//坦克
@Override
public void attack() {
System.out.println("坦克正在攻击!");
}
@Override
public void move() {
System.out.println("坦克正在移动。");
}
}
public class Missiles extends Weapon implements Moveable,Attainable{//导弹
@Override
public void attack() {
System.out.println("导弹正在攻击!!!");
}
@Override
public void move() {
System.out.println("导弹正在移动。");
}
}
军队类:
注意:类在转换成某个接口的时候不需要有继承关系!!
public class Army {//军队
private Weapon[] weapons;
private int length;//已有武器数量
private int maxSize;//数组最大容量
public Army() {
}
public Army(int maxSize) {//初始化 创建maxSize大小的weapons数组
this.weapons = new Weapon[maxSize];
this.maxSize=maxSize;
}
public void addWeapon(Weapon weapon) throws WeaponNumException {//添加武器
if (length==maxSize){//此时已满
throw new WeaponNumException("军队武器已满!!!");
}
if (weapon!=null){
weapons[length++]=weapon;
}
}
public void moveAll(){//所有武器 移动
for (int i=0;i<length;i++){//直接强转为接口类型,利用多态调用方法
if (weapons[i] instanceof Moveable){
Moveable m = (Moveable)weapons[i];
m.move();
}
}
}
public void attackAll(){//所有武器 攻击
for (int i=0;i<length;i++){//直接强转为接口类型,利用多态调用方法
if (weapons[i] instanceof Attainable){
Attainable a = (Attainable)weapons[i];
a.attack();
}
}
}
public Weapon[] getWeapons() {
return weapons;
}
public void setWeapons(Weapon[] weapons) {
this.weapons = weapons;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
@Override
public String toString() {
return "Army{" +
"weapons=" + Arrays.toString(weapons) +
", length=" + length +
", maxSize=" + maxSize +
'}';
}
}
测试类:
public class ArrayTest01 {
public static void main(String[] args) {
java.util.Scanner sr =new java.util.Scanner(System.in);
System.out.println("请输入军队需要存放的武器数量:");
int size = sr.nextInt();
Army army = new Army(size);//创建size个武器的军队
army.addWeapon(new Tank());
army.addWeapon(new Missiles());
army.addWeapon(new Aircraft());
army.addWeapon(new Aircraft());
army.moveAll();
System.out.println("======================");
army.attackAll();
}
}
/*
运行结果1:
请输入军队需要存放的武器数量:
5
坦克正在移动。
导弹正在移动。
飞机正在移动。
飞机正在移动。
======================
坦克正在攻击!
导弹正在攻击!!!
飞机正在攻击!
飞机正在攻击!
运行结果2:
请输入军队需要存放的武器数量:
3
com.chinasoft.javase.arrayCase.WeaponNumException: 军队武器已满!!!
at com.chinasoft.javase.arrayCase.Army.addWeapon(Army.java:22)
at com.chinasoft.javase.arrayCase.ArrayTest01.main(ArrayTest01.java:14)
坦克正在移动。
导弹正在移动。
飞机正在移动。
======================
坦克正在攻击!
导弹正在攻击!!!
飞机正在攻击!
*/