C语言编程中通过返回值来判断程序是否运行正常。而Java设计了一套异常机制来处理程序运行过程不正常的情况,这点非常重要,只有了解这一点,才能判断什么时候该抛异常,抛出什么异常,异常什么时候该被处理。
1、Java异常的相关语法
try-catch-finally,throw,throws
2、Java异常的分类
Java通过Exception类创建一套异常体系,定义了一系列有语义的运行时发生的可被捕获或者处理的各种错误。例如IOException,IllegalArgumentException。这点要比通过返回值来判断错误要强很多。
这点非常重要,异常就是运行的错误,它没什么,就是起到了告诉调用者程序出现的错误,如果抛出一个异常类,没有定义清楚错误的信息,那么无疑是失败的,不合理的。既然是错误信息,可被捕获,那么如果捕获要么自己容忍处理,记录下错误信息,要么继续加强异常错误的信息量,继续扔出让上层调用者更加明白出现的错误是个什么东西。
Java异常分为Checked异常和UnChecked异常,前者继承自Exception类,后者继承RuntimeException类。两种异常都是可以throw,可以catch的。区别在于Java语言本身是如何对待两种异常,以及两种异常所代表的语义和应用场景。
(1)Checked异常,就是需要被处理的异常。大家经常见到的IOException、SQLException这些,需要try-catch或者进一步throws给上层,如果不做就会出现语法错误。这是Java在编译器强制调用者处理其中方法抛出的checked异常。这类异常一般是可预期的,可预期的意思就是,上层调用者拿到你的异常后,知道它如何产生的,也预料到它会产生,所以才有抛出去的必要,才可以采取措施去处理它:容忍异常继续调用运行其他代码或者重试修复异常或者扔给上层。如果你扔给上层一个异常,上层也不知道它是干什么的,那么就没必要定义为Checked异常。对于这种异常,我们本身无法处理,也就是无法修复,但是上层知道这个异常,有办法处理,那么就应该扔给上层。那么需要捕获,还是直接抛出?捕获,再抛出:代表这个异常说明的还不够清楚,我们需要进一步加强说明,再扔给上层,上层调用者才更清楚知道如何处理
(2)UnChecked异常,就是不强制要求处理的异常。例如经常见到的参数、状态非法的异常IllegalArgumentException,这种异常表示的语义是出现了大家预期之外的异常。本来约定的参数,按照正确的传入就行了,但是传入非法的,在上层调用者意料之外,那么它就不会事先写好try-catch去处理。因此Java不在编译期从语法上要求捕获该类异常。如果出现了该类异常后,那么就直接trhow运行时异常就行了。虽然语法上不强制要求处理该类异常,但是到底该不该catch呢?这种异常上层调用者压根就没预料到,所以也不会去处理。但是一旦抛出异常,throw后,不管是Checked异常还是UnChecked异常,程序都会终止运行。那么万一后面的代码在干很重要的事情怎么办,因此一些非常重要的代码,不管有没有异常,都会try-catch,然后catch中记录异常就行了,保证后面代码的运行。
(3)catch捕获异常该做些什么呢,如果什么都不做,那么就不需要catch,直接在函数后面throws扔给上层,书写try-finally就行。可以干些什么呢?三件事:将异常的发生原因进一步描述,也就是封装的更加细致,加上一些描述信息,再扔给上层,这时也可以log记录下错误信息,防止无人记录错误原因;上层获取该异常没有太大价值,当前程序可以容忍这种错误,那么记录下来错误原因,继续执行其他代码;记录错误原因,采取一些措施进行重试或者修复工作
(4)自定义异常:这种场景很少见。不管是Checked异常还是UnChecked异常,Java都提供了几乎覆盖各种错误的异常类。我们只需要加上一些特殊文字说明就行了,自定义异常类除了可能导致错误语义表达不清晰,还会造成额外代码。
再次强调异常传达的是一种程序运行的错误信息,既然是错误信息就应该根据错误的类型进行符合场景的处理,没有什么统一的处理方式,了解其本质,才能灵活应用。下面针对具体代码进行讲解
/发现很多地方省去了log记录异常信息,因为即使记录也只是传达给日后分析的人员,而没有起到告知上层调用者的目的。
publicclassTask2 {
privatestaticfinalLogger log = LoggerFactory.getLogger(Task2.class);
publicstaticvoidreplaceCode(String propFilePath, String templateFilePath, String desFilePath)throwsIOException {
Validate.notBlank(propFilePath);//这个方法会判断参数是否为空,来抛出运行时异常。因为接口需要传入的参数各种路径是不能为空的,但是传入为空,那么就是意料之外,调用者不会一方面知
//道会出现参数异常,进行捕获处理,一方面又传入非法的参数。错误的产生是上层调用者无意之间导致的,如果知道这个异常是可以提前避免的。而IOException
//这种异常,即使之前做了狠多准备,还是可以产生这种错误
Validate.notBlank(templateFilePath);
Validate.notBlank(desFilePath);
finalBufferedWriter destWriter;
try{
destWriter = IOUtil.converseBufferdWriter(IOUtil.getFileWriter(desFilePath));
} catch(UnsupportedEncodingException e1) {
thrownewUnsupportedEncodingException("输出文件"+ e1.getMessage());//对下层函数抛出的异常进一步包装,再扔出,让上层调用者知道这是输出文件编码不对,而不是普通的
//IOException
} catch(FileNotFoundException e1) {
thrownewFileNotFoundException("输出文件"+ e1.getMessage());
}
URL templateURL = Resources.getResource(templateFilePath);
finalStrSubstitutor strSub = getPropStrSub(propFilePath);
try{
Resources.readLines(templateURL, Charsets.UTF_8, newLineProcessor() {
intlineNumber =0;
@Override
publicbooleanprocessLine(String line)throwsIOException {
if(lineNumber >0) {
destWriter.newLine();
}
lineNumber++;
returnreplaceCodePerLine(line, strSub, destWriter);
}
@Override
publicString getResult() {
returnnull;
}
});
} catch(IOException e) {
thrownewIOException("模板文件读取错误", e);//继续抛出异常,因为读取错误的IOException是调用者可预知的,读文件会出现错误。这里加强了语义扔出
} finally{
Closeables.closeQuietly(destWriter);//这里也该抛出异常,但是guava实现的是容忍这种错误,只记录了日志。应该告诉上层关闭文件出错
}
}
privatestaticbooleanreplaceCodePerLine(String line, StrSubstitutor strSub, BufferedWriter destWriter)throwsIOException
{
if(strSub ==null|| destWriter ==null) {
returnfalse;
}
if(StringUtils.contains(line,'$')) {
line = strSub.replace(line);
}
try{
destWriter.append(line);
} catch(IOException e) {
thrownewIOException("输出文件写入出错");//继续抛出异常,因为读取错误的IOException是调用者可预知的,读文件会出现错误。这里加强了语义扔出
}
returntrue;
}
publicstaticStrSubstitutor getPropStrSub(String propFilePath)throwsIOException {
finalHashMap propMap = Maps.newHashMap();
URL propURL = Resources.getResource(propFilePath);
try{
Resources.readLines(propURL, Charsets.UTF_8, newLineProcessor() {
@Override
publicbooleanprocessLine(String line)throwsIOException {
String[] metas = StringUtils.split(line, "=");
if(metas.length !=2)
returnfalse;
propMap.put(metas[0], metas[1]);
returntrue;
}
@Override
publicString getResult() {
returnnull;
}
});
} catch(IOException e) {
thrownewIOException("读取配置文件出错");//继续抛出异常,因为读取错误的IOException是调用者可预知的,读文件会出现错误。这里加强了语义扔出
}
returnnewStrSubstitutor(propMap);
}
publicstaticvoidmain(String[] args) {
longstartTime = System.currentTimeMillis();
try{
Task2.replaceCode("sdxl.properties","temp.txt","output.txt");
}catch(IOException e){
log.error(e.getMessage,e);//main的上层调用者就是系统进程,上层对IOException不清楚,也无法处理,因此直接记录异常即可。
}
System.out.println(System.currentTimeMillis() - startTime);
}
}