《java编程思想》 第十二章异常处理错误

12.4

之前程序里写日志不清楚怎么把printStackTrace()输出的内容写到日志里,仅仅是写getMessage()信息少了不少。在本节的例子中给出了一个方法:

StringWriter sw = new StringWriter();

PrintWriter pw = new PrintWriter(sw);

e.printStrackTrace(pw);

logger.error(sw.toString());

12.5

可以声明方法将异常抛出,但实际上该方法并不抛出异常。这样做的好处是为异常先占一个位子,以后可以抛出这种异常而不用修改方法声明。

在编译时被强制检查的异常称为被检查的异常。

练习8题中说“抛出练习3里定义的异常”错了,应该是练习4中定义的异常。中有个细节需要注意:声明方法时标识了抛出的异常,即使方法内部并没有真正抛出此异常,调用该方法的时还是要处理异常,否则编译器会报错。

12.6

Exception是与编程有关的所有异常类的基类。它从Throwable类中继承了一些方法:

String getMessage()

String getLocalizedMessage()

获取异常信息和用本地语言表示的异常信息。

void printStackTrace()

void printStackTrace(PrintStream)

void printStackTrace(PrintWriter)

打印信息和调用栈轨迹。第一个版本输出到标准错误流,后两个版本可以选择要输出的流。

Throwable fillInStrackTrace()这个方法的作用是对调用的对象重新填充调用栈,使得调用栈看起来和新创建的异常是相同的。经过编码测试,方法返回的Throwable的对象和调用对象是同一个对象,看来只是改变了调用栈的数据,以下是测试代码:

public class FilInStackTraceTest {

	public void f() throws Exception {
		throw new Exception();
	}
	
	public void g() {
		try {
			f();
		} catch (Exception e) {
			e.printStackTrace();
			Exception e1 = (Exception)e.fillInStackTrace();
			System.out.println(e1 == e);
			e.printStackTrace();
			e1.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		FilInStackTraceTest test = new FilInStackTraceTest();
		test.g();
	}
}

输出结果如下:

java.lang.Exception
    at com.sfauto.exception.FilInStackTraceTest.f(FilInStackTraceTest.java:6)
    at com.sfauto.exception.FilInStackTraceTest.g(FilInStackTraceTest.java:11)
    at com.sfauto.exception.FilInStackTraceTest.main(FilInStackTraceTest.java:23)
java.lang.Exception
    at com.sfauto.exception.FilInStackTraceTest.g(FilInStackTraceTest.java:14)
    at com.sfauto.exception.FilInStackTraceTest.main(FilInStackTraceTest.java:23)
java.lang.Exception
    at com.sfauto.exception.FilInStackTraceTest.g(FilInStackTraceTest.java:14)
    at com.sfauto.exception.FilInStackTraceTest.main(FilInStackTraceTest.java:23)
true

从最后一行可以看出,e与e1是一个对象,调用fillInStackTrace()方法之前和之后e.printStackTrace()的打印结果不同,而调用filllInStackTrace()方法之后e与e1printStackTrace()的打印同。由此可见fillInStackTrace()是改变了调用对象的调用栈。

printStackTrace()方法所提供的信息可以通过getStackTrace()方法来获取,此方法将返回一个数组,每一个元素都表示栈中的一帧。元素0是栈顶元素,是调用序列中的最后一个方法调用(即Throwable被创建和抛出之处,离异常最近的方法),数组中最后一个元素是栈底元素,即调用的最外层方法。

常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。Throwable和它的一些子类提供了带有参数Throwable cause的构造函数来维持异常链。书中说只有Error、Exception和RuntimeException提供这个构造函数,好像说的不对,至少我知道的SQLException和IOException都有这种构造函数。在没有此种构造函数的情况下可以调用initCause()方法来达到相同的效果。

12.7

Throwable这个Java类被用来表示任何可以作为异常被抛出的类。Throwable对象可以分为两种类型:Error用来表示编译时错误和系统错误,除特殊情况外不用关心;Exception是与编程打交道的基本异常类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception异常。所以Java程序员关心的基本类型通常是Exception。

异常的基本概念是用名称代表发生的问题,异常的名称可以望文生义。异常并非全在java.lang包中,还存在于util、net和io包中。

有一些问题属于Java的标准运行时检测的一部分,它们会自动被Java虚拟机抛出,所以不必在方法的异常说明中把它们列出来,这样的异常被称为不受检查的异常(也有叫运行时异常的吧),它们都是RuntimeException的子类。这种异常属于错误,不强制要求手动捕获,可以再自己的代码中抛出这种异常。如果不捕获这种异常,它会穿越所有的执行路径直达main()方法,在主程序退出前将调用异常的printStackTrace()方法。

RuntimeException代表的是编程错误:

(1)无法预料的错误。比如从你的代码控制范围之外传递近来的Null引用;

(2)应该在代码中检查的错误,比如数组越界。

12.8

Java中的异常不允许回到异常抛出的地点,如果想实现这一功能可以把try快放到循环中,这就建立了一个“程序继续执行之前必须要达到”的条件,还可以加入一个static类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数。

当涉及到break和continue语句的时候,finally子句也会得到执行。

当try块中包含return语句,其后的finally块也会被执行。

12.8.3小节中作者演示了两种不恰当的方法使得一些异常被忽略,值得注意

第一种问题的解决方法

12.9

当覆盖方法时,只能抛出在基类方法的异常说明中列出的异常,可以少抛出或不抛出这些异常,也可以抛出这些异常的子类,或者不抛出异常。但是不能添加新的异常。即某个方法的异常说明范围可以变小但是不能变大。

异常限制对构造器不起作用。子类的构造器可以抛出任何新异常。但是因为基类构造器必须以这样或那样的方法被调用,派生类构造函数的异常说明必须包含基类构造函数的异常说明。

派生类构造函数不能捕获基类构造函数抛出的异常。

12.10

对于抛出异常的构造函数,作者认为应该这样处理:在一个单独try-catch语句中构造对象,一旦对象构造成功(即构造函数未抛出异常)用另外的嵌套try-catch-finally语句写其他功能,在finally中记得清理该对象的资源。

12.11

练习25中,子类覆盖了父类的方法并且抛出了比父类方法更窄的异常,当我们创建了一个子类的对象,并将其向上转型为父类,调用该方法编译器会强制要求捕获父类的异常。

12.12

对于一些不知道怎么处理的被检查异常,作者推荐两种办法:

(1)在main()函数中抛出这些异常;

(2)利用异常链,把被检查异常包装成RuntimeException抛出 

