JAVA编程思想学习总结:第十二章通过异常处理错误

(1)认识异常

class MyException extends Exception{
	public MyException(){}
	public MyException(String msg){super(msg);}
}
public class FullConstructors {
	public static void f() throws MyException{
		System.out.println("Throwing MyException from f()");
		throw new MyException();
	}
	public static void g() throws MyException{
		System.out.println("Throwing MyException from g()");
		throw new MyException("Originated in g()");
	}
	public static void main(String[] args){
		try{
			f();
		}catch(MyException e){
			e.printStackTrace(System.out);
		}
		try{
			g();
		}catch(MyException e){
			e.printStackTrace(System.out);
		}
	}
}

通过继承Exception对象或其子类,可以实现自己定义的异常类,最好选择意思相近的异常类继承。

本例的结果被打印到了控制台上,本书的输出显示正是在控制台上自动地捕获和测试这些结果的。但是,通过写入System.err而将错误发送给标准错误流,也许会更好,因为System.out会被重定向,而System.err不会,这样更容易被用户注意到。

使用java.util.logging工具将输出记录到日志中也是一种方法。

/*
 * P253
 */
import java.util.logging.Logger;
import java.io.*;

class LoggingException extends Exception{
	private static Logger logger=Logger.getLogger("LoggingException");
	public LoggingException(){
		StringWriter trace=new StringWriter();
		printStackTrace(new PrintWriter(trace));
		logger.severe(trace.toString());
	}
}
public class LoggingExceptions {
	public static void main(String[] args){
		try{
			throw new LoggingException();
		}catch(LoggingException e){
			System.err.println("Caught "+e);
		}
		try{
			throw new LoggingException();
		}catch(LoggingException e){
			System.err.println("Caught " +e);
		}
	}
}

要认识到,异常也是对象的一种,所以可以继续修改这个异常类,以得到更强的功能。但要记住,使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型,其他的就不管了(大多数java库里的异常都是这么用的,所以对异常所添加的其他功能也许根本用不上。)

(2)捕获异常

/*
 * P258-259
 */
public class Rethrowing {
	public static void f() throws Exception{
		System.out.println("Origination 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.prinStackTrace()");
			e.printStackTrace(System.out);
			throw e;
		}
	}
	public static void h() throws Exception{
		try{
			f();
		}catch(Exception e){
			System.out.println("Inside h(),e.prinStackTrace()");
			e.printStackTrace(System.out);
			throw (Exception)e.fillInStackTrace();
		}
	}
	public static void main(String[] args){
		try{
			g();
		}catch(Exception e){
			System.out.println("main: getStackTrace()");
			for(StackTraceElement ste: e.getStackTrace())
				System.out.println(ste.getMethodName());
		}
		try{
			h();
		}catch(Exception e){
			System.out.println("main: printStackTrace()");
			e.printStackTrace(System.out);
		}
	}
}
在本例中,通过调用getStackTrace()方法来直接访问printStackTrace()方法。这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每个元素都表示栈中的一桢。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable)被创建和抛出之处。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。
如果只是把当前异常对象重新抛出,那么printStackTrack()方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fillInstackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。
永远不必为清理前一人异常对象而担心,或者说为异常对象的清理而担心。它们都是用new在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。
在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。现在所有Throwable的子类在构造器中都可以接受一个cause(因由)对象作为参数来把原始异常传递给新异常,来构成异常链。在Throwable的子类中,只有三种基本的异常类提供了带参数的构造器。它们是Error,Exception以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法面不是构造器。
/*
 * P260-262
 */
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;
			}
		}
		//no empty 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;
		//Recursive claa with expanded 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){
			//Most exceptions don't have a "cause" constructor.
			//In these cases you must use initCause(),
			//available in all Throwable subclasses.
			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){
			// Use constructor that takes "cause":
			throw new RuntimeException(e);
		}
		fields[fieldNumber][1]=value;
		return result;
	}
	public static void main(String[] args){
		DynamicFields df=new DynamicFields(3);
		System.out.println(df);
		try{
			df.setField("d", "A value for d");
			df.setField("number", 47);
			df.setField("number2", 48);
			System.out.println(df);
			df.setField("d", "A new value for d");
			df.setField("number", 11);
			System.out.println("df: "+df);
			System.out.println("df.getField(\"d\") : "+ df.getFieldNumber("d"));
			Object fieldObject=df.setField("d", null);//Exception
		}catch(NoSuchFieldException e){
			e.printStackTrace(System.out);
		}catch(DynamicFieldsException e){
			e.printStackTrace(System.out);
		}
	}
}

