【问题标题】:Java : Assume object reference or make copyJava:假设对象引用或复制
【发布时间】:2011-11-08 05:34:34
【问题描述】:

考虑两个类:

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

【问题讨论】:

  • 我认为只有原始类型会通过值传递。其他的将通过引用传递。
  • 我知道,但我需要知道是复制这些对象中的值(将其克隆到另一个新对象),还是保留它们的引用并继续使用它们
  • 虽然和这个问题有很大关系,关于'pass by reference'和'pass by value'的争论确实很乏味,但我想说没有'在 java 中通过引用传递。看看:stackoverflow.com/questions/7301637/…
  • 是的,我喜欢将其视为不可变引用。对象将继续以自己的方式存在,而不依赖于变量。

标签: java reference


【解决方案1】:

两个问题:

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

  2. 只要 BoundingBox 存在,是否保证那些 Point 对象会存在? 是的,它由 JVM 保证。由于这些点被 BoundingBox 引用,它们不会被垃圾回收。

【讨论】:

  • 谢谢。这就是我需要知道的,因为我的对象正在多线程应用程序中使用。我认为它可能会为垃圾收集保留某种引用计数,但我不确定。并且创建 Point 对象的线程可能不复存在。
  • 对象的生命周期与创建它的线程无关。一个对象是否被垃圾回收仅仅取决于它是否被引用。
【解决方案2】:

您应该始终为类中存储的可变对象制作防御性副本。您应该始终防御性地编程,假设使用您的类的每个人都会破坏它。

这可以通过以下事实来证明:尽管类本身可能是不可变的,但并不意味着其中的对象也是不可变的。您需要为您在类中使用的可变对象制作防御性副本。

这是一个例子:

    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 对象的指针,这意味着当可变对象发生更改时 - 指向该对象的任何内容也会更改。这可能会导致意外和意想不到的结果

要修复上述代码,您需要对所有内容进行防御性复制。重要的是要注意,在进行任何参数检查(例如有效性检查)之前应该进行复制。您还需要确保您的访问器已更改,以便它们也返回类内部的副本。

         //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 不会对它们进行垃圾收集。

【讨论】:

    【解决方案3】:

    如果您可以使 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 中的点就可以了。

    【讨论】:

    • 这也很有帮助。从来没想过。尽管我更像是 C 程序员(函数式语言),但我的大脑似乎会想到除了显而易见的事情之外的所有事情。正在创建的对象不会在其他地方更改,因此它们很可能是不可变的。可能会考虑。
    【解决方案4】:

    如果假设它们的参考值,是否保证那些 只要BoundingBox存在,Point对象就存在?

    是的,点对象将继续存在,直到不再使用。这就是 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
    

    一般来说,避免“陷阱”是一种很好的做法——代码以意想不到的方式工作。即使您现在还记得您的代码的陷阱是什么,当您第一次忘记一个时,您会真的感到困惑。

    【讨论】:

    • 哇,我不敢相信我没有想到显而易见的事情......幸好你提出来了。确实很好。
    • 另一方面,您可以使 Point 不可变(通过将 x 和 y 设为“final”),这将避免像 Java 的“String”类那样的问题。
    【解决方案5】:

    这两种方式都没有对错。这完全取决于您的应用程序的具体情况。如果您的边界框需要依赖点不从其下方更改,那么您必须在构造函数中进行复制。但是您还需要确保仅在任何 getter 方法中发布对象的副本(例如,如果您最终拥有 getTopLeft()、getBottomRight() 等。更一般地说,这是一个使用组合还是使用聚合的问题在设计对象模型时。聚合并不意味着所有权。换句话说,聚合对象可以存在于其父级范围之外,例如您只需存储传递给构造函数的相同点引用。使用对象组合,子对象的生命周期将与父对象的生命周期相同。这是通过复制点来实现的,然后注意不要传递存储点的引用(仅副本)。这样,一旦父对象消失,子对象也将消失。

    【讨论】:

      猜你喜欢
      • 2013-02-14
      • 2014-05-16
      • 2014-07-14
      • 2021-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-21
      相关资源
      最近更新 更多