1 java的异常处理机制
1.1 异常在java中以类和对象的形式存在。那么异常的继承结构是怎么样的?
我们可以使用UML图来描述一下结构继承。
画UML图有很多工具,例如:Rational Rose(收费的)、startUML等...
Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理的,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常。(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)
RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
1.2 编译时异常和运行是异常,都是发生在运行阶段。编译阶段异常是不会发生的。
编译时异常因为什么而得名?
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。
所有一掺个都是在运行阶段发生的,因为只有程序运行阶段才可以new对象。
因为异常的发生就是new异常对象。
1.3 编译时异常和运行时异常的区别?
编译时异常一般发生的概率比较高。
运行时异常一般发生的概率比较低。
举个例子:
你看到外面下雨了,倾盆大雨的。
你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。
而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。
“拿一把伞”就是对“生病异常”发生之前的一种处理方式。
对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
运行时异常一般发生的概率比较低。
举个例子:
小明走在大街上,可能会被天上的飞机轮子砸到。
被飞机轮子砸到也算一种异常。
但是这种异常发生概率较低。
再出门之前你没必要提前对这种发生概率较低的异常进行预处理。
如果你预处理这种异常,你将活的很累。
假设你再出门之前,你把能够发生的异常都预先处理,你这个人会更加的安全,但是你这个人活的很累。
假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常。
所有的异常都需要在编写阶段对其进行预处理,将是怎样的效果呢?
首先,如果这样的话,程序肯定是绝对安全的。
但是程序员编写程序太累,代码到处都是处理异常的代码。
1.4 编译时异常还有其他名字:
受检异常:CheckdException
受控异常
1.5 运行时异常还有其他名字:
未受控异常:UnCheckdException
非受控异常。
1.6 再次强调:所有的异常都是发生在运行阶段的。
1.7 java语言中对异常的处理包括两种方式:
第一种方式:在方法生命的位置上,使用throws关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try..catch语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。
举个例子:
我是某集团的一个销售员,因为我得失误,导致公司损失了1000元,
“损失1000元”这可以看做是一个异常发生了。我有两种处理方式,
第一种方式:我把这件事情告诉我的领导【异常上抛】
第二种方式:我自己掏腰包把这个钱补上。【异常的捕捉】
什么是异常,java提供异常机制有什么用?
package com.javase.Exception; /* 1 什么是异常,java提供异常机制有什么用? 以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常 java语言是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常情况, java把该异常信息打印输出到控制台,供程序员参考。 程序员看到以后,可以对程序进行修改,让程序更加的健壮。 什么是异常:程序执行过程中的不正常情况。 异常的作用:增强程序的健壮性。 2 以下程序执行控制台出现了: Exception in thread "main" java.lang.ArithmeticException: / by zero at com.javae.Exception.ExceptionTest01.main(ExceptionTest01.java:17) 这个信息被我们称作:异常信息。这个信息是JVM打印的。 */ public class ExceptionTest01 { public static void main(String[] args) { int a= 10; int b = 1; // 实际上JVM在执行到此处的时候,会new异常对象。 new ArithmeticException("/ by zero");对象 // 并且JVM将new的异常对象抛出,打印输出信息到控制台了。 int c = a / b; System.out.println(a +" / "+ b + " = " + c); // 此处运行也会创建一个:ArithmeticException类型的对象。 System.out.println(100 / 0); // 我观察到异常信息之后,对程序进行修改,更加健壮。 /*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);*/ } }
package com.javase.Exception; /* java语言中异常是以什么形式存在的呢? 1 异常在java中以类的形式存在,每一个异常都是一个类。 2 异常对应的现实生活中是怎样的? 火灾(异常类): 2008年8月8日小明家着火了(异常对象) 2008年8月18日小刚家着火了(异常对象) 2008年8月28日小红家着火了(异常对象) 类是:模板。 对象是:实际存在的个体。 钱包丢了(异常类): 2008年1月8日:小明的钱包丢了(异常对象) 2008年1月9日:小明的钱包又丢了(异常对象) ... */ public class ExceptionTest02 { public static void main(String[] args) { // 通过"异常类"实例化"异常对象" NumberFormatException numberFormatException = new NumberFormatException("数字格式化异常"); // java.lang.NumberFormatException: 数字格式化异常 System.out.println(numberFormatException); // 通过“异常类”创建“异常对象” NullPointerException nullPointerException = new NullPointerException("空指针异常发生了"); // java.lang.NullPointerException: 空指针异常发生了 System.out.println(nullPointerException); } }
package com.javase.Exception; public class ExceptionTest03 { public static void main(String[] args) { /* 程序执行到此处发生了ArithmeticException: / by zero异常。 底层new了一个ArithmeticException异常对象。 然后抛出了,由于是main方法调用了100/0 所以这个异常ArithmeticException抛给了main方法 main方法没有处理,将这个异常抛给了JVM。 JVM最终终止程序的执行。 ArithmeticException继承了RunTimeException,属于运行时异常。 在编写程序阶段不需要对这种异常进行预先的处理。 */ System.out.println(100 / 0); // 这里的Hello World没有输出,没有执行。 System.out.println("Hello World"); } }
分析代码异常报错原因案例:
package com.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 static void doSome() throws ClassNotFoundException{ System.out.println("doSome方法执行了"); } }
package com.javase.Exception; 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方法执行了"); } }
异常的上报和捕捉:
package com.javase.Exception; import java.io.FileNotFoundException; import java.io.FileOutputStream; /* 处理异常的第一种方式:在方法声明的位置使用throws关键字抛出,谁调用我这个方法,我就抛给谁,抛给调用者处理。 这种处理异常的态度:上报。 处理异常的第二种方式: 使用try..catch语句对异常进行捕捉。 这个异常不会上报,自己把这个事处理了。 异常抛到此处为止,不在上抛了。 注意: 只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。 另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行。 try..catch执行完成后,该方法后面的java语句会执行。 */ public class ExceptionTest06 { // 一般不建议在main方法上使用throws,因为这个异常如果真正发生了,一定会抛给JVM、JVM只有终止。 // 异常处理机制的作用就是增强程序的健壮性,怎么能做到、异常发生了也不影响程序的执行。 // 所以一般main方法中的异常建议使用try..catch进行捕捉,main就不要继续向上抛了。 public static void main(String[] args) { System.out.println("main begin"); // try尝试 try { m1(); // 以上代码出现异常,直接进入catch语句块中执行。 } catch (FileNotFoundException e) { // catch后面的好像一个方法的形参 // 这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常对象的内存地址。 // catch是捕捉 异常之后走的分支 // 在catch分支中干什么?处理异常。 System.out.println("文件不存在,可能路径错误!也可能该文件被删除了。"); System.out.println(e);// java.io.FileNotFoundException: C:哇哈哈哈哈哈哈哈哈.txt (拒绝访问。) } // try..catch把异常抓住以后,这里的代码会继续执行。 System.out.println("main end"); } public static void m1() throws FileNotFoundException { System.out.println("m1 begin"); m2(); // 出现异常以下代码不会被执行 System.out.println("m1 end"); } // 抛别的不行,抛ClassCastException说明你还是没有对FileNOtFoundException进行处理 // 抛FileNotFoundException的父类对象IOException,这样是可以的。因为IOException包括FileNotFoundException // private static void m2() throws FileNotFoundException { // 这样也可以,因为Exception包括所有的异常。 // private static void m2() throws Exception { // throws后面也可以写多个异常,使用逗号隔开 // private static void m2() throws ClassNotFoundException,FileNotFoundException{ private static void m2() throws FileNotFoundException{ System.out.println("m2 begin"); m3(); // 出现异常以下代码不会被执行 System.out.println("m2 end"); } private static void m3() throws FileNotFoundException { // 调用SUN jdk中某个类的构造方法 // 这个类还没有接触过,后期IO流的时候就知道了。 // 我们只是借助这个类学习一下异常处理机制。 // 创建一个输入流对象,该流指向一个文件。 System.out.println("m3 begin"); /* 编译报错的原因是什么? 1 第一:这里调用了一个构造方法:FileInputStream(String name) 2 第二:这个构造方法的声明位置上有:throws FileNotFoundException 3 第三:通过类的继承结构看到:FileNotFoundException的父类是IOException,IOException的父类是Exception 最终得知,FileNotFoundException是编译时异常。 错误原因?编译时异常要求程序员编写程序阶段必须对他进行处理,不处理编译器报错。 */ // 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。 // new FileOutputStream("C:\Users\xlWu\Desktop\学习\异常\学习.txt"); new FileOutputStream("C:哇哈哈哈哈哈哈哈哈.txt"); // 出现异常以下代码不会被执行 System.out.println("m1 end"); } }
package com.javase.Exception; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; /* 深入try..catch 1 catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。 2 catch可以写多个,建议catch的时候,精确的一个一个处理,这样有利于程序的调试。 3 catch写多个的时候,从上到下,必须遵循从小到大。 在以后的开发中,处理编译时异常,应该上报还是捕捉呢,怎么选? 如果希望调用者来处理,选择throws上报。 其它情况使用捕捉的方式。 */ public class ExceptionTest07 { /*public static void main(String[] args) throws Exception, FileNotFoundException,NullPointerException,NumberFormatException { }*/ /*public static void main(String[] args) throws Exception { }*/ public static void main(String[] args) { /*try { FileInputStream fis = new FileInputStream("D:\Users\xlWu\Desktop\学习\异常\异常的继承结构图.uml"); System.out.println("出现异常,这里无法执行!"); } catch (FileNotFoundException e) { System.out.println("文件不存在!"); } System.out.println("hello world!");*/ /*try { FileInputStream fis = new FileInputStream("D:\Users\xlWu\Desktop\学习\异常\异常的继承结构图.uml"); System.out.println("出现异常,这里无法执行!"); *//*} catch (IOException e) {// 多态 IOException e = new FileNotFoundException();*//* } catch (Exception e) {// 多态 Exception e = new FileNotFoundException(); System.out.println("文件不存在!"); }*/ /*try { // 创建输入流 FileInputStream fis = new FileInputStream("D:\Users\xlWu\Desktop\学习\异常\异常的继承结构图.uml"); // 读文件 fis.read(); } catch (Exception e) { // 所有的异常都走这个分支 System.out.println("文件不存在!"); }*/ /*try { // 创建输入流 FileInputStream fis = new FileInputStream("D:\Users\xlWu\Desktop\学习\异常\异常的继承结构图.uml"); // 读文件 fis.read(); } catch (FileNotFoundException e) { System.out.println("文件不存在!"); } catch (IOException e) { System.out.println("读文件报错了!"); }*/ // 编译报错 自上而下执行 必须由小到大 /*try { // 创建输入流 FileInputStream fis = new FileInputStream("D:\Users\xlWu\Desktop\学习\异常\异常的继承结构图.uml"); // 读文件 fis.read(); } catch (IOException e) { System.out.println("文件不存在!"); } catch (FileNotFoundException e) { System.out.println("读文件报错了!"); }*/ // JDK8的新特性 try { // 创建输入流 FileInputStream fis = new FileInputStream("D:\Users\xlWu\Desktop\学习\异常\异常的继承结构图.uml"); // 进行数学运算 System.out.println(10 / 0);// 这个是运行时异常,编写程序时可以处理,也可以不处理。 } catch (FileNotFoundException | ArithmeticException | NullPointerException e) { System.out.println("文件不存在?数学异常?空指针异常?都有可能!"); } } }