简体   繁体   English

列表的异常行为 <T> 在jAVA中

[英]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. 如您所见,方法2比方法1更有效,也更容易理解。
Apparently, though, approach #2 does not work as expected. 但是,显然,方法2无法按预期工作。

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. 该代码实际上是在循环中运行,并且有望在mList内部存在各种类型为A对象,并且每个对象的list字段中都有数量未知(超过1个)的long值。

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). 真正发生的情况是,第一种方法可以正常工作,而第二种方法会导致以下情况:每个对象的list中始终有一个long值(即使应该有更多)。

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. 我最疯狂的猜测是,它与指针有关,或者可能是我不知道的List<T>某些默认行为。

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. PS:我确实尝试过在发布前进行搜索,但是我真的不知道要搜索什么,因此没有发现任何有用的信息。

This is because mList.contains(mA) internally check for the equality of the objects by calling o1.equals(o2) . 这是因为mList.contains(mA)通过调用o1.equals(o2)在内部检查对象的相等性。 The default implementation of equals() looks like this: equals()的默认实现如下所示:

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. 覆盖类A equals()可以解决此问题。 I guess the instances are the same if they have the same id? 如果它们具有相同的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). 第二种方法导致每个对象的列表内始终有1个long值(即使应该有更多)。

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. 如您所见,您没有从mList获取类A的引用,并且正在检查刚创建的列表中long值是否包含,该值只会添加一个。 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 因此,基本上,您正在做的是在long列表上创建一个具有1个long值的A类新实例,并将其添加到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. 另一方面,您的第一个方法是获取已经添加的类A的实例,并检查该长类是否包含在列表中(如果没有),然后将其长添加到列表中。

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 如果有一个方法mList.addIfAbsent(mA) (将其添加到列表后返回mA ,或者返回已经存在并与mA equals ),它将使您的操作变得很简单

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. 在您的第二个示例中,很显然,当mA等效mA已经存在时,您会打破这种机制。 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 ." 基本上,将addIfAbsent(mA)的定义更改为“如果列表中没有其他对象等addIfAbsent(mA)列表,则将mA添加到列表中,并返回mA

You can improve performance, achieving the identical result as your second example (sans the bug) like this: 您可以提高性能,实现与第二个示例相同的结果(消除bug),如下所示:

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). 这不会降低您的big-O复杂性,但至少可以仅执行一次O(n)操作(相比于您的工作代码中的两次操作)。

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. 如果是这样,对不起,但是,如果这些列表的大小超过数千,那么如果您使用HashSet ,或者如果您关心插入顺序,则甚至可以使用LinkedHashSet ,可以显着提高性能。 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. 在这种情况下,您将尝试add一个对象,如果该对象已经存在,则为false ,这只会花费O(1)时间。 Then you would get(mA) from the set instead of your roundabout way with indexOf , also in O(1) time. 然后,您将从集合中get(mA)而不是使用indexOf的环形交叉路,也在O(1)时间内。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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