栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

为什么在Java中从构造函数中调用方法被认为是不好的做法?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

为什么在Java中从构造函数中调用方法被认为是不好的做法?

首先,通常在构造函数中调用方法没有问题。这些问题特别涉及调用构造函数的类的可重写方法以及将对象的

this
引用传递给其他对象的方法(包括构造函数)的特殊情况。

避免重载方法和“泄漏

this
” 的原因可能很复杂,但它们基本上都与防止使用未完全初始化的对象有关。

避免调用可覆盖的方法

避免在构造函数中调用可重写方法的原因是Java语言规范(JLS)第12.5节中定义的实例创建过程的结果。

除其他事项外,第12.5节的过程确保了在实例化派生类[1]时,对其基类的初始化(即,将其成员设置为其初始值并执行其构造函数)在其自身的初始化之前进行。这旨在通过两个关键原则允许一致的类初始化:

  1. 每个类的初始化都可以专注于仅初始化它明确声明的成员,这是安全的,因为要知道从基类继承的所有其他成员都已被初始化。
  2. 每个类的初始化都可以安全地使用其基类的成员作为其自身成员的初始化的输入,因为可以确保在初始化该类时已对其进行了正确的初始化。

但是,有一个陷阱:Java允许在构造函数中进行动态分派[2]。这意味着,如果作为派生类实例化的一部分执行的基类构造函数调用了派生类中存在的方法,则会在该派生类的上下文中调用该方法。

所有这些的直接结果是,在实例化派生类时,将在派生类初始化之前调用基类构造函数。如果该构造函数调用了被派生类覆盖的方法,则 即使派生类尚未初始化
也将 调用派生类方法(而不是基类方法)。显然,如果该方法使用派生类的任何成员,则这是一个问题,因为它们尚未初始化。

显然,问题是基类构造函数调用方法的结果,而派生类可以重写这些方法。为防止此问题,构造函数应仅调用自己的最终,静态或私有类的方法,因为这些方法不能被派生类覆盖。最终类的构造函数可以调用其任何方法,因为(根据定义)它们不能从其派生。

JLS的示例12.5-2很好地说明了此问题:

class Super {    Super() { printThree(); }    void printThree() { System.out.println("three"); }}class Test extends Super {    int three = (int)Math.PI;  // That is, 3    void printThree() { System.out.println(three); }    public static void main(String[] args) {        Test t = new Test();        t.printThree();    }}

0
然后打印该程序
3
。本示例中的事件顺序如下:

  1. new Test()
    main()
    方法中被调用。
  2. 由于
    Test
    没有显式构造函数,因此
    Super()
    将调用其超类的默认构造函数(即)。
  3. Super()
    构造函数调用
    printThree()
    。这将分派到
    Test
    类中方法的重写版本。
  4. 该类的
    printThree()
    方法将
    Test
    打印
    three
    成员变量的当前值,这是默认值
    0
    (因为
    Test
    尚未初始化实例)。
  5. printThree()
    方法和
    Super()
    构造每个出口,和
    Test
    实例被初始化(在该点处
    three
    的一个设置到
    3
    )。
  6. main()
    方法
    printThree()
    再次调用,这一次将打印期望值
    3
    (因为
    Test
    实例已被初始化)。

如上所述,第12.5节指出,(2)必须在(5)之前发生,以确保

Super
早在…之前初始化
Test
。但是,动态分派意味着(3)中的方法调用在未初始化的
Test
类的上下文中运行,从而导致意外的行为。

避免泄漏
this

禁止

this
从构造函数传递到另一个对象的限制更容易解释。

基本上,在构造函数完成执行之前,不能认为对象已完全初始化(因为其目的是完成对象的初始化)。因此,如果构造函数将对象的传递

this
给另一个对象,则该另一个对象将具有对该对象的引用,即使该对象尚未完全初始化(因为其构造函数仍在运行)。如果另一个对象然后尝试访问未初始化的成员或调用依赖于其完全初始化的原始对象的方法,则可能会导致意外行为。

有关如何导致意外行为的示例,请参阅本文。


[1]从技术上讲,Java中的每个类除外

Object
都是派生类-我仅在这里使用术语“派生类”和“基类”来概述所讨论的特定类之间的关系。
[2]在JLS中(据我所知)没有任何理由说明这种情况。另一种选择-禁止在构造函数中进行动态分派-将使整个问题变得毫无意义,这可能正是C
++不允许这样做的原因。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/507156.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号