由Java中toString()方法引发的无意识的递归想到的

先看一段很简单的java代码:

toString()/**

 * @author jeffwong
 */
public class InfiniteRecursion {
    public String toString(){
        return "InfiniteRecursion address "+ this + "\n"; 
    }

    public static void main(String[] args) {      
        InfiniteRecursion obj = new InfiniteRecursion();
        System.out.println(obj);
    }
}

 

运行后,我们看到了一串异常信息,StackOverflow了:

javaoverflowexception

爆栈的原因通常是因为递归或者无限循环,上面的代码造成堆栈溢出的直接原因是递归,下面来分析一下造成递归的原因:

定义类InfiniteRecursion,本来希望通过toString方法打印出当前对象的内存地址,获取当前对象当然会想到this关键字。在toString方法内,Java编译器发现一个字符串后面跟着一个加号,加号后面的对象不是String类型,所以编译器试着自动将this转换为字符串类型,也就是调用this.toString()方法,偏偏this所指代的这个对象重新实现了toString方法,于是造成循环不断地调用toString方法,这样就发生了递归调用。

上面这种toString()被Bruce Eckel总结称为无意识的递归,在.NET当中同样适用(重写ToString方法),类似代码我就不贴了。

这种无意识的递归说到底还是使用类继承不当造成的。也许你会说其实这根本没有什么技术难度的,类继承用对了不就行了吗,不就是继承自Object的区区一个ToString方法吗?

如果你手上维护的代码是一个基类代码丰富,继承链很深,含有大量包装类,类调用关系复杂……用对就行了,说起来未免太容易。再说一个由类继承引发的血案的真实案例:

用户反馈我司某重要业务部门的线上系统某页面无法正常打开,直观效果就是页面一直loading迟迟打不开。开发人员费了很大力气据说还动用了Windbg神器和传说中的架构师才排查到是一个页面方法调用的问题,为了说明问题我们暂时命名该页面为A,页面A继承自自定义基类页面B,然后出现问题的原因大致分析如下:

1、A页面某方法func1触发调用了基类B的另一个方法func2;

2、B中的方法func2执行的时候,发现被A重写了,所以就去执行A重写的方法func2;

3、A重写的func2内部又调用了func1,这样就重新触发A的func1的执行。

这样,一个神不知鬼不觉惊天地泣鬼神的无意识递归就形成了。

饱满而真实的类继承的副作用的经典案例介绍到此结束。

明天,你还会用类继承吗,你还敢用类继承吗,你还忍心用类继承吗?

标签