Java异常处理一

Java异常处理一

  1. Java 的基本原理就是“形式错误的代码不会运行”。(编译错误=”形式错误的代码”)
  2. 捕获错误最理想的是在编译期间,最好在试图运行程序以前。然而,并非所有错误都能在编译期间侦测到。有些问题必须在运行期间解决,让错误的缔结者通过一些手续向接收者传递一些适当的信息,使其知道该如何正确地处理遇到的问题。

    摘自 thinking in java 4th

异常基本介绍

“违例”(Exception)表示”例外”情况。当一个Exception产生时,要么解决这个问题(catch),要么将与错误有关的信息发送到更高一级的处理(throw),在更恰当的地方解决。

违例的构造

和 Java 的其他任何对象一样,需要用 new 在内存堆里创建违例,并需调用一个构建器。在所有标准违例中,存在着两个构建器:第一个是默认构建器,第二个则需使用一个字串自变量,使我们能在违例里置入相关信息。我们可根据需要掷出任何类型的“可掷”对象。通常情况下,我们要为每种不同类型的错误“掷”出一类不同的违例。我们的思路是在违例对象以及挑选的违例对象类型中保存信息。(通常,唯一的信息是违例对象的类型,而违例对象中保存的没什么意义)。

违例的捕获

“警戒区”代表一个特殊的代码区域,有可能产生违例,并在后面跟随用于控制那些违例的代码。

try 块

1
2
3
try{
// 可能产生Exception的代码
}

违例控制(catch)

生成的Exception 必须在某处中止(违例控制器 => catch)。针对想捕获的每种违例类型,都必须有一个相应的违例控制器。违例控制器紧接在 try 块后面,且用 catch(捕获)关键字标记。

1
2
3
4
5
try{
// 可能产生异常的代码
}catch(type1 id1){// 处理类型为type1的Exception
}catch(type2 id2){// 处理类型为type2的
}

若“掷”出一个违例,违例控制机制就会搜寻自变量与违例类型相符的第一个控制器。随后,它会进入那个 catch 从句,并认为违例已得到控制(一旦 catch 从句结束,对控制器的搜索也会停止)。只有相符的 catch 从句才会得到执行。

注意:一个衍生异常类对象可与基础异常类的一个控制器相配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//异常匹配
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

public class Human {
public static void main(String[] args) {
try {
throw new Sneeze();
} catch(Annoyance a) {
System.out.println("Caught Annoyance");
} catch(Sneeze s) {
System.out.println("Caught Sneeze");
}
}
}
//---------------------------------------------------------------------------------
//output:
//Caught Annoyance

在违例控制理论中,共存在两种基本方法。

  • 在“中断”方法中,我们假定错误非常关键,没有办法返回违例发生的地方。无论谁只要“掷”出一个违例,就表明没有办法补救错误,而且也不希望再回来。
  • 另一种方法叫作“恢复”。它意味着违例控制器有责任来纠正当前的状况,然后取得出错的方法,假定下一次会成功执行。若使用恢复,意味着在违例得到控制以后仍然想继续执行。

违例规范

方法声明时在自变量列表的后面,加上“违例规范”。

1
2
void f() throws Exception1,Exception2 { }
//throws 后面跟随全部潜在的违例类型。

捕获所有违例

1
catch(Exception e){ }

Exception 类是常见违例的基础类,所以实际使用时,应该将其放在控制器列表的末尾,防止其他控制器失效。
捕获到Exception 后可以调用:

  • 获得详细消息 getMessage()
  • 获得调用堆栈路径 printStackTrace()

重新抛出违例

  • 重新抛出捕获的异常
1
2
3
catch(Exception e){
throw e; //重新抛出捕获的句柄
}

若只是简单地重新掷出当前违例,在调用printStackTrace()方法时,会输出违例真正的产生位置,而不是重新掷出的位置。若想更新堆栈信息(将当前的堆栈信息填充到原来的违例对象里),可以调用fillInStackTrace()方法.

1
2
3
catch(Exception e){
throw e.fillInStackTrace();
}
  • 从捕获的违例中重新抛出一个不同的违例

这样做,会得到与使用fillInStackTrace()类似的效果:与违例起源地有关的信息会全部丢失,我们留下的是与新的 throw 有关的信息。

标准Java 违例

Java 异常

在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

  • Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。
    大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。。 这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。

  • Exception(异常):是程序本身可以处理的异常。
    Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、 ArithmeticException)和 ArrayIndexOutOfBoundException。

注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

Exception

Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。

  • 运行时异常:
    都是RuntimeException类及其子类异常,如NullPointerException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

  • 非运行时异常 (编译异常):
    是 RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

自定义违例

自定义异常Example

1
2
3
4
5
6
7
8
9
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg);
}
}

class SimpleException extends Exception {
}