(3)使用finally

finally可以保证无论是否有异常抛出,无论在何处返回函数,finally块中的代码总能得到执行。
对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要。它能使程序员保证:无论try块里发生了什么,内存总能得到释放。
但是java有垃圾回收机制,java在什么情况下才需要用到finally呢?当要把除内存之外的资源恢复到它们的初始状态时,需要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关。
public class AlwaysFinally {
	public static void main(String[] argw){
		System.out.println("Entering first try block");
		try{
			System.out.println("Entering second try block");
			try{
				throw new FourException();
			}finally{
				System.out.println("finally in 2 nd try block");
			}
		}catch(FourException e){
			System.out.println("Cayght FourException in 1st try block");
			return ;
		}finally{
			System.out.println("finally in 1st try block");
		}
	}
}
运行结果:
Entering first try block
Entering second try block
finally in 2 nd try block
Cayght FourException in 1st try block
finally in 1st try block
由本例可以看出,如果异常没有得到处理,虽然finally子句会得到执行,但异常会继续向更高一层的异常处理程序抛出。
而即使在catch语句中,已经有了return 语句,之后的finally还是会得到执行。
当涉及break和continue语句的时候,finally子句还是会得到执行,把finally和break及continue配合使用,在Java里就没必要使用goto语句了。
但是,在使用finally语句的过程中,会出现异常丢失的情况:1)异常还未得到处理,进入finally抛出新的异常,原异常被新的异常取代。2)在异常还未得到处理时,进入finally并直接返回,原异常直接丢失。但实际上,异常往往会以比这两种情况更微妙更难以察觉的方式完全丢失。

(4)异常的限制

当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,尖基类使用的代码应用到其派生类对象的时候,一样能够工作。
/*
 * P269-271
 */
class BaseballException extends Exception{}
class Foul extends BaseballException{}
class Strike extends BaseballException{}
abstract class Inning{
	public Inning() throws BaseballException{}
	public void event()throws BaseballException{
		//Doesn't actually have to throw anything
	}
	public abstract void atBat() throws Strike,Foul;
	public void walk(){}//Throws no checked exceptions
}
class StormException extends Exception{}
class RainedOut extends StormException{}
class PopFoul extends Foul{}
interface Storm{
	public void event()throws RainedOut;
	public void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm{
	//OK to add new exception for coustructors ,but you 
	//must deal with the base constructor exceptions:
	public StormyInning() throws RainedOut,BaseballException{}
	public StormyInning(String s)throws Foul,BaseballException{}
	//Regular methods must conform to base class;
	//void walk() throws PopFoul()//compile Erroe
	//Interface cannot add exceptions to existing 
	//methods from the base class:
	//public void event() throws RainedOut{}
	//public void event() throws BaseballException{}
	//You can choose to not throw any exceptions,
	//even if th e base version does:
	public void event(){};//event()实际也无法抛出任何异常,因为无论抛出何种类型的异常,要不与基类冲突,要不与接口冲突
	//if the method doesn't already exist in the 
	//base class,the exception is ok;
	public void rainHard() throws RainedOut{}
	//Overridden methods can throw inhrited exceptions:
	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();
			//You must catch the exceptions from the 
			//base-class version of the method;
		}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");
		}
	}
}
从这个例子可以看出,一个出现在基类方法的异常说明中异常,不一定出现在派生类方法的异常说明里,但是,一般情况下,出现在派生类方法异常说明里的异常,一定是在基类方法中已说明的异常。这点同继承的规则明显不同。在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中某个特定方法的“异常说明的接口”不是变大了而是变小了,这恰好和类接口继承时的情形相反。
不过异常限制对构造器不起作用。构造器可以抛出任何异常,不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这时的默认构造器将自动被调用)。派生类构造器异常说明必须包含基类构造器的异常说明。
派生类构造器不能捕获基类构造器抛出的异常。
最后一个值得注意的地方是转型。在例中可以看到,如果处理的刚好是StormyInning对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会(正确地)要求你捕获基类异常。所有这些限制都是为了能产生更为强壮的异常处理代码。
//Guaranteeing proper cleanup of a resource
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{
			//Don't close it here!!!
			//由于finally会在每次完成构造器之后都执行一遍,因此此处不该调用close()关闭文件的地方。我们希望文件在InputFile对象的整个生命周期内都处于打开状态。
		}
	}
	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 e){
			throw new RuntimeException("in.close() failed");
		}
	}
	
}

