Java: принять ссылку на объект или сделать копию - PullRequest
0 голосов
/ 08 ноября 2011

Рассмотрим два класса:

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 скопировать точки, переданные в его конструктор, как показано, или просто взять ссылку на них? Если он принимает свои ссылочные значения, гарантируется ли, что эти объекты Point будут существовать до тех пор, пока существует BoundingBox?

Ответы [ 5 ]

3 голосов
/ 08 ноября 2011

Вы всегда должны делать защитные копии хранящихся изменяемых объектов в ваших классах. Вы всегда должны программировать с защитой, предполагая, что каждый, кто использует ваш класс, сломает его.

Это подтверждается тем фактом, что, хотя сам класс может быть неизменным, это не означает, что объекты в нем тоже неизменны. Вам нужно делать защитные копии изменяемых объектов, которые вы используете в своем классе.

Вот пример:

    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 голосов
/ 08 ноября 2011

два вопроса:

  1. сделать копию или нет? Это зависит, если точка будет разделена - будет использоваться другими потоками, тогда лучше сделать копию; Если не использовать совместно, то просто использование ссылки может привести к незначительному увеличению производительности.

  2. Гарантируется ли, что эти объекты Point будут существовать до тех пор, пока существует BoundingBox? да, это гарантируется JVM. поскольку на точки ссылается BoundingBox, они не будут собирать мусор.

2 голосов
/ 08 ноября 2011

Если вы можете сделать объект Point неизменным , то у вас все будет в порядке, принимая ссылки только на точки.Вы можете сделать это, объявив поля x, y как окончательные, как показано ниже:

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;
    }
}

Хранение ссылок на изменяемых объектах будет означать, что экземпляр BoundingBox может просто изменить свои размеры, если ссылка topLeft, topRight была изменена любымдругой класс - ведущий к ошибкам, которые было бы очень трудно отлаживать.

Кроме того, ссылки Java подсчитывают объекты, и они не уничтожаются, пока не будут освобождены все ссылки.Таким образом, точки в вашем BoundingBox будут в порядке, если на них есть ссылки.

1 голос
/ 08 ноября 2011

Ни один из способов не является правильным или неправильным.Все зависит от специфики вашего приложения.Если ваш ограничивающий прямоугольник должен полагаться на точки, чтобы не измениться из-под него, то вам нужно сделать копию в конструкторе.Но тогда вы также должны убедиться, что публикуете копии объектов только в любых методах getter (например, если у вас есть getTopLeft (), getBottomRight () и т. Д. В более общем плане, это вопрос о том, использовать ли композицию или агрегированиепри проектировании вашей объектной модели. Агрегирование не подразумевает владение. Другими словами, агрегированные объекты могут существовать вне области их родительского объекта, как в случае, когда вы просто сохраняете те же точечные ссылки, которые были переданы в конструктор.Продолжительность жизни дочерних объектов будет такой же, как и у родительского объекта. Это достигается путем создания копий точек и последующей осторожности, чтобы не передавать ссылки на сохраненные точки (только копии). Таким образом, послеродительский объект исчезнет, ​​так же как и дети.

1 голос
/ 08 ноября 2011

Если он принимает их эталонные значения, гарантируется ли Точечные объекты будут существовать, пока существует 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

Как правило, рекомендуется избегать "ошибок" - кода, который работает непредвиденным образом. Даже если вы помните, какие у вашего кода есть ошибки, в первый раз, когда вы забудете о них, вы будете действительно запутаны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...