Java语法与架构中的异常处理(assert断言、堆栈追踪)

程序中总有些意想不到的情况所引起的错误,Java中的错误也以对象方式呈现为java.lang. Throwable的各类子类实例。只要你能捕捉包装错误的对象,就能够针对该错误作一些处理,例如,试恢复正常流程、进行日志( Logging)记录、以某种形式提醒用户等java

一、使用try、catch 以下小程序,用户能够连续输入整数最后输入0结束后会显示输入数的平均值sql

package errorDemo;
import java.util.*;

public class Average {
	public static void main(String[] args) {
		Scanner scan=new Scanner(System.in);
		double sum = 0;
		int i = 0;
		while(true) {
			int num=scan.nextInt();
			if(num==0) {
				break;
			}
			sum+=num;
			i++;
		}
		System.out.printf("%.1f%n",sum/i);
	}
}

若是用户不当心输入错误,例如第二个数输入为o,而不是0:数据库

7
o
Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Unknown Source)
	at java.base/java.util.Scanner.next(Unknown Source)
	at java.base/java.util.Scanner.nextInt(Unknown Source)
	at java.base/java.util.Scanner.nextInt(Unknown Source)
	at errorDemo.Average.main(Average.java:10)

这段错误信息对除错是颇有价值的,错误信息的第一行:Exception in thread "main" java.util.InputMismatchException Scanner对象的nextInt()方法,能够将用户输入的下一个字符串剖析为int值,出现InputMismatchException错误信息表示不符合 Scanner对象预期,由于下一个字符串自己要表明数字。 Java中全部错误都会被打包为对象后作一些处理。能够尝试(try)捕捉(catch)表明错误的对象后作一些处理 例如:express

package errorDemo;
import java.util.*;

public class Average {
	public static void main(String[] args) {
		try{
			Scanner scan=new Scanner(System.in);
			double sum = 0;
			int i = 0;
			while(true) {
				int num=scan.nextInt();
				if(num==0) {
				break;
				}
				sum+=num;
				i++;
			}
			System.out.printf("%.1f%n",sum/i);
		}catch(InputMismatchException ex) {
			System.out.println("输入整数");	
		}
	}
}

这里使用了try、 catch语法,JVM会尝试执行try区块中的程序代码。若是发生错误,执行流程会跳离错误发生点,而后比较 catch括号中声明的类型,是否符合被抛出的错误对象类型,若是是的话,就执行 catch区块中的程序代码。 执行完毕后进行try,catch以后的代码。 尝试恢复正常流程:小程序

package errorDemo;
import java.util.*;

public class Average {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		double sum = 0;
		int i = 0;
		while (true) {
			try {
				int num = scan.nextInt();
				if (num == 0) {
					break;
				}
				sum += num;
				i++;
			} catch (InputMismatchException ex) {
				System.out.printf("忽略非整数%s%n",scan.next());
			}
		}
		System.out.printf("%.1f%n", sum / i);
	}
}

若是nextInt()方式错误InputMismatchException,程序跳到catch,执行catch区域后,还在while循环,能够继续下一个循环流程。(可是并不建议捕捉InputMismatchException) 这里写图片描述 二、异常继承架构 在先前的 Average范例中,虽然没有撰写try、ctch语句,照样能够编译执行。可是这样撰写,编译却会错误:数组

int a =System.in.read();

要解决这个错误信息有两种方式,一是使用try、 catch打包System.in.read();在main()方法旁声明throws java.io.IOException。简单来讲,编译程序认为System. in read()时有可能发生错误,要求你必定要在程序中明处理错误。 例如,若这样撰写就能够经过编译:架构

try {
		int a=System.in.read();
	}catch(java.io.IOException ex) {
		ex.printStackTrace();
	}