    throw new RuntimeException(e);

补充(前两条来自《Java 8编程参考官方教程》,第三条官方教程看不明白,参考了以下博客http://blog.csdn.net/jackiehff/article/details/17839225,之前的例子理解了,最后的论述依旧不是很懂

1.7版本,异常系统添加的三个新特性:

1、带资源的try

这种特性有时被称为自动资源管理(Automatic Resource Management,ARM)

try语句的形式:

try(资源定义和初始化) {

}

在资源定义和初始化语句中声明的变量当try语句块结束时,自动释放资源。只有实现了AutoCloseable接口的资源才能使用带资源的try语句,该接口定义了close()方法,try语句块结束的时候会调用资源的close()方法。

try语句中声明的资源被隐式的声明为fianl,这意味着在创建资源变量后不能将其他变量赋值给该引用。另外,资源的作用域局限于带资源的try语句。

可以再一条try语句中管理多个资源。为此,只需要简单的使用分号分隔每个资源即可。

关闭资源的close()方法也可能抛出异常,使用带资源的try语句时,当try语句块中抛出异常同时close()方法也抛出异常,close()方法抛出的异常会被抑制,但它并没有丢失,而是被添加到第一个异常的抑制列表中,使用Throwable类定义的getSupperessed()方法可以获取抑制异常列表。

2、多重捕获

允许通过相同的catch子句捕获多个异常,使用操作符 | 分隔每个异常。每个多重捕获参数都被隐式的声明为final,因此不能赋予它新的值。正常的捕获并没有final的限制,我想是因为多重捕获形式如下:

catch(IOException | ArrayIndexOutOfBoundsException e ) {

}

如果要在catch语句块中重新赋值,编译器搞不清楚e到底是第一个类型还是第二个类型。

3、重新抛出精确的异常

考虑下面的例子:

static class FirstException extends Exception { }
  static class SecondException extends Exception { }

  public void rethrowException(String exceptionName) throws Exception {
    try {
      if (exceptionName.equals("First")) {
        throw new FirstException();
      } else {
        throw new SecondException();
      }
    } catch (Exception e) {
      throw e;
    }
  }

这个例子的 try 块既可以抛出 FirstException 也可以抛出 SecondException 。假定你想要在rethrowException方法声明中的 throws  子句中指定异常类型为这两种类型,在Java SE 7之前的版本中你不能这么做。因为 catch 子句的异常参数e是 Exception类型,并且 catch块重新抛出这个异常参数 e,所以你只能在rethrowException方法声明中的 throws 子句中指定异常类型为  Exception  。

然而在Java SE 7中, 你可以在rethrowException方法声明中的 throws 子句中指定异常类型为  FirstException 和 SecondException  。 Java SE 7编译器可以探测到由 throw e  语句抛出的异常必须来自于 try 块, 并且 try 块抛出的异常只能是 FirstException 和 SecondException。即使 catch 子句的异常参数e的类型是 Exception,编译器也可以探测到它是 FirstException 还是 SecondException 的实例:

public void rethrowException(String exceptionName)
  throws FirstException, SecondException {
    try {
      // ...
    }
    catch (Exception e) {
      throw e;
    }
  }

如果 catch 块中的 catch 参数被指定给另一个值,那么这种分析失效。然而,如果的 catch 参数被指定给另一个值, 你必须在方法声明的 throws 子句中指定异常类型为 Exception 。

具体说来,在Java SE 7及后续版本中, 当你在一个 catch 子句中声明一个或多个异常类型并且重新抛出由这个 catch 块处理的异常,编译器会验证重新抛出的异常类型是否满足以下条件:

  • try 块可以抛出它。
  • 先前的  catch  块没有办法处理它。
  • 它是  catch  子句其中一个异常参数的子类或者超类。

猜你喜欢

转载自blog.csdn.net/fromatozhappy/article/details/52751792