Java:假设对象引用或进行复制

时间:2011-11-08 05:34:34

标签: java reference

考虑两个类:

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对象将存在?

5 个答案:

答案 0 :(得分:3)

两个问题:

  1. 是否复制? 这取决于,如果该点将被共享 - 被其他线程使用,那么使副本更好;如果不共享,那么只需使用引用就可以获得微小的性能提升。

  2. 只要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()等。更一般地说,这是一个使用组合与聚合的问题在设计对象模型时,聚合并不意味着所有权。换句话说,聚合对象可以存在于其父对象范围之外,就像您只是存储传递给构造函数的相同点引用一样。使用对象组合,子对象的生命周期将与父对象的生命周期相同。这可以通过复制点来实现,然后小心不要传出存储点的引用(仅复制)。这样,一旦父对象消失了,孩子们也会离开。