java 异常--读《疯狂java》
发布日期:2021-06-28 23:30:10 浏览次数:2 分类:技术文章

本文共 10586 字,大约阅读时间需要 35 分钟。

java 异常--读《疯狂java》

1.正确关闭资源

public class CloseResource3     {           public static void main(String[] args)                  throws Exception           {                 Wolf w = new Wolf("灰太狼");                 System.out.println("Wolf 对象创建完成~");                 Wolf w2 = null;                 ObjectOutputStream oos = null;                 ObjectInputStream ois = null;                 try                 {                       //创建对象输出流                       oos = new ObjectOutputStream(                             new FileOutputStream("a.bin"));                       //创建对象输入流                       ois = new ObjectInputStream(                             new FileInputStream("a.bin"));                       //序列化输出Java对象                       oos.writeObject(w);                       oos.flush();                       //反序列化恢复Java对象                       w2 = (Wolf)ois.readObject();                 }                 //使用finally块来回收资源                 finally                 {                       if (oos != null)                       {                             try                             {                                   oos.close();                             }                             catch (Exception ex)                             {                                   ex.printStackTrace();                             }                       }                       if (ois != null)                       {                             try                             {                                   ois.close();                             }                             catch (Exception ex)                             {                                   ex.printStackTrace();                             }                       }                 }           }     }

上面程序所示的资源关闭方式才是比较安全的,这种关闭方式主要保证如下3点:

  • 使用finally块来关闭物理资源,保证关闭操作总是会被执行;
  • 关闭每个资源之前首先保证引用该资源的引用变量不为null;
  • 为每个物理资源使用单独try…catch块关闭资源,保证关闭资源时引发的异常不会影响其他资源的关闭

2.finally块的陷阱

public class ExitFinally     {
public static void main(String[] args) throws IOException {
FileOutputStream fos = null; try {
fos = new FileOutputStream("a.bin"); System.out.println("程序打开物理资源!"); System.exit(0); } finally {
//使用finally块关闭资源 if (fos != null) {
try {
fos.close(); } catch (Exception ex) {
ex.printStackTrace(); } } System.out.println("程序关闭了物理资源!"); } } }

不论try块是正常结束,还是中途非正常地退出,finally块确实都会执行。

然而在这个程序中,try语句块根本就没有结束其执行过程,System.exit(0);将停止当前线程和所有其他当场死亡的线程。finally块并不能让已经停止的线程继续执行。

当System.exit(0)被调用时,虚拟机退出前要执行两项清理工作:

  • 执行系统中注册的所有关闭钩子;
  • 如果程序调用了System.runFinalizerOnExit(true);,那么JVM会对所有还未结束的对象调用Finalizer。

第二种方式已经被证明是极度危险的,因此JDK API文档中说明第二个方式已经过时了,因此实际开发中不应该使用这种危险行为。

第一种方式则是一种安全的操作,程序可以将关闭资源的操作注册成为关闭钩子。在JVM退出之前,这些关闭钩子将会被调用,从而保证物理资源被正常关闭。

public class ExitHook     {
public static void main(String[] args) throws IOException {
final FileOutputStream fos; fos = new FileOutputStream("a.bin"); System.out.println("程序打开物理资源!"); //为系统注册关闭钩子 Runtime.getRuntime().addShutdownHook( new Thread() {
public void run() {
//使用关闭钩子来关闭资源 if (fos != null) {
try {
fos.close(); } catch (Exception ex) {
ex.printStackTrace(); } } System.out.println("程序关闭了物理资源!"); } }); //退出程序 System.exit(0); } }

代码为系统注册了一个关闭钩子,关闭钩子负责在程序退出时回收系统资源。运行上面程序,将可以看到系统可以正常关闭物理资源。

返回值

public class FinallyFlowTest2     {
public static void main(String[] args) {
int a = test(); System.out.println(a); } public static int test() {
int count = 5; try {
//因为finally块中包含了return语句 //则下面的return语句不会立即返回 throw new RuntimeException("测试异常"); } finally {
System.out.println("finally块被执行"); return count; } } }

上面程序的try块中抛出了RuntimeException异常,程序并未使用catch块来捕获这个异常。正常情况下,这个异常应该导致test()方法非正常中止,test()方法应该没有返回值。

finally 块 总结

当程序执行try块、catch块时遇到throw语句时,throw语句会导致该方法立即结束,系统执行throw语句时并不会立即抛出异常,而是去寻找该异常处理流程中是否包含finally块。

如果没有finally块,程序立即抛出异常;
如果有finally块,系统立即开始执行finally块——只有当finally块执行完成后,系统才会再次跳回来抛出异常。
如果finally块里使用return语句来结束方法,系统将不会跳回去执行try块、catch块去抛出异常。

catch块的顺序

由于异常处理机制中排在前面的catch(XxxException ex)块总是会优先获得执行的机会,因此Java对try块后的多个catch块的排列顺序是有要求的。

public class CatchSequenceTest     {
public static void main(String[] args) throws Exception {
FileInputStream fis = null; try {
fis = new FileInputStream("a.bin"); fis.read(); } //捕捉IOExcetion异常 catch (IOException ex) {
ex.printStackTrace(); } //捕捉FileNotFoundException异常 catch(FileNotFoundException fex) //① {
fex.printStackTrace(); } finally {
//简单方式关闭资源 if (fis != null) {
fis.close(); } } } }

程序提示①行粗体字代码处有编译错误,编译器不允许程序在18行捕捉FileNotFound Exception异常,