Average范例与这里的例子,程序都有可能发生错误,编译程序单单就只要求这里的范例必定要处理错误。要了解这个问题,得先了解那些错误对象的继承架构: 这里写图片描述 首先要了解错误会被包装为对象,这些对象都是可抛出的(稍后介绍 throw语法,就会了解如何抛出错误对象),所以设计错误对象都承自java.lang.Throwable类, Throwable定义了取得错误信息、堆栈追踪( (Stack Trace)等方法,它有两个子类Java. lang.Exception和Java. lang. Error。 Error与其子类实例表明严重系统错误,如硬件层面错误、JVM错误或内存不足等问题。虽然也可使用try、 catch来处理 Error对象,但并不建议,发生严重系统错误时Java应用程序自己是无力回复的。举例来讲,若JVM所需内存不足,不可能撰写程序要求操做系统给予JVM更多内存。Error对象抛出时,基本上不用处理,任其传播至JVM为止,或者是最多留下日志信息。 (若是抛出了 Throwable对象,而程序中没有任何 catch捕捉到错误象,最后由JVM捕捉到的话,那JVM基本处理就是显示错误对象打包的信息并中断程序) 程序设计自己的错误,建议使用 Exception或其子类实例来表现,因此一般称错误处理为异常处理( Exception Handling)。 就语法与继承架构上来讲,若是某个方法声明会抛出 Throwable或子类实例,只要不是属于 Error, Java.lang.RuntimeException或其子类实例,就必须明确使用try、 catch语法加以处理,或者用 throws声明这个方法会抛出异常,不然会编译失败。先前调用 System.in.read()时,in实际上是 System的静态成员,其类型为java.io. InputStream,若是查询API文件,能够看到 Inputstream的read()方法声明。 IOException是 Exception的直接子类,因此编译程序要求你明确使用语法加以处理。Exception或其子对象,但非属于RuntimeException或其子对象,称为受检异常( Checked Exception)。(受编译程序检查)受检异常存在之目的,在于API设计者实现某方法时,某些条件成立时会引起错误,并且认为调用方法的客户端有能力处理错误,要求编译程序提醒客户端必须明确处理错误,否则不可经过编译,API客户端无权选择要不要处理。( ReflectiveOperationException是JDK7以后新增的类,JDK6以前 ClassNot FoundException等类是直接继承自 Exception类) 属于 Runtimeexception衍生出来的类实例,表明API设计者实现某方法时,某些条件成立时会引起错误,并且认为API客户端应该调用方法前作好检查,以免引起错误之因此命名为执行时期异常,是由于编译程序不会强迫必定得在语法上加以处理,亦称为非受检异常( Unchecked Exception) 例如使用数组时,若存取超出索引就会抛出Arrayindexoutofboundsexception 但编译程序并无强迫你在语法上加以处理。这是由于Arrayindexoutofboundsexception是一种 Runtimeexception,能够在API文件的开头找到继承架构图。 例如 Average范例中,用户输入是否正确并不是事先能够得知,所以 Inputmismatchexception设计为一种 Runtimeexception。 Java对于 Runtimeexception的态度是,这是因漏洞而引起,也就是API客户端调用方法前没有作好前置检查才会引起,客户端应该修改程序,使得调用方法时不会发生错误,若是真要以try、 catch处理,建议是日志或呈现友好信息,例如以前的 Average范例的做法。 不过前面的 Average范例若要避免出现 Inputmismatchexception应该是取得用户的字符串输入以后,检查是否为数字格式,如果再转换为int整数,若格式不对就提醒用户作正确格式输入:app

package errorDemo;
import java.util.Scanner;

public class Average2 {
	public static void main(String[] args) {

		double sum = 0;
		int i = 0;
		while (true) {

			int num = nextInt();
			if (num == 0) {
				break;
			}
			sum += num;
			i++;
		}
		System.out.printf("%.1f%n", sum / i);
	}

	static Scanner scan = new Scanner(System.in);

	static int nextInt() {
		String input = scan.next();
		while (!input.matches("\\d*")) {
			System.out.println("输入数字");
			input = scan.next();
		}
		return Integer.parseInt(input);
	}
}

上例的 nextInt()方法中,使用了 Scanner的next()方法来取得用户输入的下个字符串,若是字符串不是数字格式,就会提示用户输入数字, String的 matches()方法中设定了"\d*"这是规则表示式( Regular Expression,),表示检查字符串中的字符是否是数字,如果则返回true。 用try catch捕捉异常对象时要注意,若是父类异常对象在子类异常对象前被捕捉则catch子类异常对象永远不会执行,编译程序会检查:框架

try {
		int a=System.in.read();
	}catch(Exception ex) {
		ex.printStackTrace();
	}catch(IOException ex) {//编译错误
		ex.printStackTrace();
	}

必须更改顺序:网站

try {
		int a=System.in.read();
	}catch(IOException ex) {
		ex.printStackTrace();
	}catch(Exception ex) {
		ex.printStackTrace();
	}

JDK7后可使用多重捕捉语法:

try{
.....
}catch(IOException | InterruptedException | ClassCastIOException e){
	e.printStackTrace();
}

catch()括号内列出异常不得有继承关系。 三、抓、抛 开发一个连接库,其中有个功能是读取纯文本文档,并以字符串返回文档中全部文字,这么撰写:

package errorDemo;
import java.io.*;
import java.util.*;

public class FileUtil {
	public static String readFile(String name) {
		StringBuilder txt=new StringBuilder();
		try {
			Scanner scan=new Scanner(new FileInputStream(name));
			while(scan.hasNext()) {
				txt.append(scan.nextLine()).append('\n');
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		return txt.toString();
	}
}

scanner建立时能够给予 Inputstream实例,而 Fileinputstream可指定档名来开启与读取文档内容,是 Inputstream的子类,所以可做为Scanner建立之用。因为建立 Fileinputstream时会抛出 Ellenotfoundexception,根据目前学到的异常处理语法,因而你捕捉 Filenotfoundexception并在控制台中显示错误信息。 有说这个连接库会用在文本模式中吗?若是这个连接库是用在Web网站上,发生错误时显示在控制台上,Web用户怎么会看获得?你开发的是连接库异常发生时如何处理,是连接库用户才知道,直接在 catch中写死处理异常或输出错误信息的方式,并不符合需求。 若是方法设计流程中发生异常,而你设计时并无充足的信息知道该如何处理(例如不知道连接库会用在什么环境),那么能够抛出异常,让调用方法的客户端来处理。例如:

package errorDemo;

import java.io.*;
import java.util.*;

public class FileUtil {
	public static String readFile(String name) throws FileNotFoundException {
		StringBuilder txt = new StringBuilder();
		Scanner scan = new Scanner(new FileInputStream(name));
		while (scan.hasNext()) {
			txt.append(scan.nextLine()).append('\n');
		}

		return txt.toString();
	}
}

操做对象的过程当中若是会抛出受检异常,但目前环境信息不足以处理异常,没法使用ty、 catch处理时,可由方法的客户端依据当时调用的环境信息进行处理。为了告诉编译程序这个事实,必须使用 theows声明此方法会抛出的异常类型或父类型,编译程序才会让你经过编译。 抛出受检异常,表示你认为调用方法的客户端有能力且应该处理异常, throws声明部份会是API操做接口的一部份,客户端不用察看原始码,从API文件上就能直接得知,该方法可能抛出哪些异常。 若是你认为客户端调用方法的时机不当引起了某个错误,但愿客户端准备好前置条件,再来调用方法,这时能够抛出非受检异常让客户端得知此状况,若是是非受检异常编译程序不会要求明确使用try、 catch或在方法上使用 throws声明,由于Java的设计上认为,非受检异常是程序设计不当引起的漏洞,异常应自动往外传播,不该使用try、 catch来尝试处理,而应改善程序逻辑来避免引起错误。 实际上在异常发生时,可以使用ty、 catch处理当时环境可进行的异常处理,当时环境下没法决定如何处理的部分,能够抛出由用方法的客户端处理。若是想先处理部分事项再抛出,能够以下:

package errorDemo;

import java.io.*;
import java.util.*;

public class FileUtil {
	public static String readFile(String name) throws FileNotFoundException {
		StringBuilder txt = new StringBuilder();

		try {
			Scanner scan = new Scanner(new FileInputStream(name));
			while (scan.hasNext()) {
				txt.append(scan.nextLine()).append('\n');
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw e;
		}
		return txt.toString();
	}
}

在 catch区块进行完部份错误处理以后,可使用 throw(注意不是 throws)将异常再抛出,实际上,你能够在任何流程中抛出异常,不必定要在 cateh区块中,在流程中抛出异常,就直接跳离原有的流程,能够抛出受检或非受检异常,若是抛出的是受检异常,表示你认为客户端有能力且应处理异常,此时必须在方法上使用 throws声明:若是抛出的异常是非受检异常,表示你认为客户端调用方法的时机出错了,抛出异常是要求客户端修正这个漏洞再来调用方法,此时也就不使用 throws声明 若是原先有个方法操做是这样的:

public class DoSome {
	public static void dosome(String arg) throws FileNotFoundException,EOFException{
		try {
			if("on".equals(arg)) {
				throw new FileNotFoundException();
			}else {
				throw new EOFException();
			}
		}catch(FileNotFoundException e) {
			e.printStackTrace();
			throw e;
		}catch(EOFException e) {
			e.printStackTrace();
			throw e;
		}
	}
}

EOFException FileNotFoundException 都是一种IOException其中zaiJDK6以后能够直接:

........
catch(IOException e) {
			e.printStackTrace();
			throw e;
		}

若是使用继承时,父类某个方法声明throws某些异常,子类从新定义该方法时能够:

  • 不声明 throws任何异常
  • throws父类该方法中声明的某些异常。
  • throws父类该方法中声明异常的子类。 可是不能够:
  • thows父类方法中未声明的其余异常。
  • throws父类方法中声明异常的父类

四、注意项 异常处理的本意是,在程序错误发生时,可以有明确的方式通知API客户端,让客户端采起进一步的动做修正错误,目前Java是惟一采用受检异常 (Checked Exception)的语言,这有两个目的:一是文件化,受检异常声明会是API操做接口的一部份,客户端只要查阅文件,就能够知道方法可能会引起哪些异常,并事先加以处理,而这是API设计者决定是否抛出受检异常时的考虑之一;二是提供编译程序信息,让编译程序可以在编译时期就检查出API客户端没有处理异常。问题是有些错误发生而引起异常时,你根本无力处理,例如使用JDBC撰写数据库联机程序时,常常要处理的java.sql.Solexception是受检异常,若是异常的发生缘由是数据库联机异常,而联机异常的缘由是因为实体线路问题,那么不管如何你都不可能使用try、 catch处理这种状况。

错误发生时,若是当时情境并无足够的信息让你处理异常,能够就现有信息处理完异常后,从新抛出异常。

public Customer getCustomer(String id) throws SQLException{
	.........
}

上面看起来彷佛没有问题,但假设这个方法是在整应用程序很是底层被调用,在某个 Sqlexception发生时,最好的方法是将异常浮现至用户画面呈现,例如网页技术,将错误信息在网页上显示出来给管理人员 为了让异常往上浮现,你也许会选择在每一个法调用上都声明 throws Solexception,但前面假设,这个方法的调用是在整个应用程序的底层,这样的作法也许会形成许多程序代码的修改(更别说要从新编译了),另外一个问题是,若是你根本无权修改应用程序的其余部份,这样的作法显然行不通。 受检异常本意良好,有助于程序设计人员注意到异常的可能性并加以处理,但在应用程序规模增大时,会逐渐对维护形成困难,上述状况不必定是你自定义API时发生,也多是在底层引入了一个会抛出受检异常的API而发生相似状况 从新抛出异常时,除了将捕捉到的异常直接抛出,也能够考虑为应用程序自定义专属异常类别,让异常更能表现应用程序特有的错误信息。自定义异常类别时,能够继承 Throwable、 Error或Exception的相关子类,一般建议继承自 Exception或其子类,若是不是继承自Error或 Runtimeexception,那么就会是受检异常。

public class CustomizedException extends Exception{
	 //自定义受检异常
 }

错误发生时,若是当时情境没有足够的信息让你处理异常,你能够就现有信息处理完异常后,从新抛出异常。既然你已经针对错作了某些处理,那么也就能够考虑自定义异常,用以更精确地表示出未处理的错误,若是认为调用API的客户端应当有能力处理未处理的错误,那就自定义受检异常、填入适当错误信息并从新抛出,并在方法上使用 throws加以声明;若是认为调用API的客户端没有准备好就调用了方法,才会形成还有未处理的错误,那就自定义非受检异常、填入适当错误信息并从新抛出。

public class CustomizedException extends Runtimeexception{
	//自定义非受检异常
}

一个基本的例子是这样的:

try{
 ...
 }catch(SomeException ex){
	 //作些可行的处理
	 // Logging之类的
	 throw new CustomizedException("error message"); // Checked或 Unchecked?
	 }

相似地,若是流程中要抛出异常,也要思考这是客户端能够处理的异常仍是客户端没有准备好前置条件就调用方法引起的异常.

if(somecondition){
	 throw new CustomizedException("error message");// Checked ak Unchecked?
 }

不管如何,Java采用了受检异常的作法,Java的标准API彷佛也打算一直这么区分下去,只是受检异常让开发人员无从选择,会由编译程序强制性要求处理,确实会在设计上形成麻烦,于是有些开发者在设计连接库时,干脆就选择彻底使用非受检异常,一些会封装应用程序底层行为的框架,如 Spring或 Hibernate,就选择了让异常体系是非受检异常,例如 Spring中的DataAccessException或者是 Hibernate3中的 HibernateException,它们选择给予开发人员较大的弹性来面对异常(也许也须要开发人员更多的经验)随着应用程序的演化,异常也能够考虑演化,也许一开始是设计为受检异常,然而随着应用程序堆栈的加深,受检异常总是一层一层往外声明抛出形成麻烦时,这也许表明了原先认为客户端可处理的异常,每一层客户端实际上都无力处理了,每层客户端都无力处理的异常,也许该视为一种漏洞,也许客户端在呼叫时都该准备好前置条件再行调用,以免引起错误,这时将受检异常演化为非受检异常,也许就有其必要。 实际上确实有这类例子, Hibemate2中的 Hibernateexception是受检异常,然而 Hibernate3 3中的 Hibernateexception变成了非受检异常 然而,即便不用面对受检异常与非受检异常的区别,开发者仍然必须思考,这是客户端能够处理的异常仍是客户端没有准备好前置条件就调用方法,才引起的异常。 5**、认识堆栈追踪** 在多重方法调用下,异常发生点多是在某个方法之中,若想得知异常发生的根源以及多重方法调用下异常的堆栈传播,能够利用异常对象自动收集的堆栈追踪( Stack Trace)来取得相关信息。 查看堆栈追踪最简单的方法,就是直接调用异常对象的 printstacktrace()。 堆栈追踪信息中显示了异常类型,最顶层是异常的根源,如下是调用方法的顺序,程序代码行数是对应于当初的程序原始码,如使用IDE,单击行数就会直接开启原始码并跳至对应行数(若是原始码存在的话)。 printstacktrace()还有接受Printstream Printwriter的版本,能够将堆栈追踪信息以指定方式至输出目的地(例如文档)。 (编译位码文档时,默认会记录原始码行数信息等除错信息,在使用 javac编译时指定-g:none自变量就不会记录除错信息,编译出来的位码文档容量会比较小) 若是想要取得个别的堆栈追踪元素进行处理,则可使getStackTrace(),这会返回 stacktraceelement数组,数组中索引0为异常根源的相关信息,以后为各方法调用中的信息,可使用 StackTraceElement 的 getClassName ()、getFileName()、getLineName ()等方法取得对应的信息。 要善用堆栈追踪,前提是程序代码中不可有私吞异常的行为,例如在捕捉异常后什么都不作。 这样的程序代码会对应用程序维护形成严重伤害,由于异常信息会彻底停止,以后调用此片断程序代码的客户端,彻底不知道发生了什么事,形成除错异常困难,甚至找不出错误根源 另外一种对应用程序维护会有伤害的方式,就是对异常作了不适当的处理,或显示了不正确的信息。例如,有时因为某个异常层级下引起的异常类型不少, 有些程序设计人员为了省麻烦,或由于常常处理找不到文档的错误,于是处理成“找不到文档”因其余缘由致使错误时依然显示找不到文档,就会误导除错方向。


在使用throw重抛异常时,异常追踪堆栈的起点还是异常发生根源,不是重抛异常的地方:

package errorDemo;

public class StackTraceDemo {
	public static void main(String[] args) {
		try {
			c();
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}
	private static void c() {
		try {
			b();
		} catch (NullPointerException e) {
			e.printStackTrace();
			throw e;
		}
	}
	private static void b() {
		a();
	}
	private static String a() {
		String txt=null;
		return txt.toUpperCase();
	}
}

抛出错误:

java.lang.NullPointerException
	at errorDemo.StackTraceDemo.a(StackTraceDemo.java:24)
	at errorDemo.StackTraceDemo.b(StackTraceDemo.java:20)
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:13)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)
java.lang.NullPointerException
	at errorDemo.StackTraceDemo.a(StackTraceDemo.java:24)
	at errorDemo.StackTraceDemo.b(StackTraceDemo.java:20)
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:13)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)

若是想让异常堆栈起点为重抛异常的地方,可使用fillInStackTrace()方法,这个方法会从新装填异常堆栈,将起点设为重抛异常的地方,并返回Throwable对象:

package errorDemo;

public class StackTraceDemo {
	public static void main(String[] args) {
		try {
			c();
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
	}
	private static void c() {
		try {
			b();
		} catch (NullPointerException e) {
			e.printStackTrace();
			Throwable t=e.fillInStackTrace();
			throw (NullPointerException)t;
		}
	}
	private static void b() {
		a();
	}
	private static String a() {
		String txt=null;
		return txt.toUpperCase();
	}
}

抛出错误:

java.lang.NullPointerException
	at errorDemo.StackTraceDemo.a(StackTraceDemo.java:25)
	at errorDemo.StackTraceDemo.b(StackTraceDemo.java:21)
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:13)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)
java.lang.NullPointerException
	at errorDemo.StackTraceDemo.c(StackTraceDemo.java:16)
	at errorDemo.StackTraceDemo.main(StackTraceDemo.java:6)

