Java异常面试题2
try-catch-finally中哪个部分可以省略?
以下三种情况都是可以的:
try-catch
try-finally
try-catch-finally
可以省略catch或者finally。catch和finally不可以同时省略。
try-catch-finally中,return的执行顺序到底是怎样的?
return的执行顺序首先要看finally到底有没有return?不同情况不同结果
如果有return肯定会覆盖try或者catch中的return。
如果没有return的语句,那就看返回值是什么类型的数据
如果返回值是基本数据类型,finally块中对返回值的改变不会影响返回值。因为在return之前已经将返回值的内容存储在栈中了。
如果返回值是引用数据类型,finally块中对返回值的改变会影响返回值。因为在return之前已经将引用对象的地址存储在栈中,finally块中对于引用对象值的改变会影响到返回值。
有以下情况
return的执行顺序首先要看finally到底有没有return?不同情况不同结果
如果有return肯定会覆盖try或者catch中的return。
finally中的return覆盖catch中的return。
publicstaticinttestTryCatch5{intresult=0;try{result=1/0;returnresult;}catch(Exceptione){returnresult;}finally{result=2;returnresult;}}//返回2
finally中的return覆盖try中的return
publicstaticinttestTryCatch1{intresult=0;try{result=1;returnresult;}catch(Exceptione){returnresult;}finally{result=2;returnresult;}}//返回2
如果没有return的语句,那就看返回值是什么类型的数据
如果返回值是基本数据类型,finally块中对返回值的改变不会影响返回值。因为在return之前已经将返回值的内容存储在栈中了。
//在执行catch中的return之前,把返回值0存储在栈中,所以finally中对于result的操作不影响返回值,所以返回0publicstaticinttestTryCatch6{intresult=0;try{result=1/0;returnresult;}catch(Exceptione){returnresult;}finally{result=2;}}//返回0
如果返回值是引用数据类型,finally块中对返回值的改变会影响返回值。因为在return之前已经将引用对象的地址存储在栈中,finally块中对于引用对象值的改变会影响到返回值。
/***引用数据类型try_finally中return的执行.下面的引用类型的返回结果都是AB*@return*/publicstaticStringBuildertestTryCatch3{StringBuilderresult=newStringBuilder("A");try{returnresult;}catch(Exceptione){returnresult;}finally{result.append("B");returnresult;}}publicstaticStringBuildertestTryCatch4{StringBuilderresult=newStringBuilder("A");try{returnresult;}catch(Exceptione){returnresult;}finally{result.append("B");}}/***引用数据类型catch_finally中return的执行*@return*/publicstaticStringBuildertestTryCatch7{StringBuilderresult=newStringBuilder("A");try{inti=5/0;returnresult;}catch(Exceptione){returnresult;}finally{result.append("B");returnresult;}}publicstaticStringBuildertestTryCatch8{StringBuilderresult=newStringBuilder("A");try{inti=5/0;returnresult;}catch(Exceptione){returnresult;}finally{result.append("B");}}
上面的引用类型的返回结果都是AB
如果catch中return了,finally还会执行吗?
会,如果在catch中return了,也会在return之前,先执行finally代码块,finally的作用就是无论出现什么状况,finally里的代码一定会被执行(理论上一定执行,实际上finally也不是一定会被执行)。而且如果finally代码块中含有return语句,会覆盖其他地方的return。如果finally代码块中不含有return语句,对于基本数据类型的数据,在finally块中改变return的值对返回值没有影响,而对引用数据类型的数据会有影响。
什么情形下,finally代码块不会执行?
没有进入try代码块的时候自然不会执行finally代码块;
System.exit强制退出程序;
守护线程被终止(就是在守护线程里的finally可能不会一定执行,因为守护线程是依赖用户线程的,如果用户线程结束不管你守护线程执行完没都会结束守护线程,所以这个时候里面的finally的代码是不会执行了);
常见的RuntimeException有哪些?
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组下标越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
还有IO操作的BufferOverflowException异常
ArithmeticExecption算术异常
Java常见异常有哪些
1、java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
2、java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
3、java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
4、java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
5、java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
6、java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
7、java.lang.ArithmeticException:算术条件异常。譬如:整数除零等
8、java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
9、java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
10、java.lang.InstantiationException:实例化异常。当试图通过newInstance方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
11、java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
12、java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
13、java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
14、java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
在finally块中清理资源或者使用try-with-resource语句
当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。
publicvoiddoNotCloseResourceInTry{FileInputStreaminputStream=null;try{Filefile=newFile("./tmp.txt");inputStream.close;}catch(FileNotFoundExceptione){log.error(e);}catch(IOExceptione){log.error(e);}}
问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try代码块内代码会正常执行,并且资源可以正常关闭。但是,使用try代码块是有原因的,一般调用一个或多个可能抛出异常的方法,而且,你自己也可能会抛出一个异常,这意味着代码可能不会执行到try代码块的最后部分。结果就是你并没有关闭资源。
所以,你应该把清理工作的代码放到finally里去,或者使用try-with-resource特性。
17.1、使用finally代码块
与前面几行try代码块不同,finally代码块总是会被执行。不管try代码块成功执行之后还是你在catch代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。
publicvoidcloseResourceInFinally{FileInputStreaminputStream=null;try{Filefile=newFile("./tmp.txt");inputStream=newFileInputStream(file);}catch(FileNotFoundExceptione){log.error(e);}finally{if(inputStream!=null){try{inputStream.close;}catch(IOExceptione){log.error(e);}}}}
17.2、Java7的try-with-resource语法
如果你的资源实现了AutoCloseable接口,你可以使用这个语法。大多数的Java标准资源都继承了这个接口。当你在try子句中打开资源,资源会在try代码块执行后或异常处理后自动关闭。
publicvoidautomaticallyCloseResource{Filefile=newFile("./tmp.txt");try{FileInputStreaminputStream=newFileInputStream(file);}catch(FileNotFoundExceptione){log.error(e);}catch(IOExceptione){log.error(e);}}
Java异常使用注意事项
在Java中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范进行异常处理的原因。而团队之间的这些规范往往是截然不同的。
对异常进行文档说明
当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。
在Javadoc添加@throws声明,并且描述抛出异常的场景。
publicvoiddoSomething(Stringinput)throwsMyBusinessException{...}
使用描述性消息抛出异常
在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。
但这里并不是说要对错误信息长篇大论,因为本来Exception的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。
如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是NumberFormatException。当你以错误的格式提供String时,它将被java.lang.Long类的构造函数抛出。
try{newLong("xyz");}catch(NumberFormatExceptione){log.error(e);}
优先捕获最具体的异常
大多数IDE都可以帮助你实现这个最佳实践。当你尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。
但问题在于,只有匹配异常的第一个catch块会被执行。因此,如果首先捕获IllegalArgumentException,则永远不会到达应该处理更具体的NumberFormatException的catch块,因为它是IllegalArgumentException的子类。
总是优先捕获最具体的异常类,并将不太具体的catch块添加到列表的末尾。
示例代码:
publicvoidcatchMostSpecificExceptionFirst{try{doSomething("Amessage");}catch(NumberFormatExceptione){log.error(e);}catch(IllegalArgumentExceptione){log.error(e)}}
上面的代码片断中看到这样一个try-catch语句的例子。第一个catch块处理所有NumberFormatException异常,第二个处理所有非NumberFormatException异常的IllegalArgumentException异常。这里的NumberFormatException是IllegalArgumentException的子类,所以要优先去捕获,即优先捕获最具体的异常
不要捕获Throwable类
Throwable是所有异常和错误的超类。你可以在catch子句中使用它,但是你永远不应该这样做!不要捕获Throwable类!
典型的例子是OutOfMemoryError或者StackOverflowError。两者都是由应用程序控制之外的情况引起的,无法处理。
所以,最好不要捕获Throwable,除非你确定自己处于一种特殊的情况下能够处理错误。
publicvoiddoNotCatchThrowable{try{//dosomething}catch(Throwablet){//don'tdothis!}}
其实就是java语法支持你像上面这样写,但是建议最好别这样写!如果在catch子句中使用Throwable,它不仅会捕获所有异常,也将捕获所有的错误。JVM抛出错误,指出不应该由应用程序处理的严重问题。
捕获异常要处理异常
很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。
publicvoiddoNotIgnoreExceptions{try{//dosomething}catch(NumberFormatExceptione){//thiswillneverhappen}}
但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。
合理的做法是至少要记录异常的信息,捕获异常要处理异常。
publicvoidlogAnException{try{//dosomething}catch(NumberFormatExceptione){log.error("Thisshouldneverhappen:"+e);}}
包装异常时不要抛弃原始的异常
捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。
在你这样做时,请确保将原始异常
原因(注:参考下方代码NumberFormatExceptione中的原始异常e)。Exception类提供了特殊的构造函数方法,它接受一个Throwable作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。包装异常时不要抛弃原始的异常
publicvoidwrapException(Stringinput)throwsMyBusinessException{try{//dosomething}catch(NumberFormatExceptione){thrownewMyBusinessException("Amessagethatdescribestheerror.",e);}}
使用标准异常
如果使用标准的异常可以解决问题,就不要定义自己的异常。JavaAPI提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用JavaAPI提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。
异常会影响性能
异常处理的性能成本非常高,每个Java程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。
尽管使用异常有利于Java开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。
异常使用情况:
1、仅在异常情况下使用异常;
2、在可恢复的异常情况下使用异常;
异常处理-阿里巴巴Java开发手册
1、[强制]Java类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过catchNumberFormatException来实现。正例:if(obj!=null){…}反例:try{obj.method;}catch(NullPointerExceptione){…}
2、[强制]异常不要用来做流程控制,条件控制。说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
3、[强制]catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。说明:对大段代码进行try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。
4、[强制]捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
5、[强制]有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。
6、[强制]finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。说明:如果JDK7及以上,可以使用try-with-resources方式。
7、[强制]不要在finally块中使用return。说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。反例:
privateintx=0;publicintcheckReturn{try{//x等于1,此处不返回return++x;}finally{//返回的结果是2return++x;}}
8、[强制]捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
9、[强制]在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截。说明:通过反射机制来调用方法,如果找不到方法,抛出NoSuchMethodException。什么情况会抛出NoSuchMethodError呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如:ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛出NoSuchMethodError。
10、[推荐]方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。说明:本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。
11、[推荐]防止NPE,是程序员的基本修养,注意NPE产生的场景:
1)返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。反例:publicintf{returnInteger对象},如果为null,自动解箱抛NPE。
2)数据库的查询结果可能为null。
3)集合里的元素即使isNotEmpty,取出的数据元素也可能为null。
4)远程调用返回对象时,一律要求进行空指针判断,防止NPE。
5)对于Session中获取的数据,建议进行NPE检查,避免空指针。
6)级联调用obj.getA.getB.getC;一连串调用,易产生NPE。
正例:使用JDK8的Optional类来防止NPE问题。
12、[推荐]定义时区分unchecked/checked异常,避免直接抛出newRuntimeException,更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException/ServiceException等。
13、[参考]对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess方法、“错误码”、“错误简短信息”。
说明:关于RPC方法返回方式使用Result方式的理由:
1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。
2)如果不加栈信息,只是new自定义异常,加入自己的理解的errormessage,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。
14、[参考]避免出现重复的代码(Don’tRepeatYourself),即DRY原则。说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
privatebooleancheckParam(DTOdto){…}
总结
综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者API的可用性。
异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。