【问题标题】:Java Arraylist<Object> want to merge the objects if they have the same property valueJava Arraylist<Object> 想要合并具有相同属性值的对象
【发布时间】:2015-08-13 08:30:43
【问题描述】:

Java ArrayList,填充了名为 packinglistrows 的对象,其中包含三个键值(ISBN、PalletNumber、Quantity)以及其他属性。

我有这个ArrayList,其中包含所有相同的 ISBN 值。我希望能够合并具有相同 PalletNumbers 数量值的项目。

例如:

ArrayList items = [ packinglistrow( 1234, 1, 10 ), packinglistrow( 1234, 2, 5), packinglistrow( 1234, 1, 15 ) ]

合并后,[0] 和 [2] 对象被合并,因为它们具有相同的 ISBN 和托盘号 1。导致合并的对象具有更新的数量:

ArrayList items = [ packinglistrow( 1234, 1, 25 ), packinglistrow( 1234, 2, 5) ]

是否正在考虑循环比较并将不同类型添加到新的ArrayList,然后循环并合并,但必须有一种更简洁的方法吗?

谢谢。

【问题讨论】:

  • Java 是一种面向对象的语言。使用方法void merge(MyData other) 创建一个类来保存这些值。然后有一个List&lt;MyData&gt;。循环是一种选择,最终与Map&lt;String, MyData&gt; 结合使用。但实际上,与类合并会更简单。
  • 谢谢,好的建议,缺乏经验,让一个类来做合并是有意义的。
  • 你能扩展一下类选项吗?我可以想象一个解决方案的方式似乎仍然有点过于复杂。由于对象仍将保存在数组中,因此需要在将键设置为值之前进行合并,可能会按托盘号对它们进行排序,然后依次测试相同的托盘号并合并。
  • 评论有点长,所以我添加了一个答案。

标签: java object arraylist properties merge


【解决方案1】:

首先创建一个类来处理这些数据。有两点需要注意。 equals 和 hashcode 方法仅基于 isbnpalletNumber 值,并且有一个 merge 方法返回 PackingListRow 的新实例,其数量介于此实例和您作为参数提供的其他实例之间。

class PackingListRow {
    private final String isbn;
    private final int palletNumber;
    private final int quantity;

    public PackingListRow(String isbn, int palletNumber, int quantity) {
        this.isbn = isbn;
        this.palletNumber = palletNumber;
        this.quantity = quantity;
    }

    public String getIsbn() {
        return isbn;
    }

    public int getPalletNumber() {
        return palletNumber;
    }

    public int getQuantity() {
        return quantity;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PackingListRow that = (PackingListRow) o;
        return Objects.equals(palletNumber, that.palletNumber) &&
                Objects.equals(isbn, that.isbn);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn, palletNumber);
    }

    @Override
    public String toString() {
        return "PackingListRow{" +
                "isbn='" + isbn + '\'' +
                ", palletNumber=" + palletNumber +
                ", quantity=" + quantity +
                '}';
    }

    public PackingListRow merge(PackingListRow other) {
        assert(this.equals(other));
        return new PackingListRow(this.isbn, this.palletNumber, this.quantity + other.quantity);
    }
}

一旦你有了它,你只需要创建另一个最初为空的新列表。它将包含合并的值。对于初始列表中的每个实例,您检查它是否已经在合并列表中。如果是,则通过调用合并来修改现有实例,否则只需将其附加到列表中。我们最终得到以下算法:

List<PackingListRow> list =
        Arrays.asList(new PackingListRow("1234", 1, 10), new PackingListRow("1234", 2, 5), new PackingListRow("1234", 1, 15));

List<PackingListRow> mergedList = new ArrayList<>();
for(PackingListRow p : list) {
    int index = mergedList.indexOf(p);
    if(index != -1) {
        mergedList.set(index, mergedList.get(index).merge(p));
    } else {
        mergedList.add(p);
    }
}

System.out.println(mergedList);

哪些输出:

[PackingListRow{isbn='1234', palletNumber=1, quantity=25}, PackingListRow{isbn='1234', palletNumber=2, quantity=5}]

对于 Java 8,我可能会使用不同的策略(至少您表明有多种方法可以解决问题)。我会创建一个为我进行分组的静态类:

class PackingListRow {
    private final String isbn;
    private final int palletNumber;
    private final int quantity;

    static class GroupPacking {

        private final String isbn;
        private final int palletNumber;

        public GroupPacking(PackingListRow p) {
            this.isbn = p.isbn;
            this.palletNumber = p.palletNumber;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            GroupPacking that = (GroupPacking) o;
            return Objects.equals(palletNumber, that.palletNumber) &&
                    Objects.equals(isbn, that.isbn);
        }

        @Override
        public int hashCode() {
            return Objects.hash(isbn, palletNumber);
        }
    }

    ....

    public PackingListRow merge(PackingListRow other) {
        assert (new GroupPacking(other).equals(new GroupPacking(this)));
        return new PackingListRow(this.isbn, this.palletNumber, this.quantity + other.quantity);
    }
}

然后您可以使用 Stream API。给定原始列表,您将获得一个Stream&lt;PackingListRow&gt;,您可以根据它们的 GroupPacking 实例(键)将元素收集到一个 Map 中。该值只是当前的PackingListRow 实例。如果您有两个具有相同 GroupPacking 值的实例(根据 equals/hashcode),则合并它们。你终于得到了地图的values()

