超车的危险到底是什么';这';来自Java构造函数?
因此,在Java中,从构造函数传递超车的危险到底是什么';这';来自Java构造函数?,java,Java,因此,在Java中,从构造函数传递这个似乎不是一个好主意 class Foo { Foo() { Never.Do(this); } } 我的简单问题是:为什么 关于Stackoverflow有一些相关的问题,但没有一个给出可能出现的问题的完整列表 例如,要求解决此问题的解决方案指出: 例如,如果您的类有一个final(非静态)字段,那么您通常可以依赖于它被设置为一个值并且从不更改 当您查看的对象当前正在执行其构造函数时,则该保证不再成立 怎么样 此外,我理解子
这个似乎不是一个好主意
class Foo {
Foo() {
Never.Do(this);
}
}
我的简单问题是:为什么
关于Stackoverflow有一些相关的问题,但没有一个给出可能出现的问题的完整列表
例如,要求解决此问题的解决方案指出:
例如,如果您的类有一个final(非静态)字段,那么您通常可以依赖于它被设置为一个值并且从不更改
当您查看的对象当前正在执行其构造函数时,则该保证不再成立
怎么样
此外,我理解子类化是一个大问题,因为超类构造函数总是在子类构造函数之前调用,这可能会导致问题
此外,我读到可能会出现这种情况,但没有详细说明
还有哪些问题可能会发生?您能否详细说明上述问题?基本上,您已经列举了可能发生的坏事,因此您已经部分回答了自己的问题。我将提供您提到的事项的详细信息:
在初始化最终字段之前分发此
例如,如果您的类有一个final(非静态)字段,那么您可以
通常取决于它被设置为一个值并且从不改变
当您查看的对象当前正在执行其构造函数时,
那么,这种保证就不再成立了
怎么样
非常简单:如果在设置最终
字段之前分发此
,则不会设置该字段,但:
class X{
final int i;
X(){
new Y(this); // ouch, don't do this!
i = 5;
}
}
class Y{
Y(X x){
assert(x.i == 5);//This assert should be true, since i is final field, but it fails here
}
}
很简单,对吧?ClassY
看到一个带有未初始化的final
字段的X
。这是一个大禁忌
Java通常确保final
字段只初始化一次,并且在初始化之前不会读取该字段。一旦您泄漏此
,此担保将消失
请注意,同样的问题也会发生在同样糟糕的非final
字段上。然而,如果发现final
字段未初始化,人们会更加惊讶
子类别化
子类化的问题与上面的问题非常相似:基类在派生类之前被初始化,因此如果泄漏基类构造函数中的this
引用,则泄漏尚未初始化其派生字段的对象。这个可以
在多态方法的情况下变得非常糟糕,如本例所示:
class A{
static void doFoo(X x){
x.foo();
}
}
class X{
X(){
A.doFoo(this); // ouch, don't do this!
}
void foo(){
System.out.println("Leaking this seems to work!");
}
}
class Y extends X {
PrintStream stream;
Y(){
this.stream = System.out;
}
@Overload // Polymorphism ruins everything!
void foo(){
// NullPointerException; stream not yet initialized
stream.println("Leaking + Polymorphism == NPE");
}
}
如您所见,有一个类X
,带有foo
方法X
泄漏到其构造函数中的A
,并且A
调用foo
。对于X
类,这很好。但是对于Y
类,会抛出NullPointerException
。原因是Y
覆盖foo
,并在其中使用它的一个字段(stream
)。由于当A
调用foo
时,stream
尚未初始化,因此您将收到异常
此示例显示了泄漏此的下一个问题:即使基类在泄漏This
时工作正常,但从基类继承的类(可能不是您编写的,但其他人不知道泄漏的This
)可能会毁掉一切
将此泄漏给您自己
这一节并没有确切地讨论一种自己的问题,但要记住一件事:即使调用自己的一个方法也可以被视为泄漏的This
,因为它会带来与泄漏到另一个类的引用类似的问题。例如,考虑前面的示例,使用不同的<代码> x/COD>构造函数:
X(){
// A.doFoo();
foo(); // ouch, don't do this!
}
现在,我们不会把这个泄露给A
,而是通过调用foo
泄露给我们自己。同样,同样的坏事也会发生:重写foo()
并使用自己的字段的类Y
将造成严重后果
现在考虑我们的第一个例子:<代码>最终< /代码>字段。同样,通过方法泄漏给您自己可能允许查找未初始化的
final
字段:
class X{
final int i;
X(){
foo();
i = 5;
}
void foo(){
assert(i == 5); // Fails, of course
}
}
当然,这个例子是非常复杂的。每个程序员都会注意到,首先调用foo
,然后设置i
是错误的。但是现在再次考虑继承:您的<代码> x.Foo.()/Case>方法可能甚至不使用<代码> i <代码>,因此在初始化<代码> i<代码>之前调用是很好的。然而,子类可能重写foo()
并在其中使用i
,再次破坏一切
还请注意,被重写的foo()
方法可能会通过将其传递给其他类而进一步泄漏this
。因此,虽然我们只想通过调用foo()
来泄漏这个
,但子类可能会覆盖foo()
并向全世界发布这个
调用自己的方法是否被认为是泄漏的,这可能是有争议的。然而,正如您所看到的,它带来了类似的问题,所以我想在这里讨论它,即使许多人可能不同意调用自己的方法被认为是泄漏的this
如果确实需要在构造函数中调用自己的方法,那么可以只使用final
或static
方法,因为这些方法不能被无辜的派生类重写
并发性
Java内存模型中的最后一个字段具有一个很好的特性:它们可以在不锁定的情况下并发读取。JVM必须确保即使是并发的解锁访问也会始终看到一个完全初始化的final
字段。举例来说,这可以通过在指定最终字段的构造函数末尾添加内存屏障来实现但是,一旦过早分发此,此保证将消失。
再次举例:
class X{
final int i;
X(Y y){
i = 5;
y.x = this; // ouch, don't do this!
}
}
class Y{
public static Y y;
public X x;
Y(){
new X(this);
}
}
//... Some code in one thread
{
Y.y = new Y();
}
//... Some code in another thread
{
assert(Y.y.x.i == 5); // May fail!
}
如你所见,我们再次握手
{
Y.y = new Y();
}
{
r1 = 'allocate memory for Y' // Constructor of Y
r1.x = new X(r1); // Constructor of Y
Y.y = r1;
}
{
r1 = 'allocate memory for Y' // constructor of Y
r2 = 'allocate memory for X' // constructor of X
r2.i = 5; // constructor of X
r1.x = r2; // constructor of X
Y.y = r1;
}
{
r1 = 'allocate memory for Y' // 1.
r2 = 'allocate memory for X' // 2.
r1.x = r2; // 3.
Y.y = r1; // 4.
r2.i = 5; // 5.
}