我正在处理一个包含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
方法阻止它运行直到构造函数完成?
答案 0 :(得分:1)
构造函数中的this
泄漏只是一个警告,因为可能导致问题。如果super
的变量已初始化,但this
之后修改了它们,但super
自身泄漏,则有人正在使用尚未完全初始化的变量。
如果您确定在构建时没有人访问任何变量,并且没有其他方法可以执行此操作(因为iE外部库阻止您以正确的方式执行),那么可以忽略警告。
在你的情况下理论上可能的是,在调用sprite.getWidth(this)
期间,图像会立即调用观察者来更新进度,因此在构造函数完成之前调用imageUpdate
。在这种情况下,偏移变量将在之后由构造函数初始化覆盖。不,同步不会阻止这个问题,因为在那一刻没有其他人持有锁。
有几种方法可以解决这个问题:
使用BufferedImage,它不需要getWidth / getHeight的观察者。
下行:图像必须满载,在某些情况下可能会引入一个小延迟(如果iE通过网络加载。
用户正确锁定:
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;
}
下行:这将调用synchronized,这可能会导致轻微的延迟。你通常不会注意到它只被调用了几次,但在一个时间关键的循环中,这可能是显而易见的。
使用外部函数在构造完成后更新数据:
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()
方法,在此之前对象未完全初始化,这可能会导致错误或要求您编写构建器方法,这会使事情变得更加复杂。