为什么创建一个对象的副本仍然会改变原始对象的实例变量?

huangapple go评论92阅读模式
英文:

Why does creating a copy of an object still alter instance variables of original object?

问题

我有两个类。类Algorithm实现了一个名为findIntersections()的方法,它是一个扫描线算法,用于在O(nLogN)时间内检查交点。

它还实现了一个名为addSegments的函数,将类型为Segment的对象(两个点)添加到基于x坐标的优先队列中。

public class Algorithm {

    public PriorityQueue pQueue = new PriorityQueue();

    //此函数将Segment类型的对象添加到优先队列中
    public void addSegments(List<Segment> segments) {
        pQueue.add(segments);
        //做一些事情
    }

    //此函数实现扫描线算法以检查交点。
    public void findIntersection() {
        while (!pQueue.isEmpty()) {
            p.poll(); //从队列中删除元素
            // 做一些事情
        }
    }
}

另一个名为Model的类从CSV文件中加载数据到优先队列中。这是一个密集的过程,我只想执行一次。

另一方面,checkForCollisions会被调用数百万次。

  • 我想要检查所提供的线段与从CSV文件中添加到优先队列中的其余线段之间的碰撞。
  • 我不想每次都从头开始向优先队列中添加元素。这是不可行的。
public class Model  {
    public Algorithm algoObj = new Algorithm();
    public ArrayList<Segment> segments = new ArrayList<>();
    public ArrayList<Segment> single_segment = new ArrayList<>();

    public boolean loadCSV() {
        //读取csv文件
        while ((strLine = br.readLine()) != null) {
            segments.add(new Segment()); //将CSV文件中的所有线段添加到ArrayList
            algo.addSegments(segments);  //向优先队列添加4000个Segment对象
        }
    }

    //这个函数会被调用数百万次
    public boolean checkForCollisions(Segment segment_to_check) {
        single_segment.add(segment_to_check);    //添加1个线段
        algoObj.addSegments(single_segment);     //向优先队列添加1个Segment对象
        algoObj.findIntersection();
        single_segment.remove(new Segment()); //将上面的线段移除,恢复原始数据
    }
}

TL;DR

我遇到的问题是,在第一次调用checkForCollisions之后,优先队列发生了变化,因为findIntersection()的工作方式是从队列中轮询元素,从而改变了队列。

如何在函数调用之间保持由algoObj.addSegments()创建的优先队列不变?

这是否与浅复制和深复制有关,如此处所解释的?


我尝试在函数开始时创建队列的副本,然后修改副本:

public boolean checkForCollisions(Segment segment_to_check) {
    Algorithm copy = algoObj;
    single_segment.add(segment_to_check);    //添加1个线段
    copy.addSegments(single_segment);     //向优先队列添加1个Segment对象
    copy.findIntersection();
    single_segment.remove(new Segment()); //将上面的线段移除,恢复原始数据
}

然而,这并不起作用,因为它仍然会改变原始algoObj的优先队列。

我认为这是一个初学者的问题,源自我在使用面向对象语言时缺乏适当理解。任何帮助将不胜感激。

英文:

I have two classes. Class Algorithm implements a method findIntersections() which is a sweep line algorithm to check for intersections in O(nLogN) time.

It also implements a function addSegments which adds objects of type Segment (two points) to a priority Queue based on the x coordinate.

public class Algorithm {
 
    public PriorityQueue pQueue = new PriortiyQueue();

    //This function adds objects of type Segments to a priority queue
    public void addSegments(List&lt;Segment&gt; segments) {
        pQueue.add(segments);
        //do something
    }

    //This function implements a sweep line algorithm to check for Intersections.
    public void findIntersection() {
        while (!pQueue.isEmpty()) {
            p.poll(); //removes element from Queue
            // do something
        }
    }
}

The other class Model loads data from a CSV file into the priority Queue. This is an intensive process which I only want to do once.

On the other hand, checkForCollissions is called millions of times.

  • I want to check for collisions between the supplied segment and the rest of the segments added in the priority queue from the csv file

  • I do not want to be adding elements to the priority queue from scratch each time. This would not be feasible.

     public class Model  {
         public Algorithm algoObj = new Algorithm();
         public ArrayList&lt;Segment&gt; algoObj = new ArrayList&lt;&gt;();
         public ArrayList&lt;Segment&gt; segments = new ArrayList&lt;&gt;();
         public ArrayList&lt;Segment&gt; single_segment = new ArrayList&lt;&gt;();
    
         public boolean loadCSV() {
             //read csv file
             while ((strLine = br.readLine()) != null) {
               segments.add(new Segment()); //Add all segments in CSV file to ArrayLisyt
               algo.addSegments(segments);  //Adds 4000 objects of type segment to priority Queue
             }
         }
    
         //This function is called millions of times
         public boolean checkForCollisions(segment_to_check) {
             single_segment.add(segment_to_check);    //Add 1 segment. 
             algoObj.addSegments(single_segment);     //Adds 1 object of type segment to priority Queue
             algoObj.findIntersection();
             single_segment.remove(new Segment()); //Remove above segment to get back to original data
         }
     }
    

