[JAVA]重写父类方法并向上转型时的初始化问题

其实是个小问题,不过蛮有意思的。

这个问题的提出,是今天朋友问我的一个问题。我来大概描述一下这个问题:

如何模仿安卓的Activity,设计一个父类,使得对象被创建(调用)时某些周期函数依次被调用,且该性质不因子类的重写而被破坏。举个例子:

open class sup(){
    //假设父类已实现A -> B -> C 的周期执行顺序
    open fun A(){
        println("f A")
    }
    open fun B(){
        println("f B")
    }
    open fun c(){
        println("f C")
    }
}
class sub:sup(){
    override fun A() {
        super.A()
        println("这行执行完再执行B()")
    }
    override fun B() {
        println("c B")
    }
}

应当预计输出:

f A
这行执行完再执行B()
c B
f C

其实这就是安卓Activity的几个生命周期,都很熟悉了:


我说很简单啊,在父类的构造或初始化函数中写个顺序就好了:

init {
    println("父类构造")
    A()
    B()
    C()
}

经过测试,这样写是没问题的,输出符合上面的预期。


然后他问了一句话,困扰了我不少时间(现在想来应该还是我对继承和重写的理解没有到位导致的问题):

他说,就算是向上转型,但这时候父类初始函数中写的A、B、C,为什么会是被重写过的子类中的A、B、C方法,子类不是还没构造么?如果子类A方法中要输出子类私有变量var1的值,但此时子类还没构造(假设子类var1在子类构造函数中赋值),岂不是直接就空了

我们都知道,构造时的执行顺序是:父类静态 -> 子类静态 -> 父类一般语句 -> 父类构造 -> 子类一般语句 -> 子类构造

他说的这个问题,好像有点道理啊...


可能文字说明比较不好理解,那我们转换成代码,结合红字标示的顺序来看一下这个问题:

public class Test {
    public static void main(String[] arg){
        sup1 s1 = new sub1();//会输出什么呢?
    }
}
class sup1{
    public sup1(){
        System.out.println("父类");
        A();
        B();
        C();
    }
    public void A(){
        System.out.println("f A");
    }
    public void B(){
        System.out.println("f B");
    }
    public void C(){
        System.out.println("f C");
    }
}
class sub1 extends sup1{
    public int var1 = 8;
    public sub1(){
        System.out.println("子类");
        var1 = 7 ;
    }
    public void A(){
        super.A();
        System.out.println(var1);
    }
}

所以我就开始想,难不成父类中构造方法,执行的是父类中的ABC,因为这样可以避免被子类重写后,抛出Null异常。毕竟上层如果不对下层开放,这么去处理,真的是个非常大的隐患:下层根本查不出来(先不考虑这么设计父类合理不合理)于是我想,Java应该会有避免机制的吧.....应该有的吧....


----------------

然而,上面这段代码最后输出:

父类
f A
0 // 不是8 也不是7 ; 同时也说明了执行了子类中重写的方法。0是默认赋值,但如果var1是个对象,这里就真的是空的。
f B
f C
子类
执行顺序没问题,但是值 var1确实空了。居然真的空了Orz......害怕.jpg

感觉不想点别的方法, 这会是个隐蔽性很高的雷...我拿据称Null控制机制做得很好的Kotlin去试了一下,在非"?"的情况下编译通过,但是也是抛空。也就是说,这是一个现有机制检测不出的Null隐患...
先马克一下,硬脆思婷。



PS:

我去看了安卓的源码,大概知道了他的处理方式。简单说一下,他不是放在构造函数里完成对象创建时自动按周期函数执行,而是通过一个ActivityMannager来处理。ActivityMannager管理一个任务栈,任务栈调用栈中某个Activity中的非构造函数callOnxxx()。这个函数类似于上面的init{},不太一样,但是本质上是一样的。

大概就像这样:

class MyActivity{
    public void onCreate(){
        System.out.println("F onCreate");
    }
    public void onStart(){
        System.out.println("F onStart");
    }
    public void onResume(){
        System.out.println("F onResume");
    }
    public void onPause(){
        System.out.println("F onPause");
    }
    public void onStop(){
        System.out.println("F onStop");
    }
    public void onDestory(){
        System.out.println("F onDestory");
    }
    public void onRestart(){
        System.out.println("F onRestart");
    }
    //任务栈调用唤醒周期函数callOnXX()
    public final void callOnCreate(){
        onCreate();
        callOnStart();
    }
    public final void callOnStart(){
        onStart();
        onResume();
    }
    public final void callOnPause(){
        onPause();
        onStop();
    }
    public final void callOnRestart(){
        onRestart();
        callOnStart();
    }
    public final void callOnResume(){
        onResume();
    }
}
//测试类
class TestActivity extends MyActivity{
    @Override
    public void onStart() {
        super.onStart();
        System.out.println("C onStart");
    }
}


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值