Java异常机制与在游戏服务器开发中的应用

发布时间: 2018-05-06

一, 异常机制

     异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。

     传统的处理异常的办法是,函数返回一个特殊的结果来表示出现异常(通常这个特殊结果是大家约定俗称的),调用该函数的程序负责检查并分析函数返回的结果。这样做有如下的弊端:例如函数返回-1代表出现异常,但是如果函数确实要返回-1这个正确的值时就会出现混淆;可读性降低,将程序代码与处理异常的代码混爹在一起;由调用函数的程序来分析错误,这就要求客户程序员对库函数有很深的了解。

异常处理的流程:

① 遇到错误,方法立即结束,并不返回一个值;同时,抛出一个异常对象 。

② 调用该方法的程序也不会继续执行下去,而是搜索一个可以处理该异常的异常处理器,并执行其中的代码 。

二, 异常的分类

异常的分类:

① 异常的继承结构:基类为ThrowableErrorException继承ThrowableRuntimeExceptionIOException等继承Exception,具体的RuntimeException继承RuntimeException

 ErrorRuntimeException及其子类成为未检查异常(unchecked),其它异常成为已检查异常(checked)。

每个类型的异常的特点

Error体系 :

     Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形。应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。如果出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。所以,在进行程序设计时,应该更关注Exception体系。

Exception体系包括RuntimeException体系和其他非RuntimeException的体系 :

 RuntimeExceptionRuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等。处理RuntimeException的原则是:如果出现RuntimeException,那么一定是程序员的错误。例如,可以通过检查数组下标和数组边界来避免数组越界访问异常。

②其他非RuntimeExceptionIOException等等):这类异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误。

三,链式异常(异常传递)

现在所有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());
}
}
}

结果:

四,异常使用的建议

1,异常处理不能代替简单的程序逻辑

  在游戏中如果我们使用redis做缓存的话,可能会遇到这样的需求:从一个sortset集合中获取一个玩家排行榜的积分,有以下两种实现方法:

(1)

Double result = jedis.zscore(key, member);

if (result == null) {
return -1;
} else {
return result.intValue();
}


(2)

Double result = jedis.zscore(key, member);
try{
result.intValue();
}catch(NullPointerException e){
return 0;
}


 这两种方法我们推荐使用第一种,因为异常检测相对于来说是非常耗时的。因此请只在异常情况下使用异常。

2,不要过分的细化异常

 有些程序员习惯把每条语句都放在一个单独的Try/catch中。这种编码方式将导致异常急剧膨胀,如果程序中有多个操作,当任务一个操作出现问题时,整个任务就会被取消,则可以将它们放在同一个try中,这样程序代码会更清晰,同时也达到了将正确处理与错误处理分开的异常处理目标。

3,充分利用异常层次的结构

  不要只抛出RuntimeException异常,应该寻找更加适当的子类或者创建自己的异常类。在程序里尽量不要捕获Exception以及更抽象的异常。这样的异常携带的信息量太少,会使程序代码更加难读和维护。

4,合理使用传递异常

 我们是否应该捕获抛出的所有异常呢?我们一般都采取分层结果,每层都可能抛出异常,一般情况下,建议将异常向上传递,让高层次的方法告知用户发生的错误,或者不成功的命令更加适宜。

例如,在游戏服务器开发中,我们一般分为几个层次:1,Command处理层,2,逻辑处理层;3,缓存层;4,数据持久化层。我们举个例子,当缓存层的redis出现异常时(比如网络闪断或长时间连接不上),这时候我们就可以抛出一个自定义的RedisFailedException异常,因为我们没办法在此处理失败的业务。比如一个查询失败了,如果在缓存捕获异常,我们只能返回null,可是上层业务可能根据结果是null认为缓存不存想要的数据,而去数据库查询了,可能会出现脏数据。而返回异常,可以让上层视情况而定。


请在下方留下您的评论.加入TG吹水群