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

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

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

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)代表错误的对象后做一些处理 例如:

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)

2、异常继承架构 在先前的 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整数,若格式不对就提醒用户做 正确格式输入:

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()括号内列出异常不得有继承关系。 3、抓、抛 开发一个链接库,其中有个功能是读取纯文本文档,并以字符串返回文档中所有文字,这么撰写:

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父类方法中声明异常的父类

4、注意项 异常处理的本意是,在程序错误发生时,能够有明确的方式通知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)
复制代码

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状况发生意味着开发时期严重错误
}
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值