六、关于assert 有时候,需求或设计时就可确认,程序执行的某个时间点或某个状况下,必定是处于或不处于何种状态,若不是,则是个严重错,开发过程当中发现这种严重错误,必须立中止程序确认需求与设计 程序执行的某个时间点或某个状况下,必然处于或不处于何种状态,这是一种断言( Assertion),例如某个时间点程序某个变量值必定是多少。断言的结果必定是成立或不成立,预期结果与实际程序状态相同时,断言成立,不然断言不成立。 Java在JDK1.4以后提供 assert语法,有两种使用的语法

assert boolean_expression;
 assert boolean_expression:detail_expression;

boolean_expression若为true,则什么事都不会发生,若是为 false,则会发生java.lang. AssertionError,此时若采起的第二个语法,则会将 detail expression的结果显示出来,若是当中是个对象,则调用 toString ()显示文字描述结果. 断言功能是在JDK1.4以后提供,因为使用 assert做为关键字,为了不JDK1.3或更早版本程序使用assert做为变量致使名称冲突问题,默认执行时不启动断言检查。若是要在执行时启动断言检查,能够在执行java指令时,指定- -enableassertions或是-ea自变量。那么什么时候该使用断言:

  • 断言客户端调用方法前,已经准备好某些前置条件(一般在 private方法之中)
  • 断言客户端调用方法后,具备方法承诺的结果。
  • 断言对象某个时间点下的状态
  • 使用断言取代批注
  • 断言程序流程中绝对不会执行到的程序代码部份
