十二章:异常处理
异常有编译时异常和运行时异常,最好是在编译时期发现,如果是运行时期要将错误信息反馈给使用者;使用异常处理使的代码更健壮。
被检查的异常转换成不被检查的异常(RuntimeException及其子类)
一、概念
使用异常处理的优点:
1、异常机制能捕获错误信息,这样可以在特定的位置填写处理异常的代码,减低代码的复杂度(以前使用标志位信息),不用在什么地方都检查处理异常,也将正常执行的代码和出了问题的代码分离。
2、异常能处理就处理,不能处理就抛出去;
二、基本异常
1、异常情形:
①当前方法或作用域无法继续执行下去,没有足够的条件解决,往上级(也就是调用该方法的位置)抛出异常。
②异常在堆中开辟空间初始化,弹出异常对象的引用,发送给throw,被异常机制接管,如果有捕获并处理(异常处理程序)就能换一种方式继续执行下去,不然将抛出异常。
③Throwable是异常的根类,异常通常通过类型(也就是通俗易懂的名称)处理,当然也可内部携带异常信息。
三、异常捕获
1、监控区域:
将有可能 产生异常的代码放入try{ }中然后进行catch处理或抛出。
2、 catch前面的异常类型不能大于后面的,否则都被前面catch住处理了。
捕获了异常如果处理了,不再抛出新异常,则跳出异常处理程序;当然也不会从异常发生出执行了。
try{
code();
}catch(异常1 e){
处理异常,并结束异常处理机制;
}catch(异常2 e){
处理异常,并结束异常处理机制;
}
3、异常模型
终止模型:异常抛出,无法继续执行;因为不了解具体异常位置,不能对对应问题正确处理,实际常用的是终止模型。
恢复模型:异常处理后继续执行
四、自定义异常
1、自定义异常带反馈信息的异常:
自定义异常构造调用基类传递异常信息,但是一般不用,都是看异常类型,然后出现异常的位置记录出来;
class MyException2 extends Exception {
private int x;
public MyException2() {}
public MyException2(String msg) { super(msg); }
public MyException2(String msg, int x) {
super(msg);
this.x = x;
}
public int val() { return x; }
public String getMessage() {//复写了Throwable的getMessage方法,用于出现什么错误提示
return "Detail Message: "+ x + " "+ super.getMessage();
}
}
public class ExtraFeatures {
public static void f() throws MyException2 {
print("Throwing MyException2 from f()");
throw new MyException2();
}
public static void g() throws MyException2 {
print("Throwing MyException2 from g()");
throw new MyException2("Originated in g()");
}
public static void h() throws MyException2 {
print("Throwing MyException2 from h()");
throw new MyException2("Originated in h()", 47);
}
public static void main(String[] args) {
try {
f();
} catch(MyException2 e) {
//将异常位置和该调用处位置发送给System.out打印到控制台(调用轨迹栈)
e.printStackTrace(System.out);
}
try {
g();
} catch(MyException2 e) {
e.printStackTrace(System.out);
}
try {
h();
} catch(MyException2 e) {
e.printStackTrace(System.out);
System.out.println("e.val() = " + e.val());
}
}
} /* Output:
Throwing MyException2 from f()
MyException2: Detail Message: 0 null
at ExtraFeatures.f(ExtraFeatures.java:22)
at ExtraFeatures.main(ExtraFeatures.java:34)
Throwing MyException2 from g()
MyException2: Detail Message: 0 Originated in g()
at ExtraFeatures.g(ExtraFeatures.java:26)
at ExtraFeatures.main(ExtraFeatures.java:39)
Throwing MyException2 from h()
MyException2: Detail Message: 47 Originated in h()
at ExtraFeatures.h(ExtraFeatures.java:30)
at ExtraFeatures.main(ExtraFeatures.java:44)
e.val() = 47
*///:~
2、日志存储异常信息
import java.util.logging.*;
import java.io.*;
class Oops1 extends Exception {
private static Logger logger = Logger.getLogger("LoggingException");
public Oops1() {
StringWriter trace = new StringWriter();//流对象
printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
}
public class Ex6 {
static void f() throws Oops1, Oops2 {
throw new Oops1();
}
public static void main(String[] args) {
try {
f();
} catch(Exception Oops1) {}
}
}
五、异常说明
1、对于方法中出现的非运行时异常,不能处理需要异常说明,如下格式;
void f() throws ExceptionA, ExceptionB,ExceptionC...{ //异常说明也称异常声明
}
2、对于抽象类和接口的方法申明抛出异常(先占个位置,并没有抛出),子类将能抛出预先设定的异常。
3、编译时被强制检查的异常叫被检查的异常
六、捕获异常
1、Throwable的一些方法的使用
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("My Exception");
} catch(Exception e) {
print("Caught Exception");
print("getMessage():" + e.getMessage());//throwable的展示异常信息
print("getLocalizedMessage():" +e.getLocalizedMessage());//详细信息或本地详细信息
print("toString():" + e);
print("printStackTrace():");
e.printStackTrace(System.out);//打印调用栈轨迹异常信息
}
}
} /* Output:
Caught Exception
getMessage():My Exception
getLocalizedMessage():My Exception
toString():java.lang.Exception: My Exception
printStackTrace():
java.lang.Exception: My Exception
at ExceptionMethods.main(ExceptionMethods.java:8)
*///:~
class ExceptionA extends Exception {
ExceptionA(String msg) { super(msg); }
}
class ExceptionB extends Exception {
ExceptionB(String msg) { super(msg); }
}
public class Ex9100{
public static void f(int x) throws ExceptionA, ExceptionB, ExceptionC {
if(x < 0) throw new ExceptionA("x < 0");
if(x == 0) throw new ExceptionB("x == 0");
}
public static void main(String[] args) {
try {
f(0);//本行发生异常被catch处理了,后面的代码不在执行
f(1);
// will catch any Exception type:
} catch(Exception e) {
print("Caught Exception");
e.printStackTrace(System.out);
}
}
}
输出:
Caught Exception
exceptions.ExceptionB: x == 0
at exceptions.Ex9100.f(Ex9100.java:24)
at exceptions.Ex9100.main(Ex9100.java:29)
3、栈轨迹
printStackTrace栈轨迹可以使用getStackTrace获取栈轨迹信息;返回的是一个由栈帧组成的数组;栈顶也就是角标0是最后一个被调用的方法。
public class WhoCalled {
static void f() {
// Generate an exception to fill in the stack trace
try {
throw new Exception();
} catch (Exception e) {
//栈元素由房发的调用轨迹组成,也是一个栈帧
for(StackTraceElement ste : e.getStackTrace())
System.out.println(ste.getMethodName());
}
}
static void g() { f(); }
static void h() { g(); }
public static void main(String[] args) {
f();
System.out.println("--------------------------------");
g();
System.out.println("--------------------------------");
h();
}
} /* Output:
f
main
--------------------------------
f
g
main
--------------------------------
f
g
h
main
*///:~
4、重新抛出异常
Throwable的fillInStackTrace方法可以更新栈轨迹信息
package exceptions;//: exceptions/Rethrowing.java
// Demonstrating fillInStackTrace()
public class Rethrowing {
public static void f() throws Exception {
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");
}
public static void g() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception)e.fillInStackTrace();//抛出异常,但是更新了异常调用栈信息
}
}
public static void main(String[] args) {
try {
g();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
try {
h();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
}
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.h(Rethrowing.java:20)
at Rethrowing.main(Rethrowing.java:35)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.h(Rethrowing.java:24)
at Rethrowing.main(Rethrowing.java:35)
*///:~
使用外包装个try、catch也有上面的效果:
package exceptions;//: exceptions/RethrowNew.java
// Rethrow a different object from the one that was caught.
class OneException extends Exception {
public OneException(String s) { super(s); }
}
class TwoException extends Exception {
public TwoException(String s) { super(s); }
}
public class RethrowNew {
public static void f() throws OneException {
System.out.println("originating the exception in f()");
throw new OneException("thrown from f()");
}
public static void main(String[] args) {
try {
try {
f();
} catch(OneException e) {
System.out.println(
"Caught in inner try, e.printStackTrace()");
e.printStackTrace(System.out);
throw new TwoException("from inner try");
}
} catch(TwoException e) {
System.out.println(
"Caught in outer try, e.printStackTrace()");
e.printStackTrace(System.out);
}
}
} /* Output:
originating the exception in f()
Caught in inner try, e.printStackTrace()
OneException: thrown from f()
at RethrowNew.f(RethrowNew.java:15)
at RethrowNew.main(RethrowNew.java:20)
Caught in outer try, e.printStackTrace()
TwoException: from inner try
at RethrowNew.main(RethrowNew.java:25)
*///:~
5、异常链
异常链:发生一个异常捕获后又抛出异常,并希望把原始异常信息保存下来。
Throwable及其的子类构造接收一个原始异常cause,这样就可以把原始异常信息携带到新异常中;该子类只有三个:Error(想向ava虚拟机发送异常)、Exception、RuntimeException;其实要连接原始异常需要使用initCause方法而不是构造器。
package exceptions;//: exceptions/DynamicFields.java
// A Class that dynamically adds fields to itself.
// Demonstrates exception chaining.
import static net.mindview.util.Print.*;
class DynamicFieldsException extends Exception {}
public class DynamicFields {
private Object[][] fields;
public DynamicFields(int initialSize) {
fields = new Object[initialSize][2];
for(int i = 0; i < initialSize; i++)
fields[i] = new Object[] { null, null };
}
public String toString() {
StringBuilder result = new StringBuilder();
for(Object[] obj : fields) {
result.append(obj[0]);
result.append(": ");
result.append(obj[1]);
result.append("\n");
}
return result.toString();
}
private int hasField(String id) {
for(int i = 0; i < fields.length; i++)
if(id.equals(fields[i][0]))
return i;
return -1;
}
private int getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField(id);
if(fieldNum == -1)
throw new NoSuchFieldException();
return fieldNum;
}
private int makeField(String id) {
for(int i = 0; i < fields.length; i++)
if(fields[i][0] == null) {
fields[i][0] = id;
return i;
}
// 没了空的 fields. Add one:
Object[][] tmp = new Object[fields.length + 1][2];
for(int i = 0; i < fields.length; i++)
tmp[i] = fields[i];
for(int i = fields.length; i < tmp.length; i++)
tmp[i] = new Object[] { null, null };
fields = tmp;
// 递归使用 fields:
return makeField(id);
}
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
public Object setField(String id, Object value) throws DynamicFieldsException {
if(value == null) {
// 大多数异常没有cause构造,只能使用initCause;在所有Throwable子类中可用。
DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException());
throw dfe;
}
int fieldNumber = hasField(id);
if(fieldNumber == -1)
fieldNumber = makeField(id);
Object result = null;
try {
result = getField(id); // Get old value
} catch(NoSuchFieldException e) {
// 使用构造 "cause":
throw new RuntimeException(e);
}
fields[fieldNumber][1] = value;
return result;
}
public static void main(String[] args) {
DynamicFields df = new DynamicFields(3);
print(df);
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
print(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
print("df: " + df);
print("df.getField(\"d\") : " + df.getField("d"));
Object field = df.setField("d", null); // Exception
} catch(NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch(DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
} /* Output:
null: null
null: null
null: null
d: A value for d
number: 47
number2: 48
df: d: A new value for d
number: 47
number2: 48
number3: 11
df.getField("d") : A new value for d
DynamicFieldsException
at DynamicFields.setField(DynamicFields.java:64)
at DynamicFields.main(DynamicFields.java:94)
Caused by: java.lang.NullPointerException
at DynamicFields.setField(DynamicFields.java:66)
... 1 more
*///:~
七、java异常标准
1、 Throwable:
作为异常抛出的基类,实现Serializable(方便序列化存储和数据传输)。
①Error:继承自Throwable,用作编译时错误和系统错误。
②Excepton:可以被抛出的基本类型,常用的有RuntimeException。
通常用户使用的自定义异常需要望文生义,所以异常通常使用名称就知道是什么问题,而不是通过构造参数。
2、RuntimeException
①是不受检查的异常,属于错误,将被自动被捕获;所以方法声明不需要声明被抛出的RuntimeException及其子类异常
②RuntimeException及其子类可以不受捕获直达mian方法。
③程序出现在执行中处理,可以抛出运行时异常的子类,提示用户。(用于无法预料的程序处理)
public class NeverCaught {
private static int i = 0;
static void f() { //此处没有异常说明
if(i == 1){//程序要求执行取钱只能单词执行,所以需要抛异常通知用户。
throw new RuntimeException("From f()");
}
}
static void g() {
i++;
f();
}
public static void main(String[] args) {
g();
}
} /*:~最后调用printStactrace栈轨迹,打印异常错误调用轨迹
Exception in thread "main" java.lang.RuntimeException: From f()
at exceptions.Cleanup.f(Cleanup.java:8)
at exceptions.Cleanup.g(Cleanup.java:13)
at exceptions.Cleanup.main(Cleanup.java:16)*/
八、finally使用
无论是否发生异常,都将执行的代码,一般用作清理内存、清理垃圾。
1、finally作用:
①关闭资源、清除垃圾、清除内存、关闭网络、关闭文件。
②finally在异常抛出前执行,案例如下:
class FourException extends Exception {}
public class AlwaysFinally {
public static void main(String[] args) {
print("Entering first try block");
try {
print("Entering second try block");
try {
throw new FourException();
} finally {
print("finally in 2nd try block");
}
} catch(FourException e) {
System.out.println(
"Caught FourException in 1st try block");
} finally {
System.out.println("finally in 1st try block");
}
}
} /* Output:
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block
*///:~
2、return的使用
return结束方法,结束前一定调用finally代码块。
//: exceptions/MultipleReturns.java
import static net.mindview.util.Print.*;
public class MultipleReturns {
public static void f(int i) {
print("Initialization that requires cleanup");
try {
print("Point 1");
if(i == 1) return;
print("Point 2");
if(i == 2) return;
print("End");
return;
} finally {
print("Performing cleanup");
}
}
public static void main(String[] args) {
for(int i = 1; i <= 3; i++)
f(i);
}
} /* Output:
Initialization that requires cleanup
Point 1
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
End
Performing cleanup
*///:~
3、异常丢失
由于finally在异常之前抛出执行,所以导致了异常丢失。
public class ExceptionSilencer {
public static void main(String[] args) {
try {
throw new RuntimeException("异常不发生");
} finally {
// 结束了方法,将不会抛出任何异常
return;
// throw new RuntimeException("异常发生了。。。"); 或者这样也会导致异常丢失
}
}
} ///:~
九、异常限制
子类覆盖基类方法,只能抛出基类声明列出的异常,是为了让基类代码应用到了派生类也能使用。
1、方法上声明异常,并没有抛出,用来强制派生类进行捕获,但是不会被捕获到(因为未抛出)。
2、基类构造器申明了异常,子类构造器必须申明该异常(不能抛出该异常的子类),并可以抛出新异常。(不收异常限制)
3、对于同时实现或并继承出现覆盖相同名称方法时,抛出多种异常,实现类该方法不抛异常,或者抛出该综合异常的子类。
(基类应用派生类该方法出现该异常或该异常的子类都能被捕获到处理,不然代码将出现问题,或者不抛异常处理也行)
4、如果基类未抛出异常,派生类不能抛异常。
(如果基类方法调用了派生类该方法产生了异常需要出,而基类却不需要处理,这就出问题了)
package exceptions;//: exceptions/StormyInning.java
// Overridden methods may throw only the exceptions
// specified in their base-class versions, or exceptions
// derived from the base-class exceptions.
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
//声明了异常但是没有抛出,强制派生类去捕获异常,但是却捕获不到
public Inning() throws BaseballException {}
public void event() throws BaseballException {
//这里其实可以不抛出任何异常
}
//声明了异常但是没有抛出,强制派生类去捕获异常,不然报错
public abstract void atBat() throws Strike, Foul;
public void walk() {} //不引发任何检查异常
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class RainedOut1 extends RainedOut {}
class PopFoul extends Foul {}
interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm {
//构造器可以添加新的异常,但是您必须处理基本构造函数异常:
public StormyInning() throws RainedOut, BaseballException {}
public StormyInning(String s) throws Foul, BaseballException {}
// 常规方法必须符合Inning基类,基类未抛异常派生类就不能抛异常:
//! void walk() throws PopFoul {} //Compile error
// 接口Storm不能向Inning基类中的现有方法添加新异常,所有如下会报错,只能不抛异常
//! public void event() throws RainedOut {}
// 如果该方法在Inning基类中尚不存在,这个异常则可以;抛出throws RainedOut,RainedOut1或只抛RainedOut1也是可以的
public void rainHard() throws RainedOut {}
// 您可以选择不引发任何异常,即使Inning基本版本可以:
public void event() {}
// 重写的方法可以抛出派生类的异常:
public void atBat() throws PopFoul {}
public static void main(String[] args) {
try {
//声明了异常但是没有抛出,强制派生类去捕获异常,不然报错
StormyInning si = new StormyInning();
si.atBat();
} catch(PopFoul e) {
System.out.println("Pop foul");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
// Strike not thrown in derived version.
try {
// What happens if you upcast?
Inning i = new StormyInning();
i.atBat();
// 你必须catch了exceptions根据Inning基类
} catch(Strike e) {
System.out.println("Strike");
} catch(Foul e) {
System.out.println("Foul");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
}
} ///:~
十、构造器
构造器有可能出现异常,为了保证资源关闭,在创建对象时进行try、catch,构造成功了后续异常需要关闭资源调用finally。
package exceptions;//: exceptions/InputFile.java
// Paying attention to exceptions in constructors.
import java.io.*;
public class InputFile {
private BufferedReader in;
public InputFile(String fname) throws Exception {
try {
in = new BufferedReader(new FileReader(fname));
// Other code that might throw exceptions
} catch(FileNotFoundException e) {
System.out.println("Could not open " + fname);
// Wasn't open, so don't close it
throw e;
} catch(Exception e) {
// All other exceptions must close it
try {
in.close();
} catch(IOException e2) {
System.out.println("in.close() unsuccessful");
}
throw e; // Rethrow
} finally {
// 不能再这关闭,需要使用,最后才关闭
}
}
public String getLine() {
String s;
try {
s = in.readLine();
} catch(IOException e) {
//异常能处理一部分在抛新异常或直接抛出
throw new RuntimeException("readLine() failed");
}
return s;
}
public void dispose() {
try {
in.close();
System.out.println("dispose() successful");
} catch(IOException e2) {
throw new RuntimeException("in.close() failed");
}
}
} ///:~
// ======================================================
public class Cleanup {
public static void main(String[] args) {
try {
InputFile in = new InputFile("Cleanup.java");
try {
String s;
int i = 1;
while((s = in.getLine()) != null)
; // Perform line-by-line processing here...
} catch(Exception e) {
System.out.println("Caught Exception in main");
e.printStackTrace(System.out);
} finally {
in.dispose();
}
} catch(Exception e) {
System.out.println("InputFile construction failed");
}
}
} /* Output:
dispose() successful
*///:~
十一、其他可选方式
1、异常处理的初衷是为了方便程序员处理错误。
2、能处理的异常才捕获处理(或者处理部分再次抛出新异常,一般是RuntimeException(e)),不能处理就抛出,不要丢失了异常
总结
1、Throwable是所有异常的根类,Error编译时或系统错误,RuntimeException运行时异常
2、基类方法上可以申明多个异常,而不抛出。
3、对于可能产生异常的地方try包住,catch捕获异常时子类异常放前面;处理后不继续在异常发生处执行,捕获后不进入该try的后续catch。
4、异常链信息可以通过Throwable、Error、Exceptional、RuntimeException的构造携带(底层用的fillInStackTrace),其他类型通过initCause携带。
5、基类抛出异常,派生类可以抛该异常或异常的子类,也可以不抛任何异常。如果基类未抛任何异常,子类也不能抛异常。
6、异常能处理就处理,不然抛出或转换成不检查的异常。