考虑两个类:
public class Point {
public int x;
public int y;
public Point(int xVal, int yVal) {
x = xVal;
y = yVal;
}
public Point(Point pt) {
x = pt.x;
y = pt.y;
}
}
public class BoundingBox {
public Point topLeft;
public Point bottomRight;
public BoundingBox(Point setTopLeft, Point setBottomRight) {
topLeft = new Point(setTopLeft);
bottomRight = new Point(setBottomRight);
}
}
BoundingBox应该如下所示制作传递给构造函数的点的副本,还是仅仅引用它们?如果它采用它们的参考值,只要BoundingBox存在,是否保证那些Point对象将存在?
答案 0 :(得分:3)
两个问题:
是否复制? 这取决于,如果该点将被共享 - 被其他线程使用,那么使副本更好;如果不共享,那么只需使用引用就可以获得微小的性能提升。
只要BoundingBox存在,是否可以保证这些Point对象存在? 是的,它由JVM保证。由于这些点是由BoundingBox引用的,因此它们不会被垃圾收集。
答案 1 :(得分:3)
您应该始终在类中保存已存储的可变对象的防御副本。你总是应该采取防御性的方式,假设每个使用你班级的人都会破坏它。
事实证明,虽然类本身可能是不可变的,但并不意味着它中的对象也是不可变的。你需要制作你在课堂上使用的可变对象的防御性副本。
以下是一个例子:
public class MyClass {
private Point foo;
private Point bar;
public MyClass(Point foo, Point bar) {
this.foo = foo;
this.bar = bar;
}
public Point foo() {
return foo;
}
public Point bar() {
return bar;
}
. . .
//Seems harmless enough?
//Lets destroy it
Point foo = new Point(1 ,2);
Point bar = new Point(3 ,4);
MyClass mc = new MyClass(foo, bar);
bar.x = 99; //<-- changes internal of mc!
这是因为对象MyClass只存储了一个指向传递给它的Point对象的指针,这意味着当可变对象被更改时 - 指向该对象的任何东西也会被更改。这可能会导致无意和无意的结果
要修复上述代码,您需要制作所有内容的defensie副本。重要的是要注意,应在任何参数检查发生之前进行复制(例如有效性检查)。您还需要确保更改了访问者,以便他们也返回该类内部的副本。
//Fixed version!
public MyClass(Point foo, Point bar) {
this.foo = new Point(foo.getLocation());
this.bar = new Point(bar.getLocation());
}
public Point foo() {
return new Point(foo.getLocation());
}
public Point bar() {
return new Point(bar.getLocation());
}
. . .
对于问题的第二部分 - 只要BoundingBox
存在,其中包含的对象也应该存在。 JVM不会垃圾收集它们,直到它们的所有引用都不再存在。
答案 2 :(得分:2)
如果你可以使Point对象不可变,那么你应该很好地仅对这些点进行引用。您可以通过将x,y字段声明为final来完成此操作:
public class Point {
public final int x;
public final int y;
public Point(int xVal, int yVal) {
x = xVal;
y = yVal;
}
public Point(Point pt) {
x = pt.x;
y = pt.y;
}
}
保持对可变对象的引用意味着如果topLeft,topRight引用被任何其他类更改,BoundingBox实例可能只是更改它的维度 - 导致很难调试的错误。
此外,Java引用计算对象,并且在释放所有引用之前不会销毁它们。因此,只要有对它们的引用,你的BoundingBox中的点就可以了。
答案 3 :(得分:1)
如果它采用它们的参考值,它是否保证那些 只要BoundingBox存在,点对象就会存在?
是的,点对象将继续存在,直到它们不再使用为止。这就是JVM垃圾收集为您所做的事情。
BoundingBox应该复制传递给它的点 如图所示的构造函数,还是仅仅引用它们?
你应该复制它们。否则,当其他人出现并且这样做时会发生什么:
Point topLeft = new Point(1, 2);
Point bottomRight = new Point(3, 4);
BoundingBox box = new BoundingBox(topLeft, bottomRight);
topLeft.x = 5; // Oops, this just changed box.topLeft.x
一般来说,避免“陷阱”是一种很好的做法 - 代码以不可预期的方式运行。即使你记得你的代码现在是什么,第一次忘记你的代码就会真的混淆。
答案 4 :(得分:1)
无论是对还是错。这一切都取决于您的应用程序的具体情况。如果你的边界框需要依赖点不要从它下面改变,那么你必须在构造函数中制作一个副本。但是你还需要确保只在任何getter方法中发布对象的副本(例如,如果你最终有getTopLeft(),getBottomRight()等。更一般地说,这是一个使用组合与聚合的问题在设计对象模型时,聚合并不意味着所有权。换句话说,聚合对象可以存在于其父对象范围之外,就像您只是存储传递给构造函数的相同点引用一样。使用对象组合,子对象的生命周期将与父对象的生命周期相同。这可以通过复制点来实现,然后小心不要传出存储点的引用(仅复制)。这样,一旦父对象消失了,孩子们也会离开。