继承中的违例限制

  • 基类方法没有抛出异常,衍生类覆盖的方法也不能抛出异常。
  • 若基类方法抛出异常,衍生类方法可不抛出异常,或抛出同样的异常(基类抛出的异常及其子类)
  • 衍生类的构造器可以增加抛出的异常种类,但必须处理基类构造器的异常。注意:衍生类上溯造型到基础类型时,编译器就会强迫我们捕获针对基础类的违例。

“违例规范接口”可能在继承和覆盖时变得更“窄”,但它不会变得更“宽”——这与继承时的类接口规则(衍生类方法是对基类接口的扩展)是正好相反的。

摘自 thinking in java

finally

try-catch语句还可以包括第三部分,就是finally子句。它表示无论是否出现异常,都应当执行的内容。try-catch-finally语句的一般语法形式为:

1
2
3
4
5
6
7
8
9
try {  
// 可能会发生异常的程序代码
} catch (Type1 id1) {
// 捕获并处理try抛出的异常类型Type1
} catch (Type2 id2) {
// 捕获并处理try抛出的异常类型Type2
} finally {
// 无论是否发生异常,都将执行的语句块
}

小结:

  • try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
  • catch 块:用于处理try捕获到的异常。
  • finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。

在以下3种特殊情况下,finally块不会被执行完:

  • JVM 过早终止(调用 System.exit(int));
  • 在 finally 块中抛出一个未处理的异常;
  • 计算机断电、失火、或遭遇病毒攻击。

try、catch、finally语句块的执行顺序

  • 当try没有捕获到异常时,try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
  • 当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
  • 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到 catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句 也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;

finaly 带来的问题

try -finally 中的丢失

  • 返回值的覆盖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class testfinally {

private String test(){
try{
System.out.println("try starts...");
return "success";
}
finally{
System.out.println("finally starts...");
return "false";
}
}
public static void main(String[] argvs){
String result = new testfinally().test();
System.out.println("result is: "+result);
}
}
//----------------------------------------------------------------------------
//output:
//try starts...
//finally starts...
//result is: false
  • 返回值的副本操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class testfinally {
private String test(){
String flag = "";
try{
System.out.println("try starts...");
flag = "success";
return flag;
}
finally{
System.out.println("finally starts...");
flag = flag + "-> return after finally";
System.out.println("flag is: "+flag);
}
}

public static void main(String[] argvs){
String result = new testfinally().test();
System.out.println("result is: "+result);
}
}
//------------------------------------------------------------------------------------------------
//output:
//try starts...
//finally starts...
//flag is: success-> return after finally
//result is: success

说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int inc(){
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x =2;
return x;
} finally{
x = 3;
// System.out.println("x:" + x);
// return x;
}
}

这是《深入理解Java虚拟机-JVM高级特性与最佳实践》第二版书中的例子(P187~P188)。出现这种情况的原因是:在没有出线异常的情况下,先 执行了x=1;然后执行return x;时,首先是将x的一个副本保存在本地变量表中,执行return之前必须执行finally中的操作:x=3;将x的值设置为了3,但是return 时是将本地变量表中保存的x的那个副本拿出来放到栈顶返回。故没出异常时,返回值为1;出Exception异常或其子类异常时,返回值是2;如果出现非 Exception异常,则执行完x=3之后,抛出异常,没有返回值。

catch -finally 中的丢失

  • 抛出异常覆盖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class testfinally {

private String test() throws Exception {
String flag = "";
try{
System.out.println("try starts...");
throw new Exception();
} catch (Exception e) {
System.out.println("catch starts...");
throw e;
}
finally{
System.out.println("finally starts...");
flag = "-> return in finally";
return flag;
}
}

public static void main(String[] argvs){
try {
String result = new testfinally().test();
System.out.println("result is: "+result);
}catch(Exception e){
e.printStackTrace();
}

}
}
//--------------------------------------------------------------------------------------------------
//output:
//try starts...
//catch starts...
//finally starts...
//result is: -> return in finally

public class testfinally {

private String test() throws Exception {
String flag = "";
try{
System.out.println("try starts...");
throw new Exception("from try");
} catch (Exception e) {
System.out.println("catch starts...");
throw e;
}
finally{
System.out.println("finally starts...");
throw new Exception("from finally");
}
}

public static void main(String[] argvs){
try {
String result = new testfinally().test();
System.out.println("result is: "+result);
}catch(Exception e){
e.printStackTrace();
}
}
}
//---------------------------------------------------------------------------
//output:
//try starts...
//catch starts...
//finally starts...
//java.lang.Exception: from finally
//at mytest1.testfinally.test(testfinally.java:16)
//at mytest1.testfinally.main(testfinally.java:22)
  • 返回值的副本操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class FinallyTest {  

public static int method(){
int a=1;
try{
throw new Exception();
}catch(Exception e){
System.out.println("catch:a="+a);
return a;
}finally{
a=2;
System.out.println("finally:a="+a);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(method());
}

}
//---------------------------------------------------------------------------------------------
//output:
//catch:a=1
//finally:a=2
//1
-------------本文结束感谢您的阅读-------------
坚持分享,您的支持将鼓励我继续创作!
0%