因为Java的异常有非常严格的继承体系,许多异常类之间有严格的父子关系,比如,程序FileNotFoundException异常就是IOException的子类。根据Java继承的特性,子类其实是一种特殊的父类,也就是说,FileNotFoundException只是一种特殊的IOException。程序前面的catch块已经捕捉了IOException,这意味着FileNotFoundException作为子类已经被捕捉过了,因此程序在后面再次试图捕捉FileNotFoundException纯属多此一举。

经过上面分析可以看出,在try块后使用catch块来捕捉多个异常时,程序应该小心多个catch块之前的顺序:捕捉父类异常的catch块都应该排在捕捉子类异常的catch块之后(简称为先处理小异常,再处理大异常),否则将出现编译错误。

上面这条规则和前面介绍的if…else分支语句的处理规则基本相似:if…else分支语句应该先处理范围小的条件,后处理范围大的条件,否则将会导致if…else分支语句后面的分支得不到执行的机会。try…catch语句的处理规则也是如此:try…catch语句的多个catch块应该先捕获子类异常(子类代表的范围较小),后捕获父类异常(父类代表的范围较大),否则编译器会提示编译错误。

由于Exception是所有异常类的根父类,因此try…catch块应该把捕捉Exception的catch块排在所有catch块的最后面。否则,Java运行时将直接进入捕捉Exception的catch块(因为所有异常对象都是Exception或其子类的实例),而排在它后面的catch块将永远也不会获得执行的机会。当然,编译器还是比较智能的,当检测到程序员试图做这样一件“蠢事”时,编译器会直接提示编译错误,阻止这样的代码获得执行。

不要用catch代替流程控制

public class ExceptionFlowTest {       public static void main(String[] args)        {             String[] books =              {                   "疯狂Java讲义",                   "轻量级Java EE企业应用实战",                   "疯狂Ajax讲义"             };             int i = 0;             while(true)             {                   try                   {                         System.out.println(books[i++]);                   }                   catch (IndexOutOfBoundsException ex)                   {                         //结束循环                         break;                   }             }       } }

程序使用“死循环”来遍历数组,当遍历数组产生“数组越界”异常时,会自动捕捉该异常,并跳出死循环。程序看上去没有任何问题,尝试运行该程序,确实可以完全遍历该字符串数组里的每个字符串。

看来这个“别出心裁”的主意还不错,完全可以使用这种方式来遍历数组。问题是,这种做法真的好吗?实际上,这种遍历数组的方式不仅难以阅读,而且运行速度还非常慢

切记:千万不要使用异常来进行流程控制。异常机制不是为流程控制而准备的,异常机制只是为程序的意外情况准备的,因此程序只应该为异常情况使用异常机制。所以,不要使用这种“别出心裁”的方法来遍历数组。

不能用catch 捕获 不可能抛出的异常

public static void test1() {
try {
System.out.println("www.crazyit.org"); } catch (IndexOutOfBoundsException ex) {
ex.printStackTrace(); } } public static void test2() {
try {
System.out.println("www.crazyit.org"); } catch (NullPointerException ex) {
ex.printStackTrace(); } }

IndexOutOfBoundsException、NullPointerException两个异常类都是RuntimeException的子类,因此它们都属于运行时异常

而IOException、ClassNotFound Exception异常则属于Checked异常
如果一个catch子句试图捕获一个类型为XxxException的Checked异常,那么它对应的try子句必须可能抛出XxxException或其子类的异常,否则编译器将提示该程序具有编译错误,
但在所有Checked异常中,Exception是一个异类,无论try块是怎样的代码,catch(Exception ex)总是正确的。

如果一个代码可能抛出某个Checked异常(这段代码调用的某个方法、构造器声明抛出了该Checked异常),那么程序必须处理这个Checked异常。对于Checked异常的处理方式有两种:

  • 当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后在对应的catch块中修复该异常;
  • 前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。

总之,程序使用catch捕捉异常时,其实并不能随心所欲地捕捉所有异常。程序可以在任意想捕捉的地方捕捉RuntimeException异常、Exception,但对于其他Checked异常,只有当try块可能抛出该异常时(try块中调用的某个方法声明抛出了该Checked异常),catch块才能捕捉该Checked异常。

public class DoFixThing2     {
public static void main(String[] args) throws Exception {
test(); } public static void test() throws ClassNotFoundException {
try {
//加载一个类 Class.forName("org.crazyit.learning.Student"); System.out.println("www.crazyit.org"); } finally {
test(); } } }

不可能抛出异常。因为根据finally的执行流程,每当程序试图调用throw语句抛出异常时,程序总会先执行finally块中代码,这就导致程序进行无限递归,即使程序实际已经发生了StackOverflowError错误,依然不会非正常退出

运行上面程序,将看到程序直接进入“挂起”状态程序永远不会结束,甚至会导致整个操作系统的运行都非常缓慢,只有通过强行结束java.exe进程来结束该程序。

这个程序给出的教训是:无论如何不要在finally块中递归调用可能引起异常的方法

因为这将导致该方法的异常不能被正常抛出,甚至StackOverflowError错误也不能中止程序,只能采用强行结束java.exe进程的方式来中止程序的运行。

继承得到的异常

Java语言规定,子类重写父类方法时不能声明抛出比父类方法类型更多、范围更大的异常。也就是说,子类重写父类方法时,子类方法只能声明抛出父类方法所声明抛出的异常的子类。

转载地址:https://blog.csdn.net/ynchyong/article/details/112313417 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:线性表--读《疯狂java》
下一篇:Java 中的陷阱--读《疯狂java》 笔记

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年04月21日 15时54分59秒