回归java7-java进阶-异常处理

异常处理

程序员考虑可能发生的异常,编程时捕获并处理异常,不能让程序发生终止。

异常类继承层次

Throwable - Error(错误) - VirtualMechineError、LinkageError、AWTError…

Throwable - Exception(异常) - RuntimeException(运行时异常)- IndexOutOfBoundsException、DateTimeParseException、NullPointerException、ClassCastExcetption、ArithmeticException…

Exception - IOException(受检查时异常)- FileNotFoundException、EOFException…

Exception - ParseException(受检查时异常)

Trowable类

所有的异常类都直接或间接地继承与java.lang.Throwable类

重要方法:
String getMessage() 获得发生异常的详细消息
void printStackTrace() 打印异常堆栈跟踪信息(堆栈跟踪:方法调用过程的轨迹,包含程序执行过程中方法调用的顺序和所在源代码行号。)
String toString() 获得异常对象的描述

// 只写部分
try {
    return number / divisor;
} catch (Throwable throwable) {
    System.out.println(throwable.getMessage());
}

// throwable对象是系统在程序发生异常时创建

Error和Exception

Trowable有两个直接子类:Error和Exception

Error:程序无法恢复的严重错误,程序员无能为力,只能让程序终止。(JVM内部错误、内存溢出、资源耗尽等)
Exception:程序可以恢复的异常,程序员能掌控的。(除零、空指针访问、网络连接中断、读取不存在的文件)

受检查异常和运行时异常

Exception类可分为受检查异常和运行时异常

受检查异常:除RuntimeException以外的异常类。
编译器会检查这类异常是否进行了处理,即捕获(try - catch)或抛出(方法后声明throws),否则发生编译错误。

运行时异常:继承RuntimeException类的直接或间接子类。
往往是程序员所犯错误导致的。编译器不检查这类异常是否进行了处理(不捕获也不抛出)。一旦运行时异常发生就会导致程序终止。
应该提前预判,防止发生这种异常。(提前判断除数是否为0)

捕获异常

当前方法有能力解决,则捕获异常进行处理;当前方法没有能力解决,则抛给上层调用方法处理(不能解决继续抛),向上传递知道有方法处理它,若都无法则JVM终止程序运行。

try {
    // 可能会发生异常的语句
} catch (Throwable e) {
    // 处理异常e
} catch (Throwable e) {
    // 处理异常e
} ...
// 多catch,一个catch代码块捕获到一个异常时,其他catch就不再进行匹配

当捕获的多个异常类之间存在父子关系时,捕获异常顺序与catch代码块的顺序有关。一般先捕获子类,后捕获父类,否则子类捕获不到。

try - catch可以嵌套:

try {
    // 可能会发生异常的语句
    try {
        // 可能会发生异常的语句
    } catch (Throwable e) {
        // 处理异常e
    }
} catch (Throwable e) {
    // 处理异常e
}

程序执行时内层如果发生异常,首先由内层catch捕获,如果捕获不到则由外层catch捕获。

try - catch还可以嵌套在catch、finally代码块中。
如果能用多catch捕获的异常,尽量不要使用try - catch嵌套。

多重捕获:有些异常虽然种类不同,但捕获后的处理是相同的。

try {
    // 可能会发生异常的语句
} catch (IOException | ParseException e) {
    // 调用方法methodA处理
}

释放资源

有时在try-catch语句中会占用一些非java资源(打开文件、网络连接、打开数据库连接、使用数据结果集…),不能通过JVM垃圾收集器回收,需要程序员释放。
使用finally代码块或java7之后提供的自动资源管理(automatic resource management)技术。

finally代码块

try {
    // 可能会发生异常的语句
} catch (Throwable e1) {
    // 处理异常e
} catch (Throwable e2) {
    // 处理异常e
} finally {
    // 释放资源
}

无论try正常结束还是catch异常结束都会执行finally代码块

public class HelloWorld {
    
    public static void main(String[] args) {
        Date date = readDate();
        System.out.println("读取的日期 = " + date);
    }
    
