我正在上一个类,其中包含一个Image并将其绘制为以0,0为中心;为此,它检索图像的高度和宽度,并将其显示偏移基于这些值。但是,如果将其设置为ImageObserver以防图像尚未完全加载,则会在构造函数中泄漏this
:
public class Sprite extends SimplePaintable implements ImageObserver {
private final Image sprite;
private int xOffset;
private boolean xOffsetSet;
private int yOffset;
private boolean yOffsetSet;
public Sprite(Image sprite) {
this.sprite = sprite;
//warning: leaking this in constructor
int width = sprite.getWidth(this);
xOffset = width/2;
xOffsetSet = width != -1;
int height = sprite.getHeight(this);
yOffset = height/2;
yOffsetSet = height != -1;
}
@Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
assert img == sprite;
if ((infoflags & WIDTH) != 0) {
xOffset = width / 2;
xOffsetSet = true;
}
if ((infoflags & HEIGHT) != 0) {
yOffset = height / 2;
yOffsetSet = true;
}
return !(xOffsetSet && yOffsetSet);
}
...
一开始我认为这很好,因为仅偏移量变量未初始化,并且其默认值适合(不)显示未加载的图像,但是后来我意识到,如果立即调用图像作为
getWidth(this)
加载的图像,则理论上可以调用imageUpdate
在构造函数完成之前,导致偏移量在imageUpdate
中正确设置,然后由构造函数取消设置。这是一个问题,还是仅在EDT上同步加载图像?如果这是一个问题,那么使imageUpdate成为synchronized
方法会阻止它在构造函数完成之前运行吗? 最佳答案
构造函数中的this
泄漏只是警告,因为它可能会导致问题。如果super
变量被初始化,但是this
之后对其进行了修改,但是super
自身泄漏了,那么有人正在使用尚未完全初始化的变量。
如果您确定在构造时不会有人访问任何变量,并且没有其他方法可以做到这一点(因为外部库阻止您以正确的方式进行操作),那么可以忽略该警告。
在您的情况下,从理论上讲,在调用sprite.getWidth(this)
时,图像会调用观察者以立即更新进度,因此在构造函数完成之前调用imageUpdate
。在这种情况下,偏移量变量将在以后由构造函数初始化覆盖。不,同步不会阻止该问题,因为在此刻没有其他人握住该锁。
有几种方法可以解决此问题:
private final Object offsetLock = new Object();
public Sprite(Image sprite) {
this.sprite = sprite;
synchronized(offsetLock) {
int width = sprite.getWidth(this);
xOffset = width/2;
xOffsetSet = width != -1;
int height = sprite.getHeight(this);
yOffset = height/2;
yOffsetSet = height != -1;
}
}
@Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
assert img == sprite;
if ((infoflags & WIDTH) != 0) {
synchronized(offsetLock) {
xOffset = width / 2;
xOffsetSet = true;
}
}
if ((infoflags & HEIGHT) != 0) {
synchronized(offsetLock) {
yOffset = height / 2;
yOffsetSet = true;
}
}
return xOffsetSet && yOffsetSet;
}
缺点:这将调用同步,这可能会导致一些延迟。您通常不会注意到是否只调用了几次,但是在时间紧迫的循环中这可能会很明显。
public Sprite(Image sprite) {
this.sprite = sprite;
}
protected updateOffsets() {
updateWidth(sprite.getWidth(this));
updateHeight(sprite.getHeight(this));
}
protected updateWidth(final int newWidth) {
if (newWidth != -1) {
xOffset = newWidth/2;
xOffsetSet = true;
}
}
protected updateHeight(final int newHeight) {
if (newHeight!= -1) {
yOffset = newHeight/2;
yOffsetSet = true;
}
}
@Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
assert img == sprite;
if ((infoflags & WIDTH) != 0) {
updateWidth(width);
}
if ((infoflags & HEIGHT) != 0) {
updateHeight(height);
}
return xOffsetSet && yOffsetSet;
}
缺点:有人必须调用
updateOffsets()
方法,并且在此对象未完全初始化之前,这可能会导致错误或要求您编写一个builder方法,从而使事情更加复杂。