java异常机制
异常的基本概念
什么是异常
-
在程序运行中出现的错误,称为异常
-
异常模拟的是现实世界中不正常的事件
-
java中采用类的方式实现异常(和其他的类是一样的,按照类和对象理解就可以啦)
-
类是可以创建对象的
NullPointerException e = 0x1234;
-
e 是引用类型,e 中保存的内存地址指向堆中的异常对象
-
这个对象一定是
NullPointerException
类型的异常 -
e 表示真实存在的
NullPointerException
类型的异常“对象”异常都是一类一类的
-
-
异常举例
ArithmeticException 算术异常(divide by zero)
ArrayIndexOutOfBoundsException 数组越界;
ArrayStoreException 向数组对象中存储错误类型的数据
ClassCastException 类型转换异常 是一类异常
ClassNotFoundException 找不到类异常
CloneNotSupportedException 克隆不支持异常
表示调用了Object类中的clone方法来创建一个对象,但是对象的类中没有实现Cloneable接口
IndexOutOfBoundsException 越界异常
InstantiationException 实例化异常
尝试创建一个不能实例化的类的异常,例如实例化了一个接口,抽象方法,数组类,原始数据类型;
NegativeArraySizeException 负的数组大小异常
尝试创建负数长度的数组的异常;
NoSuchFieldException 访问类中并不存在的成员抛出的异常;
NoSuchMethodException 访问类中并不存在的方法抛出的异常;
NullPointerException 空指针异常;
NumberFormatException 在将字符串类型转为数字类型时,字符串格式不正确的异常;
SecurityException 操作不安全的异常;
StringIndexOutOfBoundsException 由String类中的方法抛出的“字符串越界异常”,一般是`charAt`方法引起的;
UnsupportedOperationException 请求的操作不支持的异常
RuntimeException 继承自 Exception 类,它 和 它的子类都是 unchecked 异常;
Exception 的直接子类都是 checked 异常, 即编译时异常,需要提前处理(throws 或者 try...catch...)
- 异常举例
public class Test01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a/b;// ArithmeticException
// 上面的代码出现了异常,“没有处理”,下面的代码不会执行,直接退出了 JVM
System.out.println(c);
}
}
/*控制台输出
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.test.exception.Test01.main(Test01.java:7)
*/
- 以上程序编译通过了,但是运行时出现了异常,表示发生了某一个异常事件。
- 控制台输出信息的本质:
- 程序在执行过程中发生了ArithmeticException(算术异常)这个事件,JVM为我们创建了一个ArithmeticException 类型的对象,并且这个对象中包含了详细的异常信息,并且JVM将这个对象中的信息输出到控制台。
异常机制的作用
-
java 语言为我们提供了一种完善的异常处理机制,作用是:
程序发生异常事件之后,为我们输出详细的信息,通过这个信息,可以对程序进行一些处理,使得程序更加健壮。
异常的分类&异常继承结构
异常继承结构
异常的捕获和处理
使用throws
关键字声明抛出异常
-
在方法声明的位置上使用 throws 关键字向上抛出异常
-
例如
import java.io.*; public class Test02 { public static void main(String[] args) { FileInputStream fs = new FileInputStream("c:\\ab.txt"); } }
- 以上程序编译不通过
.\Test02.java:7: 错误: 未报告的异常错误FileNotFoundException; 必须对其进行捕获或声明以便抛出
FileInputStream fs = new FileInputStream("c:\\ab.txt");
// 为啥编译器会检测出来这个个编译时异常咧?
因为FileInputStream 这个构造方法在声明的位置上使用了 throws FileNotFoundException;
-
使用 throws 处理异常不是真正的处理异常,而是推卸责任 ;只是一直往上抛并没有处理
import java.io.*; public class Test03 { public static void main(String[] args) throws FileNotFoundException { m1();// m1()方法的声明位置上使用throws(向上抛) // 上面的m1方法如果出现了异常,因为采用的是throws上抛给了JVM,JVM遇到这个异常就会退出JVM // 下面这行代码就不会执行 System.out.println("hello world"); } static void m1() throws FileNotFoundException{ m2();// m2 方法声明的位置上使用throws(向上抛) } static void m2() throws FileNotFoundException { m3();// m3() 方法的声明位置上使用 throws(向上抛) } static void m3() throws FileNotFoundException { m4(); // m4方法的声明位置上使用 throws(向上抛) } static void m4() throws FileNotFoundException{ new FileInputStream("c://daf"); // FileInputStream 构造方法声明位置上使用throws(向上抛) } } /* * 在程序执行过程中出现了 FileNotFoundException类型的异常 * JVM为我们创建了一个FileNotFoundException类型的对象 * 该对象中携带了以下信息,JVM负责将这些信息打印到控制台。 * 并且JVM停掉了程序的运行。 Exception in thread "main" java.io.FileNotFoundException: c:\daf (系统找不到指定的文件。) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at com.test.exception.Test03.m4(Test03.java:20) at com.test.exception.Test03.m3(Test03.java:17) at com.test.exception.Test03.m2(Test03.java:14) at com.test.exception.Test03.m1(Test03.java:11) at com.test.exception.Test03.main(Test03.java:8) */
使用try-catch
捕获处理异常
-
语法结构
try{ // 可能出现异常的代码 }catch(异常类型1 变量){ // 处理异常的代码 }catch(异常类型2 变量){ // 处理异常的代码 }...
-
catch 语句块可以写多个
-
注意:catch语句块中从上到下的异常类型必须是从小到大的
- 例如:
try {//可以编译通过 FileInputStream fis = new FileInputStream("adnx.txt");//捕获对应的FileNotFoundException异常 fis.read();//捕获对应的IOException类型的异常 }catch(FileNotFoundException e) { //子异常 }catch(IOException e) { // 父异常 } try {//编译报错 FileInputStream fis = new FileInputStream("adnx.txt");//捕获对应的FileNotFoundException异常 fis.read();//捕获对应的IOException类型的异常 }catch(IOException e) {//父异常 }catch(FileNotFoundException e) {//子异常 }
-
try … catch …中最多执行一个 catch 语句块。执行结束之后 try…catch… 就结束了
-
-
例子:
import java.io.*; public class Test05 { public static void main(String[] args) { try { // 程序执行到此处发生了FileNotFoundException类型的异常 // JVM 会自动创建一个FileNotFoundException 类型的对象,将该对象的内存地址赋值给catch出语句块中的e变量 FileInputStream fis = new FileInputStream("abc"); // 上面代码出现了异常,try语句块的代码不再继续执行,直接进入catch语句块中执行 fis.read(); }catch(FileNotFoundException e) { System.out.println("文件不存在异常"); // FileNotFoundException 类将Object类中的toString()方法重写了。 System.out.println(e);//java.io.FileNotFoundException: abc (系统找不到指定的文件。) }catch(IOException e){ System.out.println("其它IO异常"); } } }
getMessage()
&& printStackTrace()
如何取得异常对象的具体信息,常用的方法有两种:
-
取得异常描述信息
getMessage()
-
取得异常的堆栈信息(适用于程序调式阶段)
printStackTrace()
举例:
import java.io.*;
public class Test06 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("c:/adf.txt");
} catch (FileNotFoundException e) {
// 打印堆栈异常信息
//一般情况下都会使用该方法去调试程序
// e.printStackTrace();
// /*输出如下:
// * java.io.FileNotFoundException: c:\adf.txt (系统找不到指定的文件。)
// at java.io.FileInputStream.open0(Native Method)
// at java.io.FileInputStream.open(Unknown Source)
// at java.io.FileInputStream.<init>(Unknown Source)
// at java.io.FileInputStream.<init>(Unknown Source)
// at com.test.exception.Test06.main(Test06.java:9)
// */
// System.out.println("abc");
// 在发生FileNotFoundException异常的时候,JVM为我们执行了下面这行代码,
//FileNotFoundException e = new FileNotFoundException("c:\\adf.txt (系统找不到指定的文件。)");
// Throwable 构造方法:
// public Throwable(String message) {
// fillInStackTrace();
// detailMessage = message;
// }
// 然后我们在调用e.getMessage()的时候会得到下面的输出信息
String msg = e.getMessage();
System.out.println(msg);
//输出 c:\adf.txt (系统找不到指定的文件。)
}
}
}
finally
语句块详解
-
finally
语句块可以直接和 try 语句块连用try ... finally...
-
try...catch...finally
也可以 -
在
finally
语句块中的代码一定会执行的。-
测试:
public class Test07 { public static void main(String[] args) { try { System.out.println("Abc"); return; } finally { System.out.println("hhhh"); } } } // 最终输出 Abc hhhh
可以这么理解程序的执行顺序:
先执行第 4 行, 再执行第 7 行 最后再执行第 5 行
-
再测试
import java.io.*; public class Test08 { public static void main(String[] args) throws FileNotFoundException{ try { FileInputStream fis = new FileInputStream("c:/ab.txt"); // 下面这句不会执行 System.out.println("aaaaa"); } finally { // 下面这句会执行 System.out.println("hhhhh"); } } } /* 输出结构 hhhhh Exception in thread "main" java.io.FileNotFoundException: c:\ab.txt (系统找不到指定的文件。) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at com.test.exception.Test08.main(Test08.java:8) */
可以这么理解程序的执行顺序:
执行到第 6 行出现异常,先执行第 11 行, 再向上抛出异常,JVM打印出异常信息
-
再测试(注意)
- 只有这一种情况下,finally 语句块中的代码不会被执行
- 在执行 finally 语句块之前退出 JVM,则 finally语句块不会被执行
public class Test09 { public static void main(String[] args) { try { // 退出jvm System.exit(0); } finally { // 什么都不会输出 System.out.println("hhhh"); } } }
- 只有这一种情况下,finally 语句块中的代码不会被执行
-
深入 finally
语句块(面试常考)
- 举例01
public class Test10 {
public static void main(String[] args) {
int i = m1();
System.out.println(i); //10
}
private static int m1() {
int i = 10;
try {
return i;
} finally {
i++;
System.out.println("m()->i: " + i);//11
}
}
}
-
以上代码的输出结果为:
m()->i: 11 10
以上代码的执行原理:
public class Test10 { public static void main(String[] args) { int i = m1(); System.out.println(i);//10 } private static int m1() { int i = 10; try { int temp = i; //jvm定义一个临时变量temp 存储 i, 然后return的是临时变量的值 return temp;// 所以返回的10 } finally { i++;// 针对i操作 System.out.println("m()->i: " + i);//11 } } }
-
02
finally
语句块是一般来说是一定会执行的,所以通常在程序中为了保证某资源一定会被释放,一般写在finally
语句块中释放资源import java.io.*; public class Test11 { public static void main(String[] args) { // 必须在外边声明,否则finally语句块中找不到fis FileInputStream fis = null; try { fis = new FileInputStream("Test11.java"); } catch (FileNotFoundException e) { e.printStackTrace(); }finally { // 为了保证资源一定会释放 if(fis != null) { try { fis.close();// 释放资源 }catch(IOException e) {// 捕获异常 e.printStackTrace(); } } } } }
final
&& finalize
&& finally
区别
三者没有任何联系
final
- 修饰的类不能被继承
- 修饰的方法不能被重写Override
- 修饰的实例变量要手动赋值-且不能再修改
- 修饰的静态变量是最终的
finalize
- 是
Object
类中的方法名- 垃圾回收器在回收垃圾之前会调用该方法
- 一般用于在回收前释放资源
- 垃圾回收器在回收垃圾之前会调用该方法
- 是
finally
- 是异常机制中用于声明最终执行的代码块的关键字
finally
语句块中的代码总是执行,无论是否发生异常- 但是在try中如果执行了
System.exit(0)
,那么 jvm 就会退出,这个时候finally
语句块中的代码就不会被执行了。
- 但是在try中如果执行了
自定义异常 &手动抛出异常
-
假设有如下业务:
- 用户在注册的时候要求用户名要超过6位,否则就抛出 IllegalNameException 异常
-
自定义异常
自定义异常既可以定义编译时异常又可以定义运行时异常
- 定义编译时异常直接继承
Exception
- 定义运行时异常直接继承
RuntimeException
- 定义编译时异常直接继承
-
实现
public class Test12 { public static void main(String[] args) { CustomeService cs = new CustomeService(); try { cs.register("jack");// 需要处理异常(要么抛出,要么捕获) // jack 小于 6个字符,所以 /* com.test.exception.IllegalNameException: 用户名长度不能低于6个字符 at com.test.exception.CustomeService.register(Test12.java:27) at com.test.exception.Test12.main(Test12.java:7) */ } catch (IllegalNameException e) { e.printStackTrace(); } } } class IllegalNameException extends Exception{//定义编译时异常 //定义异常一般提供两个构造方法 public IllegalNameException() { } public IllegalNameException(String msg) { super(msg); } } class CustomeService{ public void register(String name) throws IllegalNameException{ if(name.length() < 6) { IllegalNameException e = new IllegalNameException("用户名长度不能低于6个字符"); throw e; //手动抛出异常//这里不使用 try...catch... 处理,因为是要让用户知道该异常,所以需要向上抛出 }else { //程序能执行到这里,证明用户名是合法的 System.out.println("注册成功"); } } }
-
自定义异常
-
可以定义编译时异常或者运行时异常
- 根据异常出现的概率来决定
- 然后就可以确定继承那个类了
- 继承
Exception
是定义编译时异常 - 继承
RuntimeException
是运行时异常
- 继承
-
定义异常的时候一般只要写两个构造方法
public IllegalNameException() {} public IllegalNameException(String msg) { super(msg);// 可以用于打印e.message }
-
自定义的异常一般在服务端都是向上抛出
- throws
-
-
方法的覆盖与异常
定律:重写的方法不能比被重写的方法抛出更宽泛的异常
- 子类无法抛出比父类更多的异常
class A{
public static void m1(){
}
}
class B extends A{
@Override
public static void m1() throws Exception{// 编译无法通过
// 子类不能抛出比父类更多的异常
}
}
-
子类无法抛出比父类更高层次的异常
import java.io.*; class A{ public static void m1() throws FileNotFoundException{ } } class B extends A{ @Override public static void m1() throws IOException{// 编译无法通过 // 子类不能抛出比父类更高层次的异常 } }
import java.io.*; class A{ public static void m1() throws IoException{ } } class B extends A{ @Override public static void m1() throws FileNotFoundException{// 编译可以通过 // 子类可以抛出比父类更低层次的异常 } }
以上