【问题标题】:How can I replace object in java collection?如何替换java集合中的对象?
【发布时间】:2016-04-19 18:40:59
【问题描述】:

我正在尝试用新的修改版本替换集合中的元素。下面是旨在展示我想要实现的目标的简短代码。

整个想法是我有一个由其他对象的集合组成的对象。在某个时间点,我预计集合中的这个对象(在我的示例手机中)可能需要一些修改,我只想在一个地方修改代码。

我知道为了更新对象的属性,我可以在遍历集合时使用 setter,如下所示。但也许有更好、更通用的方法来实现这一点。

public class Customer {
    private int id;
    private Collection<Phone> phoneCollection;
    public Customer() {
        phoneCollection = new ArrayList<>();
    }
//getters and setters    

}

和电话类

public class Phone {
    private int id;
    private String number;
    private String name;
//getters and setters    
}

public static void main(String[] args) {
        Customer c = new Customer();

        c.addPhone(new Phone(1, "12345", "aaa"));
        c.addPhone(new Phone(2, "34567", "bbb"));
        System.out.println(c);

        Phone p = new Phone(2, "9999999", "new name");

        Collection<Phone> col = c.getPhoneCollection();
        for (Phone phone : col) {
            if (phone.getId() == p.getId()) {
//             This is working fine
//                phone.setNumber(p.getNumber());
//                phone.setName(p.getName());

//              But I'd like to replace whole object if possible and this is not working, at least not that way
                  phone = p;
            }
        }
        System.out.println(c);
    }
}

这有可能实现我想要的吗? 我尝试了复制构造函数的想法和我在网上找到的其他方法,但它们都没有像我预期的那样工作。

编辑 1

读了一些cmets后,我有了一个想法

我在 Phone 类中添加了以下方法

public static void replace(Phone org, Phone dst){
    org.setName(dst.getName());
    org.setNumber(dst.getNumber());
}

现在我的 foreach 部分看起来像这样

    for (Phone phone : col) {
        if (phone.getId() == p.getId()) {
            Phone.replace(phone, p);
        }
    }

它完成了这项工作。 现在,如果我更改 Phone 类属性,我只需要更改该方法。你认为这样解决问题可以吗?

【问题讨论】:

  • 不,你不能这样做。如果集合是不可修改的,您希望它如何工作?
  • 是的,它不起作用,因为foreach 是一个只读循环,您不能更改对phone 的引用

标签: java collections iteration


【解决方案1】:

您不应该在迭代时修改集合;这可能会为您赢得ConcurrentModificationException。您可以扫描集合以查找与您的搜索条件匹配的第一个对象。然后就可以退出循环,移除旧对象,添加新对象。

Collection<Phone> col = c.getPhoneCollection();
Phone original = null;
for (Phone phone : col) {
    if (phone.getId() == p.getId()) {
        original = phone;
        break;
    }
}
if (original != null) {
    Phone replacement = new Phone(original);
    replacement.setNumber(p.getNumber());
    replacement.setName(p.getName());
    col.remove(original);
    col.add(replacement);
}

或者,您可以声明更具体的集合类型,例如 List,这将允许您使用索引,这将使替换步骤更加高效。

如果您的电话 ID 对于每部电话都是唯一的,则应考虑使用将每个电话 ID 映射到相应电话的 Map&lt;Integer, Phone&gt;。 (或者,您可以使用某种第三方稀疏数组结构,该结构不涉及将每个 ID 装箱到 Integer。)当然,如果您的 ID 不是唯一的,那么您可能需要将上面的内容修改为收集所有匹配电话的辅助集合(并重新考虑现有代码的逻辑)。

【讨论】:

  • 如果在第二个 if 中创建替换是在循环之外完成的,恕我直言会稍微干净一些。也许将原始重命名为找到?
  • 使用迭代器在迭代内部删除也会更简单,同时避免ConcurrentModificationException
  • 您可能会获得UnsupportedOperationException,因为并非所有子类都实现remove 方法(即default 并引发此异常)
  • @user949300 - 同意。我做了那个改变。
  • @AndrewTobilko - 是的。但是OP的代码表明该集合实际上是一个ArrayList,它确实支持remove
【解决方案2】:

你也可以使用 Set (HashSet),这只是当你不想按照 Mike 建议的方式去做的时候。

