我曾经认为,直观地说,Java中的构造函数是构成对象的东西,并且在构造函数返回之前没有任何东西可以触及该对象。但是,我一遍又一遍地证明了这一点:
this
所有这些事实都违反了我对构造函数的直觉。
我再也无法自信地说出构造函数在Java中实际做了什么,或者它的用途是什么。如果我用所有最终字段创建一个简单的DTO,那么我可以理解构造函数的用途是什么,因为这与C中的结构完全相同,除非它无法修改。除此之外,我不知道在Java中可以可靠地使用哪些构造函数。它们只是一种惯例/句法糖吗? (即如果只有工厂为您初始化对象,则只有X x = new X()
,然后修改x
中的每个字段以使它们具有非默认值 - 鉴于上述3个事实,这将是几乎相当于Java的实际情况)
我可以命名两个实际上由构造函数保证的属性:如果我X x = new X()
,那么我知道x
是X
的实例,但不是{{1}的子类它的最终字段已完全初始化。你可能想说你知道X
的构造函数已经完成并且你有一个有效的对象,但是如果你将X
传递给另一个线程 - the other thread may see the uninitialized version(即你是什么),这是不正确的刚才所说的与调用工厂的保证没有什么不同。构造函数实际上保证了哪些其他属性?
答案 0 :(得分:6)
所有这些事实都违反了我对构造函数的直觉。
他们不应该。构造函数完全按照您的想法执行。
1:通过共享此
可以泄露未初始化的对象3:未初始化的对象在完全构造之前可以泄露给另一个线程
this
泄漏,构造函数中的启动线程以及存储新构造的对象的问题,其中多个线程在没有同步的情况下访问它,这些都是非final(和非final)初始化重新排序的问题 - 易失性)字段。但是初始化代码仍由构造函数完成。构造对象的线程完全看到对象。这是关于这些更改在其他线程中可见的时候,这是由语言定义无法保证的。
你可能很想说你知道X的构造函数已经完成并且你有一个有效的对象,但如果你将X传递给另一个线程,那么这是不正确的 - 另一个线程可能会看到未初始化的版本(即你刚刚说了什么)与调用工厂的保证没什么不同。
这是对的。如果你有一个不同步的对象并且你在一个线程中改变它也是正确的,其他线程可能会或可能不会看到突变。这是线程编程的本质。即使构造函数也不能安全地无需正确同步对象。
2:未初始化的对象可能会被从终结器访问它的子类泄露
本文档讨论的是终结器,并且在被垃圾收集后不正确地访问对象。通过黑客子类和终结器,你可以生成一个没有正确构造的对象,但这是一个很大的黑客攻击。对我来说,这不会以某种方式挑战构造函数的作用。相反,它展示了现代,成熟的JVM的复杂性。该文档还说明了如何编写代码来解决这个问题。
Java中的构造函数保证了哪些属性?
根据定义,构造函数:
就你的3个问题而言,#1和#3同样是关于非最终和非易失性字段的初始化是由构造该对象的线程以外的线程看到的。无法保证不同步的可见性。
#2问题显示了一种机制,如果在执行构造函数时抛出异常,则可以覆盖finalize方法以获取和不正确构造的对象。构造函数指向1-5已发生。通过hack,你可以绕过6的一部分。如果这会挑战构造函数的身份,我认为这是在旁观者眼中。
答案 1 :(得分:1)
12.5。创建新的类实例
在对新创建的对象的引用之前返回为 结果,处理指示的构造函数以初始化new 使用以下过程对象:
将构造函数的参数分配给此构造函数调用的新创建的参数变量。
如果此构造函数以同一类中的另一个构造函数的显式构造函数调用(第8.8.7.1节)开头(使用此),则 评估参数并处理构造函数调用 递归地使用这五个相同的步骤。如果那个构造函数 调用突然完成,然后此过程突然完成 出于同样的原因;否则,继续步骤5.
这个构造函数不是以同一个类中另一个构造函数的显式构造函数调用开始的(使用它)。如果 这个构造函数用于Object以外的类,然后是这个 构造函数将以a的显式或隐式调用开始 超类构造函数(使用超级)。评估参数和 使用这些来递归地处理超类构造函数调用 同样的五个步骤。如果该构造函数调用突然完成, 然后由于同样的原因,这个程序突然完成。除此以外, 继续第4步。
为此类执行实例初始值设定项和实例变量初始值设定项,分配实例变量的值 初始化器到相应的实例变量中 从左到右的顺序,它们在源代码中以文本形式出现 为了上课。如果执行任何这些初始化程序导致 异常,然后没有处理进一步的初始化程序 程序突然完成同样的异常。除此以外, 继续第5步。
执行此构造函数的其余部分。如果该执行突然完成,则此过程突然完成 出于同样的原因。否则,此过程正常完成。
**
与C ++不同,Java编程语言在创建新类实例期间未指定方法> dispatch的更改规则。如果调用的方法在被初始化的对象的子类中被重写,则使用这些重写方法>甚至在新对象完全初始化之前。
来自JLS 16.9:
请注意,没有规则可以让我们得出结论V是 在实例变量初始化程序之前肯定是未分配的。我们可以 非正式地得出结论,V在任何之前都不是绝对未分配的 C的实例变量初始化程序,但不需要这样的 规则明确说明。
读取构造的线程中对象的最终字段 该对象按照初始化顺序排序 通常发生在规则之前的构造函数中的字段。如果 读取发生在构造函数中设置字段后,它会看到 值为最终字段分配,否则它将看到默认值 值。
答案 2 :(得分:0)
一个类包含被调用以从类蓝图创建对象的构造函数。
This是Oracle对构造函数的描述。
现在到了你的观点。
直观地说,Java中的构造函数是创建对象的东西,并且在构造函数返回之前没有任何东西可以触及该对象。
所以根据官方文件,你的假设是不对的。点 1
和 2
是滥用Java的规则和行为,除非您有意识地泄漏您的对象!由于与Constructor
无关,我将跳过讨论这些观点。
现在,如果我们谈论您的 3rd
点,在多线程环境中,没有什么可以保证您的代码的一致性,除非“正确同步的块“或”原子指令“。由于对象创建不是synchronized
或atomic
指令,因此无法保证一致! Constructor
无法解决这个问题。换句话说,Constructor
无法让您的对象创建atomic
。
现在,你的问题的答案, “构造函数实际上保证了哪些其他属性?” 有点容易。 Constructors
仅仅是特殊类型的方法,在从类的蓝图创建对象期间调用。所以它什么都不能保证,除非你给它一个机会像其他任何方法一样执行。它一直被执行后,它可以保证您的对象被创建并按照您的需要进行初始化。
答案 3 :(得分:-1)
构造函数是java,它只是用来初始化创建的对象的状态。还有更多。