List<PackingListRow> mergedList =
                new ArrayList<>(list.stream().collect(toMap(PackingListRow.GroupPacking::new, p -> p, PackingListRow::merge)).values());

【讨论】:

  • 这是非常好的解释和展示实现之一。节省了我的一天 +1
  • 合并后如果我想要一个不同的 DTO (PackingListRow{isbn='1234', quantity=25}),你能告诉我怎么做吗?其他标准相同,我只想要更少的字段。
【解决方案2】:

这里有工作示例(Java8 + Google Guava):

package com.rgrebski.test;

import com.google.common.base.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimaps;
import org.assertj.core.api.Assertions;
import org.testng.annotations.*;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class PackingListRowMergeTest {

    @Test
    public void test() {
        //given
        List<PackingListRow> packingListRow = ImmutableList.of(
                new PackingListRow(1234, 1, 10),
                new PackingListRow(1234, 2, 5),
                new PackingListRow(1234, 1, 15)
        );

        //when
        List<PackingListRow> mergedPackingListRow = Multimaps.index(packingListRow, groupByPaletNumbers())
                .asMap()
                .values()
                .stream()
                .map(packingListRowCollectionToSinglePackingListRowWithQuantitySum())
                .collect(Collectors.toList());

        //then
        Assertions.assertThat(mergedPackingListRow).containsExactly(
                new PackingListRow(1234, 1, 25),
                new PackingListRow(1234, 2, 5)
                );
    }

    protected java.util.function.Function<Collection<PackingListRow>, PackingListRow> packingListRowCollectionToSinglePackingListRowWithQuantitySum() {
        return new java.util.function.Function<Collection<PackingListRow>, PackingListRow>() {
            @Override
            public PackingListRow apply(Collection<PackingListRow> packingListRows) {
                int quantitySum = packingListRows.stream().flatMapToInt(packingListRow -> IntStream.of(packingListRow.getQuantity())).sum();
                PackingListRow firstPackingListRow = packingListRows.stream().findFirst().get();

                return new PackingListRow(firstPackingListRow.getIsbn(), firstPackingListRow.getPaletNumber(), quantitySum);
            }
        };
    }

    private Function<? super PackingListRow, Integer> groupByPaletNumbers() {
        return new Function<PackingListRow, Integer>() {
            @Override
            public Integer apply(PackingListRow input) {
                return input.getPaletNumber();
            }
        };
    }

    private static class PackingListRow {

        private int isbn;
        private int paletNumber;
        private int quantity;

        public PackingListRow(int isbn, int paletNumber, int quantity) {

            this.isbn = isbn;
            this.paletNumber = paletNumber;
            this.quantity = quantity;
        }


        public int getIsbn() {
            return isbn;
        }

        public int getPaletNumber() {
            return paletNumber;
        }

        public int getQuantity() {
            return quantity;
        }


        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            PackingListRow that = (PackingListRow) o;

            return Objects.equal(this.isbn, that.isbn) &&
                    Objects.equal(this.paletNumber, that.paletNumber) &&
                    Objects.equal(this.quantity, that.quantity);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(isbn, paletNumber, quantity);
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                    .add("isbn", isbn)
                    .add("paletNumber", paletNumber)
                    .add("quantity", quantity)
                    .toString();
        }
    }
}

【讨论】:

    【解决方案3】:

    为 ISBN 和货盘号创建一个对象似乎是合理的,但如果 ISBN 和货盘号相等,则将数量分开,因为您认为它们相等。所以对象可能看起来像这样:

    public class PackingListRow {
        private final String isbn;
        private final int palletNumber;
    
        public PackingListRow(String isbn, int palletNumber) {
            this.isbn = isbn;
            this.palletNumber = palletNumber;
        }
    
        @Override
        public int hashCode() {
            return palletNumber * 31 + ((isbn == null) ? 0 : isbn.hashCode());
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            PackingListRow other = (PackingListRow) obj;
            if (isbn == null) {
                if (other.isbn != null)
                    return false;
            } else if (!isbn.equals(other.isbn))
                return false;
            if (palletNumber != other.palletNumber)
                return false;
            return true;
        }
    
        @Override
        public String toString() {
            return isbn+":"+palletNumber;
        }
    }
    

    之后,您可以将结果存储在Map 对象中,并通过如下方法添加项目:

    public static void addItem(Map<PackingListRow, Integer> items, PackingListRow row, 
                               int quantity) {
        Integer oldQuantity = items.get(row);
        items.put(row, oldQuantity == null ? quantity : quantity+oldQuantity);
    }
    

    如果您使用的是 Java 8,则更简单:

    public static void addItem(Map<PackingListRow, Integer> items, PackingListRow row, 
                               int quantity) {
        items.merge(row, quantity, Integer::sum);
    }
    

    测试您的样本数据:

    public static void main(String[] args) {
        Map<PackingListRow, Integer> items = new HashMap<PackingListRow, Integer>();
        addItem(items, new PackingListRow("1234", 1), 10);
        addItem(items, new PackingListRow("1234", 2), 5);
        addItem(items, new PackingListRow("1234", 1), 15);
        System.out.println(items);
    }
    

    输出是:

    {1234:1=25, 1234:2=5}
    

    【讨论】:

      猜你喜欢
      • 2021-03-22
      • 1970-01-01
      • 2020-06-20
      • 2021-05-02
      • 2021-01-11
      • 1970-01-01
      • 2015-06-30
      • 1970-01-01
      • 2015-12-16
      相关资源
      最近更新 更多