将电话用作集合中的一个项目。不要忘记在 Phone 中实现 hashCode() 和 equals()。 hashCode() 应该返回 id,因为它应该是唯一的。

由于您担心更换项目,HashSet 将如何帮助您:

  1. 创建对象的实例。
  2. 从集合中移除要替换的对象。
  3. 将新对象(您在步骤 1 中创建的)添加回集合中。

这两个操作 2 和 3 都在 O(1) / 恒定时间内得到保证。

你不需要为这个问题维护一个地图,那是多余的。

如果你想从集合本身中获取对象然后修改它,那么HashMap会更好,保证在O(1)时间内搜索。

【讨论】:

  • 这只有在他试图否定他的收藏中的重复项时才会有所帮助。
  • 嗯,使用 HashMap 本质上是一回事。不允许重复。
  • 但是在这种情况下,除非目标是消除重复元素,否则集合相对于列表有什么优势?
  • 为了使用 HashMap,必须维护一个键值对。这意味着 Phone 对象现在公开,其 Id/电话号码作为键,对象本身作为值。这个问题不需要键值对实现,这是多余的和不必要的。相反,开发人员只需要找到对象并将其替换为新的/更新的对象。由于 Set 内部使用 HashMap 作为后备结构,这可以在 O(1) 时间内实现。使用列表时,除非事先知道对象所在的特定索引(在这种情况下不知道),否则成本搜索是 O(n)
  • 你说的都是正确的,除了你最后几句话。 HashMaps 确实有 O(1)(摊销)访问时间,而且 HashSets 也有;但是,由于无法访问集合中任何给定位置的元素,您仍然需要执行 O(n) 搜索以从您的集合中接收所述对象以对其执行任何操作。换句话说,如果没有线性搜索,或者事先没有对该对象的引用,您将无法从集合中提取任何对象,这完全否定了我们的问题。
【解决方案3】:

使用map 以电话的 id 作为键来代替列表。那么你的代码如下所示:

public static void main(String[] args) {
        Customer c = new Customer();

        c.addPhone(new Phone(1, "12345", "aaa"));
        c.addPhone(new Phone(2, "34567", "bbb"));
        System.out.println(c);

        Phone p = new Phone(2, "9999999", "new name");

        Map<Integer, Phone> phoneMap = c.getPhoneMap();
        phoneMap.put(p.getId(), p);

        System.out.println(c);
}

【讨论】:

  • 我喜欢这个主意,但为什么不直接phoneMap.put(p)
  • 我的实体中有列表(或集合),并希望坚持下去。我正在寻找最简单的可重复方法来更新由一些基本属性和许多集合/列表组成的对象。这些列表绑定到 JSF 组件。
  • 电话映射应该是 Map&lt;Integer, Phone&gt; 而不是 Map&lt;String, Phone&gt;,因为电话 ID 是 int 值,而不是字符串。
  • @TedHopp,我为他做了那个编辑。我假设 ID 是基于stackoverflow.com/a/2853253/4307644 的字符串,但是向上滚动我可以看到我做出了错误的决定。我已经编辑了他的答案以反映这一点。
【解决方案4】:

如果您从集合中取出对象并更新其属性,它也会反映在集合中的同一对象中。因此,您不必在更新后从技术上替换对象。 作为“迈克 M”。指出,您可以使用 hashmap 快速检索对象而无需迭代并更新对象值。

【讨论】:

  • 这就是 OP 已经在做的事情。问题具体是关于改变集合中的现有对象。
【解决方案5】:

如果订单对您很重要,您可以将Collection 更改为List(因为您始终使用ArrayList)然后:

int index = col.indexOf(phone);
col.remove(phone);
col.add(p, index);

【讨论】:

  • 麻烦的是,他没有要删除的电话,只有电话的ID。
  • 他正在使用增强的for 循环,这给了他一个Phone。也可以使用col.remove(index);
  • col.indexOf(theNewPhone) 将始终返回-1。除非他覆盖 Phone 的 equals。
  • @Zircon,如果有重复怎么办?毕竟是List
  • 他正在遍历整个List,所以无论如何它们最终都会被替换。无论如何,我的解决方案都行不通,因为我认为它会导致ConcurrentModificationException
猜你喜欢
  • 2012-01-08
  • 2011-02-14
  • 1970-01-01
  • 2020-08-31
  • 2015-01-10
  • 2019-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多