Exception
1.简介
-
什么是异常,java提供异常机制有什么用?
当程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常。java语言是很完善的,提供了异常处理机制,当程序执行过程中出现了不正常的情况,java把异常信息打印输出到控制台,供程序员参考,程序员看到异常信息后,可以对程序进行修改,让程序看起来更加的健壮。
-
如:
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(c); } }
以上程序出现了:
Exception in thread “main” java.lang.ArithmeticException: / by zero
at com.ma.javase.exception.ExceptionTest01.main(ExceptionTest01.java:9)这个信息我们称为:异常信息。这个信息是JVM打印出来的。
-
java语言中异常是以什么形式存在的?
异常在java中是以类的形式存在,每一个异常类都可以创建异常对象。
如:
public class ExceptionTest02 { public static void main(String[] args) { //通过异常类实例化异常对象 NumberFormatException nfe=new NumberFormatException("数字格式化异常"); System.out.println(nfe);//java.lang.NumberFormatException: 数字格式化异常 //再通过异常类创建对象 NullPointerException npe=new NullPointerException("空指针异常"); System.out.println(npe);//java.lang.NullPointerException: 空指针异常 } }
2. UML
- java 的异常处理机制:
-
异常在java中以类和对象的形式存在。那么异常的继承结构是怎么样的?
-
我们可以使用UML图来描述一下继承结构。
-
画UML图有很多工具,例如:Rational Rose(收费的),startURL等…
-
什么是UML?有什么用?
-
UML是一种统一建模语言。
-
UML不是只有java中使用。只要是面向对象的编程语言,都有UML。
-
一般画UML图的都是软件架构师或者是系统分析师。这些级别的人员使用的。
-
在UML图中可以描述类和类之间的关系,程序执行的流程,对象状态等。
-
3.编译时异常和运行时异常
-
编译时异常:
所有Exception的直接子类,都叫做编译时异常。编译时异常是表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器会报错。编译时异常又称为受检异常。
-
运行时异常:
运行时异常在编写程序阶段,你可以选择处理,也可以不处理。
4.异常处理的两种方式
-
java语言中对异常的处理包括两种方式:
第一种方式:在方法声明的位置上,使用thows关键字 抛给上一级。
第二种方式:使用try…catch语句进行异常的捕捉。
-
两种方式如何选择:
如果希望调用者来处理,选择throws上报。其他情况选择捕捉的方式。如:
public class ExceptionTest04 { //第一种处理方式:在方法声明位置上继续使用throws上抛 // public static void main(String[] args) throws ClassNotFoundException { //main方法中调用doSome(); //因为doSome()方法声明位置上有:throws ClassNotFoundException //我们在调用doSome()方法的时候必须对这种异常进行预先的处理。 //如果不处理,编译器就报错 //编译器报错: // doSome(); //} //第二种处理方式:try...catch捕获异常 public static void main(String[] args) { //main方法中调用doSome(); //因为doSome()方法声明位置上有:throws ClassNotFoundException //我们在调用doSome()方法的时候必须对这种异常进行预先的处理。 //如果不处理,编译器就报错 //编译器报错: try { doSome(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * doSome方法在方法声明的位置上:throws ClassNotFoundException * 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。 * 叫类没找到异常。这个异常直接父类是:Exception 所有ClassNotFoundException属于编译时异常 * @throws ClassCastException */ public static void doSome() throws ClassNotFoundException{ System.out.println("doSome"); } }
-
异常捕捉和上报的联合使用:
/* * 处理异常的第一种方式:在方法声明的位置上使用throws关键字抛出,谁调用我这个方法,我就抛给谁。 * 抛给调用者来处理 * throws后面可以写多个异常:用逗号隔开 * */ public class ExceptionTest05 { //一般不建议在main方法上使用throws 因为这个异常一旦发生,JVM只有终止 //异常处理机制的作用就是增强程序的健壮性。怎么能做到异常发生了,也不影响程序的执行。 //所以一般main方法上的异常建议使用try..catch进行捕捉 public static void main(String[] args) { System.out.println("main begin"); try { //try尝试 m1(); } catch (FileNotFoundException e) { //这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常对象的内存地址 //catch是捕捉异常后走的分支 System.out.println("文件不存在,可能路径错误"); } //try...catch把异常抓住后,这里的代码会继续执行 System.out.println("main over"); } 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()方法声明位置上有:throws FileNotFoundException //我们在这里调用m3()没有对异常进行预处理,所有编译报错 m3(); //若出现异常不执行 System.out.println("m2 over"); }
-
try…catch特性:
/* * 深入try..catch * 1.catch后面的小括号中的类型可以是具体的异常类型,也可以是异常类型的父类型 * 2.catch可以写多个,建议catch的时候,精确的一个一个处理。这样有利于程序的调试 * 3.catch写多个的时候,从上到小,必须遵守从小到大 * * */ public class ExceptionTest06 { public static void main(String[] args) { try { FileInputStream fs= new FileInputStream("C:\\Users\\user\\Desktop\\MarkDown学习\\Day01.md"); System.out.println("以上代码出现异常,这里无法执行!"); fs.read(); } catch (FileNotFoundException e) { System.out.println("文件不存在"); } catch (IOException e) { System.out.println("读文件报错"); } //都会执行 System.out.println("hello world"); } }
-
jdk8新特性:
public class ExceptionTest06 { public static void main(String[] args) { //jdk8新特性: try { FileInputStream fs= new FileInputStream("C:\\Users\\user\\Desktop\\MarkDown学习\\Day01.md"); //进行数学运算: System.out.println(100/0); //运行时异常 } catch (FileNotFoundException | ArithmeticException | NullPointerException e) { System.out.println("有异常"); } } }
5.异常对象的常用方法
异常对象有两个非常重要的方法
-
获取异常简单的描述信息:
String msg=exception.getMessage();
-
打印异常追踪的堆栈信息
exception.printStackTrace();
-
如:
/* * 异常对象有两个非常重要的方法 * 获取异常简单的描述信息: * String msg=exception.getMessage(); * * * 打印异常追踪的堆栈信息 * exception.printStackTrace(); * */ public class ExceptionTest07 { public static void main(String[] args) { NullPointerException e=new NullPointerException("空指针异常"); //获取异常简单描述的信息:这个信息实际上就是构造方法上面String参数 String msg= e.getMessage(); System.out.println(msg);//空指针异常 //打印异常信息 e.printStackTrace();//java.lang.NullPointerException: 空指针异常 //at com.ma.javase.exception.ExceptionTest07.main(ExceptionTest07.java:13) System.out.println("hello world"); } }
6.try,catch和finally
-
关于try,catch中的finally子句:
-
在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现异常。
finally子句必须和try一起出现,不能单独编写。 -
finally语句通常使用在哪些情况下呢?
通常在finally语句块中完成资源的释放或关闭。
因为finally中的代码比较有保障。
即使try语句块中的代码出现异常,finally中的代码也会执行。
-
-
代码如下:
public class ExceptionTest09 { public static void main(String[] args) { FileInputStream fils=null;//声明位置放在try外面,这样在finally中才能使用 try { //创建输入流对象 fils=new FileInputStream("C:\\Users\\user\\Desktop\\MarkDown学习\\Day01.md"); //这里一定会出现空指针异常 String s=""; s.toString(); //流使用完需要关闭,因为流是占用资源的 //即使以上程序出现异常,流也必须关闭 //放在这里有可能关闭不了 fils.close(); } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); }catch (NullPointerException e){ e.printStackTrace(); } finally { //流的关闭放在这里比较保险 //finally中的代码是一定会执行的 //即使try中出现异常 if(fils!=null){ //避免空指针异常 try { //close()方法有异常,采用捕捉的方式 fils.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
try和finally联合使用:
public class ExceptionTest10 { public static void main(String[] args) { //可以try和finally,没有catch //执行顺序:先执行try.. 再执行finally.. 最后执行return...(语句只要执行,方法必然结束) try{ System.out.println("try..."); return; }finally { System.out.println("finally..."); } //这里不能写语句,因为这个代码是执行不到的 //System.out.println("..."); } }
面试题:
-
public class ExceptionTest11 { public static void main(String[] args) { System.out.println(m()); //100 } /* * java语法规则: * 1.方法体中的代码必须遵循自上而下的顺序,依次逐行执行。 * 2.return语句一旦执行,整个方法必须结束。 * * * */ public static int m(){ int i=100; try{ //这行代码出现在int i=100下面,所以必须返回100 return i; }finally { ++i; } } }
-
final,finally,finalize的区别:
public class ExceptionTest12 { public static void main(String[] args) { //final是一个关键字,表示最终的,不变的 final int i=100; //i=122; //finally也是一个关键字,和try联合使用,使用在异常处理机制中 //在finally语句块中的代码是一定会执行的 try{ }finally { System.out.println("..."); } //finalize()是Object类中的一个方法,作为方法名出现 //所有finalize是标识符。 //finalize()方法是JVM的gc垃圾回收器负责调用 } }
-
throws和throw的区别:
- throws:在方法声明位置上使用,表示上报异常信息给调用者。
- throw: 手动抛出异常。
7.自定义异常
-
SUN公司提供的JDK内置的异常肯定是不够用的。在实际开发中,有很多业务,这些业务出现异常之后 JDK中都是没有的。那么异常类我们程序员可以自己去定义。
-
Java中怎么自定义异常呢?
两步:
第一步:编写一个类继承Exception(编译时异常)或者RuntimeException(运行时异常)。
第二步:提供两个构造方法,一个无参数的构造方法,一个带有String参数的构造方法。 -
代码演示:
-
创建异常:
/* * 1. SUN公司提供的JDK内置的异常肯定是不够用的。在实际开发中,有很多业务 * 这些业务出现异常之后 JDK中都是没有的。那么异常类我们程序员可以自己去 * 定义。 * 2. Java中怎么自定义异常呢? * 两步: * 第一步:编写一个类继承Exception(编译时异常)或者RuntimeException(运行时异常). * 第二步:提供两个构造方法,一个无参数的,一个带有String参数的。 * * */ public class MyStackOperationException extends Exception{//编译时异常 //无参构造方法 public MyStackOperationException(){}; //带有String参数的有参构造方法 public MyStackOperationException(String s){ //父类方法 super(s); }; } /*public class MyStackOperationException extends RuntimeException{//运行时异常 }*/
-
使用异常:throw new MyStackOperationException(String )
弹栈,压栈案例:
public class MyStack { private Object[]ele; private int index; public MyStack() { this.ele = new Object[10]; this.index = -1; } public Object[] getEle() { return ele; } public void setEle(Object[] ele) { this.ele = ele; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public void push(Object obj) throws MyStackOperationException { if (index>=ele.length-1){ //创建异常对象 // MyStackOperationException msop=new MyStackOperationException("压栈失败,栈已满"); //手动将异常抛出去 // throw msop; //这里用try..catch没有意义。栈已满这个信息需要传递出去,所以用throws //合并 throw new MyStackOperationException("压栈失败,栈已满"); } index++; ele[index]=obj; System.out.println("压栈"+obj+"压栈成功,栈帧指向"+index); } public void POP() throws MyStackOperationException { if(index<0) { throw new MyStackOperationException("弹栈失败"); } System.out.print("弹栈"+ele[index]+"元素成功,"); //栈帧向下移动一位 index--; System.out.println("栈帧指向"+index); } }
-
测试:
public class Test { public static void main(String[] args) { MyStack ms=new MyStack(); try { ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); ms.push(new Object()); } catch (MyStackOperationException e) { System.out.println(e.getMessage()); } try { ms.POP(); ms.POP(); ms.POP(); ms.POP(); ms.POP(); ms.POP(); ms.POP(); ms.POP(); ms.POP(); ms.POP(); ms.POP(); } catch (MyStackOperationException e) { System.out.println(e.getMessage()); } } }
-
8.异常与方法的覆盖
- 重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。
如:
class Animal{
public void doSome(){
}
public void doOther() throws Exception{
}
}
class Cat extends Animal{
//编译报错
/* public void doSome() throws Exception{
}*/
//编译正常
/* public void doOther() {
}*/
//编译正常
/* public void doOther() throws Exception{
}*/
//编译正常
public void doOther() throws NullPointerException{
}
}