Reputation: 2035
working on my app I came across a behavior I have not expected or have previously encountered.
Consider this simple class:
public class A {
public long id;
public long date;
public List<Long> list;
/* constructors */
}
Now consider these 2 approaches to doing the same thing:
/* Approach #1 */
List<A> mList = new ArrayList<A>();
long mLong = ......;
A mA = new A(id, date);
if(!mList.contains(mA))
mList.add(mA);
mA = mList.get(mList.indexOf(mA));
if(!mA.list.contains(mLong))
mA.list.add(mLong);
/* Approach #2 */
List<A> mList = new ArrayList<A>();
long mLong = ......;
A mA = new A(id, date);
if(!mA.list.contains(mLong))
mA.list.add(mLong);
if(!mList.contains(mA))
mList.add(mA);
As you can see, approach #2 is more efficient than approach #1, and also much easier to understand.
Apparently, though, approach #2 does not work as expected.
The code actually runs in a loop, and it is expected that there could be various objects of type A
inside mList
, and an unknown (more than 1) amount of long
values inside the list
field of each object.
What really happens is that the first approach works fine, while the second approach results in a situation where there is always 1 long
value inside list
of every object (even when there should be more).
I personally can't see what could possibly cause it to work that way, which is why I'm here, asking for the answer to this 'mystery'. My wildest guess would say it's related to pointers, or maybe some default behavior of List<T>
I'm not aware of.
With that being said, what could be the cause to this unexpected behavior?
P.S: I did try to run a search before posting, but I really had no idea what to search for, so I didn't find anything useful.
Upvotes: 0
Views: 94
Reputation: 1843
If there was a method mList.addIfAbsent(mA)
(which returns either mA
after adding it to the list, or the object which was already present and matches mA
on equals
), it would make your operation as trivial as
mA = mList.addIfAbsent(mA);
mA.list.addIfAbsent(mLong);
In your second example you obviously break this mechanism for the case when the mA
equivalent is already in there. Basically, you change the definition of addIfAbsent(mA)
to "adds mA
to the list if no other object in the list is equal to it, and returns mA
."
You can improve performance, achieving the identical result as your second example (sans the bug) like this:
int indOfOld = mList.indexOf(ma);
if (indOfOld != -1)
ma = mList.get(indOfOld);
else
mList.add(mA);
if(!mA.list.contains(mLong))
mA.list.add(mLong);
This won't cut your big-O complexity, but will at least make do with just one O(n) operation (compared with two in your working code).
BTW this may be obvious to you and everyone else; if so, excuse me—but if those lists get any larger than thousands of elements, you could get a significant improvement in performance if you used a HashSet
, or even a LinkedHashSet
if you care for the insertion order. In that case, you would just try to add
an object, getting false
if it was already there, and this would cost you just O(1) time. Then you would get(mA)
from the set instead of your roundabout way with indexOf
, also in O(1) time.
Upvotes: 0
Reputation: 26198
second approach results in a situation where there is always 1 long value inside list of every object (even when there should be more).
problem
A mA = new A(id, date);
if(!mA.list.contains(mLong))
As you can see you are not getting the reference of the class A from the mList and you are checking if the value long contains on the list that was just created which will only add one. So basically what you are doing is creating a new intance of class A with 1 long value on the list of long and add to the mList
on the other hand your first Approach is getting the instance of already added class A and checking if that long contains in the list if not then add it on the list long.
Upvotes: 1
Reputation: 10977
This is because mList.contains(mA)
internally check for the equality of the objects by calling o1.equals(o2)
. The default implementation of equals()
looks like this:
public boolean equals(Object o) {
return this == o;
}
Obviously the instances are not the same so you are adding a new instance every time. Override equals()
in your class A
to fix the problem. I guess the instances are the same if they have the same id?
public boolean equals(Object o) {
return this.mId == o.mId;
}
Upvotes: 1