TL;DR

The problem I am having is that after the first call of checkForCollisions the priority queue has changed since findIntersection() works by polling elements from the queue, thus altering the queue.

How do I keep the priority queue created by algoObj.addSegments() from changing between function calls?

Does this have to do witch shallow and deep copying as explained here?


I tried creating a copy of the queue at the beginning of the function and then altering the copy:

        public boolean checkForCollisions(segment_to_check) {
            Algorithm copy = algoObj;
            single_segment.add(segment_to_check);    //Add 1 segment. 
            copy.addSegments(single_segment);     //Adds 1 object of type segment to priority Queue
            copy.findIntersection();
            single_segment.remove(new Segment()); //Remove above segment to get back to original data
        }
    }

This however does not work as it still alters the priority queue of the original algoObj.

I believe this is a beginner's question and stems from my lack of proper understanding when working with OO languages. Any help would be appreciated.

答案1

得分: 1

首先,必须了解将现有对象分配给另一个变量不会创建原始对象的副本:

MyObject a = new MyObject();
MyObject b = a; // 不会创建副本!
// 现在 a 和 b “指向” 同一个 MyObject 实例!

关于您的实际问题的一些想法:

您的优先队列只是一个工作数据结构,仅在运行交集算法时使用。当完成算法(即找到交集)时,它将为空或至少已更改,正如您已经写过的。因此,每次运行算法之前都必须重新创建优先队列。

因此您应该这样做:

  1. 从 CSV 文件加载段落到您的 ArrayList,但暂时不要将其传递给优先队列。

  2. 在调用 findIntersection() 之前,每次都要重新填充(或重新创建)优先队列。最好的方法是通过将所有段落传递给该方法,并从头开始创建新的优先队列:

    public void findIntersection(Collection<Segment> segments) {
        PriorityQueue<Segment> pQueue = new PriorityQueue<Segment>(segments);
        while (!pQueue.isEmpty()) {
            p.poll(); // 从队列中移除元素
            // 做一些操作
        }
    }
    

提示:正如我在开头已经写过的,这不会复制各个段落或段落集合。它只是传递引用。当然,优先队列在构建时必须创建内部结构,因此如果段落集合很大,这可能需要一些时间。

如果这个解决方案对您的需求来说太慢,您将需要优化算法。您真的需要经常检查交集吗?如果只向列表中添加一个段落,只需检查与其他段落的交集,但不需要检查其他段落是否相互交叉。可能您可以将段落存储在类似于 Bentley–Ottmann 算法使用的二叉搜索树中。每当出现新段落时,可以将其与搜索树进行比较,这应该可以在大约 O(log n) 的时间复杂度内完成。之后,如果需要,可以将该段插入到树中。

或者,您可以首先添加所有段落,然后仅检查一次交集。

英文:

First of all, it is crucial to know that assigning an existing object to another variable does not create a copy of the original object:

MyObject a = new MyObject();
MyObject b = a; // does NOT create a copy!
// now a and b &quot;point&quot; to the same single instance of MyObject!

Some thoughts about your actual problem:

Your priority queue is just a working data structure that is used for the intersection algorithm, and just while the algorithm is running. When its done (so the intersection(s) have been found), it is empty or at least altered, as you already wrote. So the priority queue must be recreated for every algorithm run.

So what you should do:

  1. Load the segments from the CSV file into your ArrayList, but don't pass it to the priority queue yet.

  2. Refill (or recreate) the priority queue every time before calling findIntersection(). This is best be done by passing all segments to the method and creating a new prioerity queue from scratch:

    public void findIntersection(Collection&lt;Segment&gt; segments) {
        PriorityQueue&lt;Segment&gt; pQueue = new PrioerityQueue&lt;Segment&gt;(segments);
        while (!pQueue.isEmpty()) {
            p.poll(); //removes element from Queue
            // do something
        }
    }
    

Hint: As I've already wrote at the beginning, this does not copy the individual segments nor the segment collection. It just passes a references. Of course, the priority queue will have to create internal structures at construction time, so if the segments collection is huge, this may take some time.

If this solution is too slow for your needs, you will have to work on your algorithms. Do you really need to check for intersections so often? If you add just one segment to the list, it should be sufficient to check intersections with the other segments, but not if the other segments intersect each other. Probably you could store your segments in a binary search tree similar to the one used by the Bentley–Ottmann algorithm. Whenever a new segment "arrives", it can be checked against the search tree, which should be doable with a time complexity of about O(log n). After that, the segment can be inserted into the tree, if necessary.

Or maybe you can add all segments first and then check for intersections just once.

huangapple
  • 本文由 发表于 2020年10月23日 16:01:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/64496171.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定