/*
 * P273
 */
public class Cleanup {
	public static void main(String[] args){
		try{
			InputFile in=new InputFile("src/exceptions/Cleanup.java");
			try{
				String s;
				int i=1;
				while((s=in.getLine())!=null)	System.out.println(""+s);
			}catch(Exception e){
				System.out.println("Caught Exception in main");
				e.printStackTrace(System.out);
			}finally{
				in.dispose();
				//System.out.println("the secone dipose,what will happen?");
				//in.dispose();
				//在此处多调用了一次in.dispose(),程序并未发生错误。
				//此处写此代码的原因是为了测试程序可能在InputFile方法中已经调用一次close方法了,如果再调用一次colse()方法程序是否后报异常
			}
		}catch(Exception e){
			System.out.println("InputFile construction failed");
		}
	}
}

以上两段代码展示了在发生异常后正常清理其它资源的方式。执行清理的finally与内部的try语句块相关联。在这种方式中,finally子句在构造失败时是不会执行的,而在构成成功时将总是执行。其基本规则是:在创建需要清理的对象之后,立即进入一个try-finally语句块。尽力创建不能失败的构造器(虽然这么做并非总是可行)。

(5)异常匹配

抛出异常的时候,异常处理系统会按照代码书写顺序找出最近的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。所以,应该尽量将派生类异常处理程序写有前面,而将基类异常程序写在后面,避免基类异常程序处理掉它不该处理的派生类异常。

(6)其它可选方式

“被检查的异常”使这个问题变得有些复杂。因为它们强制你在可能还没准备好处理错误的时候被迫加上catch子句,这就导致了吞食则有害(harmful if swallowed)问题。
程序员们只做最简单的事情,常常是在无意中吞食了异常,然而一旦这么做,虽然能通过编译,但除非你记得复查并改正代码,否则异常将会丢失。
异常说明可能有两种意思:一个是“我的代码会产生异常,这由你来处理”。另一个是“我的代码忽略了这些异常,这由你来处理”。学习异常处理机制和语法的时候,我们一直在关注你来处理部分,但这里特别值得注意的事实是,我们通常都忽略了异常说明所表达的完整含义。
“被检查的异常”和强静态类型检查对于开发健壮的程序曾被认为是非常有必要的。但实际这些好处来自于:
1)不在于编译器是否会强制程序员去处理错误,而是要有一致的。使用异常报告错误的模型。
2)不在于什么时候进行检查,而是一定要有类型检查,也就是说,必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,反倒没有关系。
一种有效的方法是把“被检查的异常”包装进RuntimeException里面。这种技巧给你一种选择,你可以不写try-catch子句和/或异常说明,直接忽略异常,让它自己沿着调用栈往上冒泡。同时,还可以用getCause()捕获并处理特定的异常。
import java.io.*;
class WrapCheckedException{
	void throwRuntimeException(int type){
		try{
			switch(type){
				case 0:throw new FileNotFoundException();
				case 1:throw new IOException();
				case 2:throw new RuntimeException("Where am I?");
				default:return;
			}
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
}
class SomeOtherException extends Exception{}
public class TurnOffChecking {
	public static void main(String[] args){
		WrapCheckedException wce=new WrapCheckedException();
		//You can call throw RuntimeException() without a try block,
		//and let RuntimeExceptions leave the method;
		wce.throwRuntimeException(3);
		// Or you can choose to catch exception;
		for(int i=0;i<=3;i++){
			try{
				if(i<3)
					wce.throwRuntimeException(i);
				else throw new SomeOtherException();
			}catch(SomeOtherException e){
				System.out.println("SomeOtherException e"+ e);
			}catch(RuntimeException re){
				try{
					throw re.getCause();
				}catch(FileNotFoundException e){
					System.out.println("FileNotFoundException :"+e);
				}catch(IOException e){
					System.out.println("IOException:"+e);
				}catch(Throwable e){
					System.out.println("Throwable: "+e);
				}
			}
		}
	}
}

(7)异常使用指南

1)在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
2)解决问题并且重新调用产生异常的方法。
3)进行少许修补,然后绕过异常发生的地方继续执行。
4)用别的数据进行计算,以代替方法预计会返回的值。
5)把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
6)把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
7)终止程序。
8)进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
9)让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值