Java异常机制与在游戏服务器开发中的应用
发表于2016-08-30
一、异常机制
异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
传统的处理异常的办法是,函数返回一个特殊的结果来表示出现异常(通常这个特殊结果是大家约定俗称的),调用该函数的程序负责检查并分析函数返回的结果。这样做有如下的弊端:例如函数返回-1代表出现异常,但是如果函数确实要返回-1这个正确的值时就会出现混淆;可读性降低,将程序代码与处理异常的代码混爹在一起;由调用函数的程序来分析错误,这就要求客户程序员对库函数有很深的了解。
异常处理的流程:
遇到错误,方法立即结束,并不返回一个值;同时,抛出一个异常对象 。
调用该方法的程序也不会继续执行下去,而是搜索一个可以处理该异常的异常处理器,并执行其中的代码 。
二、异常的分类
异常的分类:
异常的继承结构:基类为Throwable,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception,具体的RuntimeException继承RuntimeException。
Error和RuntimeException及其子类成为未检查异常(unchecked),其它异常成为已检查异常(checked)。
每个类型的异常的特点
Error体系 :
Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形。应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。如果出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。所以,在进行程序设计时,应该更关注Exception体系。
Exception体系包括RuntimeException体系和其他非RuntimeException的体系 :
RuntimeException:RuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等。处理RuntimeException的原则是:如果出现RuntimeException,那么一定是程序员的错误。例如,可以通过检查数组下标和数组边界来避免数组越界访问异常。
其他非RuntimeException(IOException等等):这类异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误。
三、链式异常(异常传递)
现在所有Throwable的子类子构造器中都可以接受一个cause对象作为参数,这个cause就异常原由,代表着原始异常,即使在当前位置创建并抛出行的异常,也可以通过这个cause追踪到异常最初发生的位置。下面举例说明一下:
public class MyException extends Exception{ private static final long serialVersionUID = 1L; public MyException(String msg){ super (msg); } public MyException(String msg ,Throwable cause){ super (msg, cause); } } public class MyLowerException extends Exception{ public MyLowerException(String str){ super (str); } } 运行: public class TestException { public static void testLower() throws MyLowerException{ throw new MyLowerException( "这是底层的异常" ); } public static void testUp() throws MyException{ try { testLower(); } catch (MyLowerException e) { // TODO Auto-generated catch block //e.printStackTrace(); MyException ex = new MyException( "aaaaaa" ,e); //ex.initCause(e); throw ex; } } public static void main(String[] args) { try { testUp(); } catch (MyException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println( "1->" + e.getMessage()); System.err.println( "2->" + e.getCause()); } } } |
结果:
四、异常使用的建议
异常处理不能代替简单的程序逻辑
在游戏中如果我们使用redis做缓存的话,可能会遇到这样的需求:从一个sortset集合中获取一个玩家排行榜的积分,有以下两种实现方法:
Double result = jedis.zscore(key, member); if (result == null ) { return - 1 ; } else { return result.intValue(); } |
Double result = jedis.zscore(key, member); try { result.intValue(); } catch (NullPointerException e){ return 0 ; } |
这两种方法我们推荐使用第一种,因为异常检测相对于来说是非常耗时的。因此请只在异常情况下使用异常。
不要过分的细化异常
有些程序员习惯把每条语句都放在一个单独的Try/catch中。这种编码方式将导致异常急剧膨胀,如果程序中有多个操作,当任务一个操作出现问题时,整个任务就会被取消,则可以将它们放在同一个try中,这样程序代码会更清晰,同时也达到了将正确处理与错误处理分开的异常处理目标。
充分利用异常层次的结构
不要只抛出RuntimeException异常,应该寻找更加适当的子类或者创建自己的异常类。在程序里尽量不要捕获Exception以及更抽象的异常。这样的异常携带的信息量太少,会使程序代码更加难读和维护。
合理使用传递异常
我们是否应该捕获抛出的所有异常呢?我们一般都采取分层结果,每层都可能抛出异常,一般情况下,建议将异常向上传递,让高层次的方法告知用户发生的错误,或者不成功的命令更加适宜。
例如,在游戏服务器开发中,我们一般分为几个层次:1,Command处理层,2,逻辑处理层;3,缓存层;4,数据持久化层。我们举个例子,当缓存层的redis出现异常时(比如网络闪断或长时间连接不上),这时候我们就可以抛出一个自定义的RedisFailedException异常,因为我们没办法在此处理失败的业务。比如一个查询失败了,如果在缓存捕获异常,我们只能返回null,可是上层业务可能根据结果是null认为缓存不存想要的数据,而去数据库查询了,可能会出现脏数据。而返回异常,可以让上层视情况而定。
本文来自游戏技术网:http://www.youxijishu.com