异常处理
1.java的异常处理机制
1.1 异常在Java中是以对象的形式存在的
1.1.1 异常的继承结构
- 继承结构可以用画UML(Unified Modeling Language 统一建模语言)图的形式来表示
- 一般是架构师或者系统分析师使用
- 相当于盖楼之前的设计图
- Java中的异常继承结构
- Object
- Trowable
- Exception
- Exception 的直接子类:编译时异常 (要求程序员务必在编写程序的时候预先处理这些异常)
- 编译时异常只是提醒程序员,如果不处理这段代码非常非常大的概率出现错误
- 编译时异常其实不是异常,只是警告程序员,这样写运行的时候大概率报错。
- 只有运行阶段才会发生异常,发生异常就是new一个异常对象
- 就像有一条马路和一根钢丝,提示程序员把钢丝换成马路,不然编译不给过,因为走钢丝99%出事(大概率)
- RuntimeException : 运行时异常 (在编写程序阶段处理不处理都可以)
- 运行时异常是程序运行时发生的异常
- Exception 的直接子类:编译时异常 (要求程序员务必在编写程序的时候预先处理这些异常)
1.1.2 编译时异常和运行时异常的比较
- 编译时异常发生的概率非常高,如果不在编写时处理,运行大概率报错
- 运行时异常发生概率很小,因为大概率的错误在编写的时候避免了
1.1.3 编译时异常的其他名字
- 受检异常:CheckedException
- 受控异常
1.1.5 运行时异常的其他名字
- 未受控异常 UnCheckedException
- 非受控异常
1.1.6 处理异常的两种方式
- 在方法声明的位置上使用throws关键字
- 如果在该方法处不方便处理,就使用throws关键字抛给上一级,给上一级处理
- 使用try…catch语句进行一次的捕捉
- 在这里就直接处理掉,不继续抛给调用者,真正解决这个异常
注意:
- Java发生异常如果一直上抛给调用者,最终抛给了main方法,main方法继续上抛则抛给了调用者JVM,JVM会终止程序
2.异常的发生过程
//例一
class Draft01{
public static void main(String[] args) {
/*
程序执行到这里发生了ArithmaticException
底层new了一个ArithmaticException异常对象,然后抛给main方法(抛给调用者)
main方法没有对这个异常进行预先处理,将这个异常抛给了JVM
JVM终止了程序的运行
*/
System.out.println(100/0);
//由于程序在上面终止了,所以没有输出Hello World!
System.out.println("Hello World!");
}
}
//例二
class Main{
public static void main(String[] args) {
NumberFormatException nfe=new NumberFormatException("数字格式化异常!");
System.out.println(nfe);
NullPointerException npe=new NullPointerException("空指针异常!");
System.out.println(npe);
//doSome();报错
/*
doSome()抛出了ClassNotFoundException异常
main方法调用 doSome() 的时候需要处理这个异常
而main方法中没有处理
*/
System.out.println("Hello");//doSome()方法的异常没有捕捉,后面的代码不执行
}
/*
这里在方法的声明处使用throws ClassNotFoundException说明该方法很呢能出现
ClassNotFoundException异常,需要调用者对这个异常进行处理
如果没人调用,则程序正常运行
*/
public static void doSome() throws ClassNotFoundException{
System.out.println("dosome");
}
}
2.1 代码什么时候不执行
- 异常没有捕捉,采用上报的方式,后面的代码不执行
- try语句块中某句出现异常,后面的代码不执行,直接进入catch语句块
2.2 try…catch深入
- catch()括号中可以写父类异常类型也可以写确切的异常类型
- catch可以写多个,建议精确写出,一个个处理,这样利于调试
- catch写多个的时候必须遵循从小到大(子类到父类)
- 因为catch是从上到下依次捕捉
- 如果父类在上面,下面的子类异常都被上面的父类捕捉,后面的子类catch语句不会执行,编译器会报错
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try {
FileInputStream fis=new FileInputStream("");
fis.read();
}catch (FileNotFoundException | NullPointerException e){//Java8新写法,或的方式写多个异常
//子类异常,如果出现FileNotFoundException异常,就执行里面的语句
System.out.println("文件不存在!!");
}catch (IOException e){//父类异常
System.out.println("读文件报错了!!");
}
}
}
3. 异常对象的常用方法
-
获取异常的简单描述信息(异常的构造方法中传入的信息)
-
String msg=e.getMessage();
-
-
打印异常追踪的堆栈信息(更详细的异常信息,方便找出具体错误,建议开发中使用该方法)
-
e.printStackTrace();
-
public class Main {
public static void main(String[] args) {
NullPointerException e=new NullPointerException("空指针异常!");
//throw e; 这样才算抛出异常,上面只是创建异常对象,没有抛出之前是一个普通的Java对象
//获取异常的简单描述信息
String msg=e.getMessage();//上面空指针异常的构造方法中传入的信息
System.out.println(msg);
//打印异常追踪的堆栈信息
e.printStackTrace();
}
}
4. finally子句的使用
- finally子句中的代码是最后执行的,并且一定会执行,即使try语句块中出现了异常
- finally子句常用情况
-
完成资源的释放/关闭
-
如果在别的情况下关闭,可能在关闭前出现异常,导致关闭语句不执行,非常不安全
-
try不能单独使用,可以和catch或者finally联用
public class Main {
public static void main(String[] args) {
/*
try和finally可以联合使用
执行顺序:
try...
finally...
return
*/
try {
System.out.println("try...");
return;
}finally {
//finally中的语句会执行
System.out.println("finally...");
}
//System.out.println("hello"); 报错,这里无法执行到,finally执行完执行return
}
}
- 不执行finally子句的特殊情况
public class Main {
public static void main(String[] args) {
try {
System.out.println("try...");
//退出JVM,此时不再执行finally中的语句
System.exit(0);
}finally {
System.out.println("finally...");
}
}
}
5. final,finally,finalize的区别
- final
- final修饰的类无法继承
- final修饰的方法无法覆盖
//final修饰的类无法继承
final class A{
//常量
public static final double PI=3.1415926;
public final void doSome(){
//final修饰的方法无法继承
}
}
-
finally
- 一个关键字,和try联用,用于异常处理
-
finalize
- 一个标识符
- Object类中的一个方法
- 垃圾回收前执行该方法中的代码(遗言),该方法已经过时
6. 自定义异常
- Java中自带的异常是不够用的,实际开发中需要自己自定义一些异常
6.1 自定义异常的步骤
- 第一步
- 编写一个Exception或者RuntimeException
- 第二步
- 提供两个构造方法,一个无参,一个有参
public class MyException extends Exception{//编译时异常
//无参构造
public MyException() {
}
//有参构造
public MyException(String message) {
super(message);
}
}
/*
public class MyException extends Exception{//运行时异常
}
*/
public class Main {
public static void main(String[] args) {
//创建一个异常对象(还没抛出)
MyException e=new MyException("用户名不能为空!");
//打印异常堆栈信息
e.printStackTrace();
}
}
6.2 异常在开发中的使用
public class Main {
public static void main(String[] args) {
try {
doSome();
}catch (Exception e){
//打印异常的简单信息
System.out.println(e.getMessage());
//打印异常堆栈信息(更详细,定位到出错的行数)
e.printStackTrace();
}
}
public static void doSome() throws MyException{
//抛出编译时异常,需要解决,(上抛或try catch捕捉)
//如果在这里捕捉相当于没抛,不如直接打印一行信息
//所以采用上抛的方式,调用的时候按具体情况来捕捉,既可以终止程序,又可以打印异常信息
throw new MyException("这里出现了异常!");
}
}
6.3 重写之后的方法只能抛出比原来相同或更少的异常
- 父类方法没有抛出异常,子类不能抛出
class Animal{
public void doSome(){
}
}
class Cat extends Animal{
/*
父类中的方法没有抛出异常,子类不能抛出
public void doSome() throws Exception{
}*/
}
- 父类方法抛出异常,子类可以抛出相同,范围更小异常,或者不抛出
class Animal{
public void doSome() throws Exception{
}
public void doOther() throws Exception{
}
public void m() throws Exception {
}
}
class Cat extends Animal{
//父类方法抛出异常,子类可以抛出相同或范围更小的异常,也可以不抛出
//不抛出
public void doSome(){
}
//相同
public void doOther() throws Exception {
}
//更小
public void m() throws NullPointerException {
}
}