美文网首页
为什么不要在构造函数做太多事情

为什么不要在构造函数做太多事情

作者: going_hlf | 来源:发表于2020-10-27 01:23 被阅读0次

java构造器调用的层次结构带来了一个困境。如果在构造器中调用了正在构造的对象的动态绑定方法,会发生什么呢?
在普通的方法中,动态绑定的调用是在运行时解析的,因为编译期对象不知道它属于方法所在的类还是类的派生类。
如果在构造器中调用了动态绑定方法,就会用到那个方法的重写定义。然而,调用的结果难以预料因为被重写的方法在对象被完全构造出来之前已经被调用,这使得一些 bug 很隐蔽,难以发现。
从概念上讲,构造器的工作就是创建对象,且对象的初始化过程是先基类,再派生类。在构造器内部,整个对象可能只是部分形成——只知道基类对象已经初始化。如果构造器只是构造对象过程中的一个步骤,且构造的对象所属的类是从构造器所属的类派生出的,那么派生部分在当前构造器被调用时还没有初始化。然而,一个动态绑定的方法调用向外深入到继承层次结构中,它可以调用派生类的方法。如果你在构造器中这么做,就可能调用一个方法,该方法操纵的成员可能还没有初始化——这肯定会带来灾难。

class Demo {
    void Show() {
        System.out.println("Demo Show called");
    }
}

class Base {
    Base() {
        Demo obj = func();
        System.out.println("Base constructor called para = " + obj);
        obj.Show();
    }

    Demo func() {
        System.out.println("Base func called");
        return null;
    }
}

class Derived extends Base {
    private Demo obj;

    Derived() {
        System.out.println("Derived constructor called");
        obj = new Demo();
    }

    @Override
    Demo func() {
        System.out.println("Base func called");
        return obj;
    }
}

public class App {
    public static void main(String[] args) {
        Base base = new Derived();
    }
}

执行结果:

Base func called
Base constructor called para = null
Exception in thread "main" java.lang.NullPointerException
    at Base.<init>(App.java:11)
    at Derived.<init>(App.java:23)
    at App.main(App.java:37)

这一切,在编译期不会报任何错误。
逻辑方面我们已经做得非常完美,然而行为仍不可思议的错了,编译器也没有报错(C++ 在这种情况下会产生更加合理的行为)。像这样的 bug 很容易被忽略,需要花很长时间才能发现。
因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的 final 方法(这也适用于可被看作是 final 的 private 方法)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。

C++有些不一样:

#include <iostream>

struct TestObj {
    int para;
    void Show() {
        std::cout << "TestObj Show called" << endl; [1]
        std::cout << "TestObj Show called" << para << endl; [2]
    }
};

struct Base {
    Base() {
        std::cout << "Base construct called\n";
        Func()->Show();
    }

    virtual TestObj* Func()
    {
        std::cout << "Base Func called\n";
        return 0;
    }
};

struct Derived : Base{
    Derived() {
        std::cout << "Derived construct called\n";
    }

    TestObj* Func() override
    {
        std::cout << "Derived Func called\n";
        return new TestObj;
    }
};

int main()
{
    Derived obj;

    return 0;
}

[1]不加参数的运行结果

Base construct called
Base Func called
TestObj Show called
Derived construct called

Process finished with exit code 0

[2]加参数的运行结果,崩溃

Base construct called
Base Func called
TestObj Show called
Process finished with exit code -1073741819 (0xC0000005)
  1. 说明C++的构造函数的虚函数机制,在构造子类之前,虚表中还是基类自己的函数,所以不会崩溃。
  2. 返回空指针之所以不会崩溃,是因为它并没有操作数据,调用方法不涉及内存的问题,所以不会崩溃,加上数据操作后,程序崩溃。
  3. 如果将Base中的Func的默认实现去掉,改为纯虚函数,则编译错误。(这点优于java)

相关文章

  • 为什么不要在构造函数做太多事情

    java构造器调用的层次结构带来了一个困境。如果在构造器中调用了正在构造的对象的动态绑定方法,会发生什么呢?在普通...

  • Toast 源码实现 (完结)

    1. 从构造函数来看, 构造函数做的事情有: --> 指定 Toast 对应的 context 环境--> 创...

  • c++11 继承构造函数和委托构造函数

    1 继承构造函数 1.1 为什么需要继承构造函数 子类为完成基类初始化,在C++11之前,需要在初始化列表调用基类...

  • JavaScript原型

    创建对象的三种方式 字面量的方式 调用系统的构造函数 自定义构造函数方式 自定义构造函数创建对象做的事情 使用工厂...

  • 9月19日C++学习总结

    1.继承时的构造函数:(1)基类的构造函数不被继承,需要在派生类中自行声明。(2)声明构造函数时,只需要对本类中新...

  • Dart构造函数

    默认构造函数 不声明构造函数,则提供默认的无参构造,和java类似。 普通构造函数 另外一种构造方法写法:(减少代...

  • 虚函数

    常见的不不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。 1.为什么...

  • [flutter]2、class

    1、属性、方法 2、构造方法 构造函数可以不写函数体1、传入this属性的构造函数Person(this.age,...

  • Java基础 --- 构造函数

    1 概述 1-1 为什么会出现构造函数(作用) 对象不初始化,没办法使用,构造函数的作用,就是给对象进行初始化 ...

  • java构造函数

    Q: 构造函数应该是公有吗? 不,可以公有,可以私有,或者不指定 Q: 构造函数调用链路? 构造函数在新创建类时候...

网友评论

      本文标题:为什么不要在构造函数做太多事情

      本文链接:https://www.haomeiwen.com/subject/pqltvktx.html