一、异常处理类继承关系
1.1、继承关系
1.2、受查异常
对于受查异常,要求编译器在编译的时候检测,而且必须通过try-catch进行处理,否则编译阶段就会报错,典型的首查异常有FileNotFoundException、EOFException。
//捕获首查异常,否则编译时就会报错
public class FileNotFountExceptionTest {
public static void main(String[] args){
File file = new File("D://text.txt");
try {
FileInputStream fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
//错误案例
public class FileNotFountExceptionTest {
public static void main(String[] args){
File file = new File("D://text.txt");
FileInputStream fis = new FileInputStream(file);
}
}
1.3、非受查异常
运行时异常就是非受查异常,也就是说可以通过编译阶段,但是在运行时可能会出现异常,典型的非受查异常有NullPointerException、IndexOutOfBoundsException、ClassNotFound。对于非受查异常我们可以通过捕获处理或者捕获之后又JVM打印出异常信息。
1.4、错误
错误是指程序中不可控的,例如当一个方法申请的栈深度大于JVM规定的栈深度的时候就会抛出StackOverFlowError,而如果需要分配的内存空间不够的时候,就会出现OutOfMemoryError。
StackOverFlowError
package JVM;
/**
* Created by luckyboy on 2018/8/21.
*/
public class StackOverFlowErrorTest {
public static void main(String[] args){
callSelf();
}
private static void callSelf(){
callSelf();
}
}
输出结果
Exception in thread "main" java.lang.StackOverflowError
at JVM.StackOverFlowErrorTest.callSelf(StackOverFlowErrorTest.java:12)
OutOfMemoryError
package JVM;
import java.util.ArrayList;
import java.util.List;
public class OutOfMemoryErrorTest {
public static void main(String[] args){
List list = new ArrayList<>();
for(int i = 0;i<10000000;i++){
byte[] bytes = new byte[1024*1024];
list.add(bytes);
}
System.out.println("堆内存逸出");
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at JVM.OutOfMemoryErrorTest.main(OutOfMemoryErrorTest.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
二、处理非受查异常实现的两种方式
2.1、使用try-catch-finally方式处理异常
try{
//功能:try代码块中放置可能发生异常的代码
//如果在try这个代码块中没有出现异常,那么执行完代码块中的语句后会执行finally中的语句
//如果在try这个代码块中出现异常,异常点之后的代码不会被执行,转到catch代码块中
}catch(Exception e){
//每一个catch代码块用于捕获try代码块中出现的异常,或者是异常的子类
//catch后面的括号定义了异常类型和异常参数。
//如果try代码块中没有异常,那么catch代码块将不会被执行
//如果catch子句抛出了异常,那么
}finally{
//finally块是可选的,如果catch块存在,那么finally块可有可无;finally 主要用于流的关闭和数据库连接关闭操作
//无论异常是否发生,异常块是否被处理,finally都会执行
//一个try块至少要有一个catch块或者是finally块与之对应
}
2.1.1异常处理流程:
当try中发生异常时,将执行控制流从异常发生的地方转移到能够处理这种异常的地方;也就是说当一个函数的某条语句发生异常时,这条语句的下面的语句将不会被执行,执行流会匹配最近的异常处理代码块-catch代码块去执行。异常处理完成以后,执行流会接着在处理了这个异常代码块的后面继续执行,这种模式称之为终结异常处理模式。我们看一个demo
package Exception;
public class NumberFormatExceptionTest {
public static void main(String[] args){
String str = "1e";
stringtoNum(str);
}
public static void stringtoNum(String str){
Integer i = null;
try{
i = Integer.parseInt(str);
System.out.println("转换成功");
}catch(NumberFormatException e){
System.out.println("执行catch处理异常");
}finally{
System.out.println("执行catch处理异常后执行了finally块");
}
System.out.println("catch代码块执行后继续执行了此条命令");
}
}
输出结构
执行catch块处理异常
执行catch处理异常后执行了finally块
catch代码块执行后继续执行了此条命令
我们从这里可以看到当转换异常发生时,代码块中异常点后面的语句没有被执行转而去执行了catch块中的语句,执行完以后去执行了finally块,最后执行代码块外码的语句。
2.1.2局部变量
try、catch、finally代码块中定义的局部变量只能在各自的代码块中使用,这是因为代码块一旦被执行,局部变量就会被JVM回收掉。
2.1.3异常捕获的继承关系和执行关系
每一个catch块处理一个异常,异常匹配的顺序是按照从上往下的方式寻找匹配的异常类,其中只有第一个匹配的catch块会得到执行。类型匹配不仅支持精确匹配,同样也支持父类匹配(父类异常可以匹配子类异常类)。如果一个try块下的多个catch块之间捕获的异常类之间有继承关系,应该将子类异常放在父类异常前面,否则编译时会报错。示例
package Exception;
public class TestException {
public static void main(String[] args){
divid(2,0);
}
public static void divid(int i,int j){
try{
int d = i/j;
}catch(ArithmeticException e){
System.out.println("异常发生捕获了ArithmeticException类");
}catch(Exception e){
System.out.println("异常发生捕获了Exception类");
}
}
}
异常发生捕获了ArithmeticException类
我们知道ArithmeticException是Eexception类的一个子类,我们catch精准的匹配了ArithmeticException之后执行代码块中的代码,后面的catch块不再执行。
2.1.4 finally和return之间的关系
package Exception;
public class FinallyReturnTest {
public static void main(String[] args){
int i = test();
System.out.println(i);
}
public static int test2(){
try{
return 1;
}finally{
System.out.println("finally语句块中的return会覆盖掉try中的return");
return 3;
}
}
}
输出结果
finally语句块中的return会覆盖掉try中的return
从输出结果我们可以看到如果在finally块之前调用了return语句,那么finally语句也是会执行的。
package Exception;
public class FinallyReturnTest {
public static void main(String[] args){
int i = test3(2,1);
System.out.println(i);
}
public static int test3(int a,int b){
try{
return a/b;
}catch(Exception e){
throw e;
}finally{
return 1;
}
}
}
输出结果
1
从结果可以看到finally子句覆盖了catch到的异常,导致最后异常抛出没有显示
2.2异常声明和对捕获的异常重新抛出
如果我们的方法代码中运行时可能会出现异常,如果我们想要将异常交给调用方法处理,那么我们可以在方法使用throws声明异常或者是在catch中重新抛出一个异常。
2.2.1使用printStackTrace()打印异常信息
2.2.2使用throws在方法上声明一个异常
在方法的末尾申明异常类主要是让调用者去处理异常,而使用throw时抛出异常,让
2.2.3使用throw抛出捕获的异常
package Exception;
public class NumberFormatExceptionTest {
public static void main(String[] args){
String str = "1e";
stringtoNum(str);
}
public static void stringtoNum(String str){
Integer i = null;
try{
i = Integer.parseInt(str);
}catch(NumberFormatException e){
throw e;
}finally{
System.out.println("执行catch处理异常后执行了finally块");
}
System.out.println("代码块外main的异常");
}
}
输出结果
执行catch处理异常后执行了finally块
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at Exception.NumberFormatExceptionTest.stringtoNum(NumberFormatExceptionTest.java:15)
at Exception.NumberFormatExceptionTest.main(NumberFormatExceptionTest.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
从上面我们可以看出,如果我们string的格式不对的时候会出现异常,我们使用异常捕获的方式先捕获异常,然后将异常抛出给调用者自己处理。
2.2.4使用异常链重新抛出异常
如果我们捕获一个异常的时候想要抛出一个异常的同时,不能丢失之前异常的信息,那么这个时候我们就要使用异常链的方式将之前的异常信息包含在新抛出的异常之中。
Throwabl这个超类中提供了一个构造器将之前的异常添加进当前的异常,另外一个就是initCause方法传递参数
Throwable(Throwable cause)//传递异常类
public Throwable initCause(Throwable cause)//传递异常类
示例
2.3自定义异常类
如果我们要自定义异常类,那么我们只需要继承Exception这个类,然后写自己的方法;
2.4类继承和异常处理
如果一个子类继承了父类,同时子类覆盖了父类的方法,如果父类声明了异常,那么子类声明的方法中申明的异常只能是父类声明异常的子类,或者是相同的异常,否则就会出现检查异常
下面是正确的案例
package Exception;
import java.io.IOException;
import java.util.NoSuchElementException;
public class LimitException {
}
abstract class Father{
public abstract void throwException()throws Exception;
}
class Son extends Father{
@Override
public void throwException() throws NullPointerException{
}
}
错误的案例
package Exception;
import java.io.IOException;
import java.util.NoSuchElementException;
public class LimitException {
public static void main(String[] args){
Son son = new Son();
}
}
abstract class Father{
public abstract void throwException()throws NullPointerException;
}
class Son extends Father{
@Override
public void throwException() throws Exception{
}
}