异常是Java程序中经常遇到的问题,一个异常就是一个Bug,就要花很多时间来定位异常。
Java异常
(1)Throwable是Java异常的*类,所有的异常都继承于这个类。
(2)Error,Exception是异常类的两个大分类。
(3)Error是非程序异常,即程序不能捕获的异常,一般是编译或者系统性的错误,如OutOfMemorry内存溢出异常等。
(4)Exception是程序异常类,由程序内部产生。Exception又分为运行时异常、非运行时异常。
(5)运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过,运行时异常可处理或者不处理。运行时异常一般常出来定义系统的自定义异常,业务根据自定义异常做出不同的处理。常见的运行时异常如NullPointException、ArrayIndexOutOfBoundsException等。
(6)非运行时异常是程序必须进行处理的异常,捕获或者抛出,如果不处理程序就不能编译通过。如常见的IOException、ClassNotFoundException等。
常见的Java异常坑
最顽固的坑--NullPointerException
空指针异常应该是每一个程序员必须要踩得的坑,而且应该是经常踩不断踩的坑,极其普遍且难以根除。但是这个坑可以通过某些方法有效的避免。
(1)什么是空指针?
当一个变量的值为 null 时,在 Java 里面表示一个不存在的空对象,没有实际内容,没有给它分配内存,null 也是对象成员变量的默认值。
所以,一个对象如果没有进行初始化操作,这时候,如果你调用这个对象的方法或者变量,就会出现空指针异常。
如下面示例会发生空指针异常:
Object object = null;
String string = object.toString();
空指针它是属于运行时异常 RuntimeException 的子类,它不是捕获型的,只有在程序运行时才可能报出来,而且会造成程序中断。
如何有效避免空指针?
以下是我在编码过程中遇到的问题及解决办法。
字符串比较,常量放前面---总是从已知的非空String对象中调用equals()方法
if(status.equals(SUCCESS)){
}
这个时候 status 可能为 null 造成空指针异常,应该把常量放前面,就能避免空指针异常。这个应该在各种开发规范里面都会提到,也是最基础的。
if(SUCCESS.equals(status)){
}
当valueOf()和toString()返回相同的结果时,宁愿使用前者。
因为调用null对象的toString()会抛出空指针异常,如果我们能够使用valueOf()获得相同的值,那宁愿使用valueOf(),传递一个null给valueOf()将会返回“null”,尤其是在那些包装类,像Integer、Float、Double和BigDecimal。
BigDecimal bd = getPrice();
System.out.println(String.valueOf(bd)); //不会抛出空指针异常
System.out.println(bd.toString()); //抛出 NullPointerException
初始化默认值
在对象初始化的时候给它一个默认值或者默认构造实现,如:
User user = new User();
String name = StringUtils.EMPTY;
避免返回空集合
在返回一个集合的话,默认会是 null,统一规范返回一个空集合。
举个 List 例子,如:
public List getUserList(){
List list = userMapper.gerUserList();
return list == null ? new ArrayList() : list;
}
这样接收方就不用担心空指针异常了,也不会影响业务。
采用JDK8 Optional 新特性
Optional是Jdk8提供的一个可以包含null值的容器对象,可以用来代替xx != null的判断。(这个暂时因为我们车道系统统一适用的是Java7,这个方案还没有在车到系统代码里使用过。)
OutOfMemoryError
内存异常异常也是一个经常出现的Bug,但是这个不是程序能控制的,这个问题是指要分配的对象的内存超出了当前最大的堆内存,需要调整堆内存大小(-Xmx)以及优化程序。
(1)常见的有以下几种原因:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小
(2)常见解决方法:
1.应用服务器提示错误的解决:
把启动参数内存值设置足够大。
2.Java代码导致错误的解决:
1)检查代码中是否有死循环或递归调用。
2)检查是否有大循环重复产生新对象实体。
3)检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
4 )检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
IOException
IO,即:input, output,我们在读写磁盘文件、网络内容的时候经常会生的一种异常,这种异常是受检查异常,需要进行手工捕获。常用的一种异常处理方式有两种,一种是:使用throws抛出可能发生的异常,另一种是:直接try-catch。
ClassNotFoundException
类找不到异常,Java开发中经常遇到,是不是很绝望?这是在加载类的时候抛出来的,即在类路径下不能加载指定的类。
注意:在车道程序中不推荐使用throws把异常抛给系统处理,车道系统要捕获所有能捕获的异常并进行处理,目的是当发生程序级异常时要保证车道程序可以正常收费,不可因为程序异常影响到正在进行的收费处理。
异常处理的一般原则
1、 能处理就早处理,抛出不去还不能处理的异常就要想办法消化掉,或者转换为RuntimeException处理。因为对于一个应用系统来说,抛出大量异常是有问题的,应该从程序开发角度尽可能的控制异常发生的可能。
2、 对于检查异常,如果不能行之有效的处理,还不如转换为RuntimeException抛出。这样也让上层的代码有选择的余地――可处理也可不处理。
3、 对于一个应用系统来说,应该有自己的一套异常处理框架,这样当异常发生时,也能得到统一的处理风格,将优雅的异常信息反馈给用户。
异常的转译与异常链
1、异常转译的原理
所谓的异常转译就是将一种异常转换另一种新的异常,也许这种新的异常更能准确表达程序发生异常。
在Java中有个概念就是异常原因,异常原因导致当前抛出异常的那个异常对象,几乎所有带异常原因的异常构造方法都使用Throwable类型做参数,这也就为异常的转译提供了直接的支持,因为任何形式的异常和错误都是Throwable的子类。比如将SQLException转换为另外一个新的异常DAOException,可以这么写:
先自定义一个异常DAOException:
比如有一个SQLException类型的异常对象e,要转换为DAOException,可以这么写:
public class DAOException extends RuntimeException {
//(省略了部分代码)
public DAOException(String message, Throwable cause) {
super(message, cause);
}
}
DAOException daoEx = new DAOException ( "SQL异常", e);
异常转译是针对所有继承Throwable超类的类而言的,从编程的语法角度讲,其子类之间都可以相互转换。但是,从合理性和系统设计角度考虑,可将异常分为三类:Error、Exception、RuntimeException。
异常的处理存在着一套哲学思想:对于一个应用系统来说,系统所发生的任何异常或者错误对操作用户来说都是系统"运行时"异常,都是这个应用系统内部的异常。这也是异常转译和应用系统异常框架设计的指导原则。在系统中大量处理非检查异常的AxiTrader返佣www.fx61.com/brokerlist/axitrader.html负面影响很多,最重要的一个方面就是代码可读性降低,程序编写复杂,异常处理的代码也很苍白无力。因此,很有必要将这些检查异常Exception和错误Error转换为RuntimeException异常,让程序员根据情况来决定是否捕获和处理所发生的异常。
①:Error到Exception:将错误转换为异常,并继续抛出。例如Spring WEB框架中,将org.springframework.web.servlet.DispatcherServlet的doDispatch()方法中,将捕获的错误转译为一个NestedServletException异常。这样做的目的是为了最大限度挽回因错误发生带来的负面影响。因为一个Error常常是很严重的错误,可能会引起系统挂起。
②:Exception到RuntimeException:将检查异常转换为RuntimeException可以让程序代码变得更优雅,让开发人员集中精力设计更合理的程序代码,反过来也增加了系统发生异常的可能性。
③:Error到RuntimeException:目的还是一样的。把所有的异常和错误转译为不检查异常,这样可以让代码更为简洁,还有利于对错误和异常信息的统一处理。
1、 异常链
异常链顾名思义就是将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。Java API文档中给出了一个简单的模型:
try {
lowLevelOp();
} catch (LowLevelException le) {
throw (HighLevelException)
new HighLevelException().initCause(le);
}
当程序捕获到了一个底层异常le,在处理部分选择了继续抛出一个更高级别的新异常给此方法的调用者。这样异常的原因就会逐层传递。这样,位于高层的异常递归调用getCause()方法,就可以遍历各层的异常原因。这就是Java异常链的原理。异常链的实际应用很少,发生异常时候逐层上抛不是个好注意,上层拿到这些异常又能奈之何?而且异常逐层上抛会消耗大量资源,因为要保存一个完整的异常链信息。
相关文章
- 08-29异常处理工具类
- 08-29zookeeper的异常处理(Disconnected, SyncConnected, Expired)
- 08-29【转】【Salesforce】Apex 的异常处理
- 08-29k8s节点删除后重新加入异常处理
- 08-29阶段5 3.微服务项目【学成在线】_day18 用户授权_06-方法授权-方法授权测试-无权限异常处理
- 08-29Struts2 异常处理
- 08-2975、在继承中关于异常的处理
- 08-29异常处理关键字
- 08-29异常处理
- 08-29go语言异常处理