public void charge(int money) {//取钱调用
		if(money<0) {
			if(money<this.balance) {
				this.balance-=money;
			}else {
			System.out.println("钱不够");
			}
		}else {
			System.out.println("error");
		}
	}

原先的设计在错误发生时,直接在控制台中显示错误信息,适当地将 charge法中的子流程封装为方法调用,并将错误信息以例外抛出,原程序可修改以下:

public void charge(int money) throws InsufficientException {
	checkGreater(money);
	checkBalance(money);
	this.balance-=money;
}
private void checkGreater(int money){
	if(money<0) {
		throw new IllgalArgumentException("error");
	}
}
private void checkBalance(int money) throws InsufficientException{
	if(money>this.balance) {
		throw new InsufficientException("钱不够",this.balance);
		}
}

这里假设余额不足是种商务流程上可处理的错误,所以让Insufficientexception继承自 Exception成为受检例外,要求客户端呼叫时必处理,而调用 charge方法时,原本就不应传入负数,所以checxgrater()会抛出非受检Illegalargumentexception,这是种让错的程序看得出错,藉由防护式程序设计( Defensive Programmig),来实现速错(Failas)概念。 实际上, checkgreater()是一种前置条件检查,若是程序上线后就再也不须要这种检查的话,能够将之以 assert取代,并在开发阶段使用-ea选项,而程序上线后取消该选项。使用assert:

public void charge(int money) throws InsufficientException {
	assert money>=0 : "error";

	checkGreater(money);
	checkBalance(money);
	this.balance-=money;
	
	assert this.balance>=0 : "this.balance不能是负数";
}

private void checkBalance(int money) throws InsufficientException{
	if(money>this.balance) {
		throw new InsufficientException("钱不够",this.balance);
		}
}

另一种用assert的状况,必定不能有default的情况:

...
switch(...){
	case ...:
	...
	break;
	........

	default:
	assert false:"非定义常数";//不可让default情况发生。default情况发生意味着开发时期严重错误
}
相关文章
相关标签/搜索