    public static Date readDate() {
        
        FileInputStream readfile = null;
        InputStreamReader ir = null;
        BufferedReader in = null;
        try {
            readfile = new FileInputStream("readme.txt");
            ir = new InputStreamReader(readfile);
            in = new BufferedReader(ir);
            // 读取文件中第一行的数据
            String str = in.readLine();
            if (str == null) {
                return null;
            }
            
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            Date date = df.parse(str);
            return date;
        } catch (FileNotFoundException e) {
            System.out.println("处理FileNotFoundException");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("处理IOException");
            e.printStackTrace();
        } catch (ParseException e) {
            System.out.println("处理ParseException");
            e.printStackTrace();
        } finally {
            try {
                if (readfile != null) {
                    readfile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (ir != null) {
                    ir.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        return null;
    }
}

FileInputStream、InputStreamReader、BufferedReader是三个输入流,都需要关闭。
这里finally里面的三个try-catch是不能合并的,

finally {
    try {
        if (readfile != null) {
            readfile.close();
        }
        if (ir != null) {
            ir.close();
        }
        if (in != null) {
            in.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

因为,每一个close()方法对应关闭一个资源,如果第一个close方法关闭时发生了异常,那么后面的两个也不会关闭。

自动资源管理

使用finally代码块释放资源会导致程序代码大量增加,一个finally代码块往往比正常执行的程序还要多。
java7后提供的自动资源管理技术可以替代finally代码块,不需要自己关闭资源,释放过程交给JVM。

自动资源管理是在try语句上的扩展:

try (声明或初始化资源语句,语句之间用;分隔) {
    // 可能会发生异常的语句
} catch (Throwable e1) {
    // 处理异常e1
} catch (Throwable e2) {
    // 处理异常e2
}
// 自动资源管理
try ( // 声明或初始化三个输入流
     FileInputStream readfile = new FileInputStream("readme.txt");
     InputStreamReader ir = new InputStreamReader(readfile);
     BufferedReader in = new BufferedReader(ir)) {
    // 读取文件中第一行的数据
    String str = in.readLine();
    if (str == null) {
        return null;
    }

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    Date date = df.parse(str);
    return date;
    
} catch (FileNotFoundException e) {
    System.out.println("处理FileNotFoundException");
    e.printStackTrace();
} catch (IOException e) {
    System.out.println("处理IOException");
    e.printStackTrace();
} catch (ParseException e) {
    System.out.println("处理ParseException");
    e.printStackTrace();
}

throws与声明方法抛出异常

在一个方法中如果能够处理异常,需要捕获并处理。但若没能力处理该异常,需要在方法后声明抛出该异常,通知上层调用者该方法有可能发生异常。

方法中可能抛出的异常(除了Error和RuntimeException及其子类外)都必须通过throws语句列出。

public static void main(String[] args) {
    
    try {
        Date date = readDate();
        System.out.println(date);
    } catch (IOException e) {
    	System.out.println("处理IOException");
    	e.printStackTrace();
	} catch (ParseException e) {
    	System.out.println("处理ParseException");
    	e.printStackTrace();
	}
}


public static Date readDate() throws IOException, ParseException {
    
    // 自动资源管理
    FileInputStream readfile = new FileInputStream("readme.txt");
    InputStreamReader ir = new InputStreamReader(readfile);
    BufferedReader in = new BufferedReader(ir);
    
    String str = in.readLine();
    if (str == null) {
        return null;
    }

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    Date date = df.parse(str);
    return date;
}

在readDate()方法可能发生异常,但其内没有捕获处理,所以需要抛出异常。
FileNotFoundException属于IOException异常。
对于readDate()的调用者main()同样,要么捕获异常自己处理,要么抛给上层调用者。(但main再抛出,真的发生异常时,程序就终止了)

如果声明抛出的多个异常类之间有父子关系,可以只声明抛出父类。但在没有父子关系的情况下,最好明确抛出每一个异常,因为上层调用者会根据这些异常信息进行相应处理。
声明抛出IOException和ParseException两个异常比只声明抛出Exception好。

自定义异常类

实现自定义异常类需要继承Exception或其子类。
自定义运行时异常类,需要继承RuntimeException或其子类。

public class MyException extends Exception {
    
    public MyException() {}
    
    public MyException(String message) {
        super(message);
    }
}

自定义异常类一般需要提供两个构造方法:

  1. 无参数的默认构造方法,异常描述信息是空的;
  2. 字符串构造方法,message是异常描述信息,getMessage()方法可以获得这些信息。

throw与显示抛出异常

throws用于方法后声明抛出异常;
throw用来人工引发异常。

之前接触的异常都是由于系统生成的,当异常发生时,系统会生成一个异常对象,并将其抛出。但也可以通过throw语句显示抛出异常:

throw Throwable或其子类的实例

目的举例:不想将某些异常传给上层调用者,可以捕获之后重新显示抛出另外一种异常给上层调用者。

public static Date readDate() throws MyException {
    
    // 自动资源管理
    try (FileInputStream readfile = new FileInputStream("readme.txt");
         InputStreamReader ir = new InputStreamReader(readfile);
         BufferedReader in = new BufferedReader(ir)) {
    
    	String str = in.readLine();
    	if (str == null) {
        	return null;
    	}

   		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    	Date date = df.parse(str);
    	return date;
    } catch (FileNotFoundException e) {
    	throw new MyException(e.getMessage());
	} catch (IOException e) {
    	throw new MyException(e.getMessage());
	} catch (ParseException e) {
    	System.out.println("处理ParseException");
    	e.printStackTrace();
	}
    return null;
}

throw显示抛出的异常与系统生成并抛出的异常在处理方式上没有区别,就是两种方法;要么捕获自己处理,要么抛给上层调用者。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值