简体   繁体   中英

Unexpected behavior with List<T> in jAVA

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?

PS: 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.

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM