如何使用Java Stream将一个List<Src>映射为一个目标对象Tgt?

huangapple go评论82阅读模式
英文:

How to use Java Stream Map a List<Src> to an Object Tgt?

问题

首先,这个问题来自于我的实际工作。我需要解决它,并且我做了一些权衡来实现不完美的解决方案。此外,我将基本数据结构进行了抽象和简化,以避免敏感数据的泄露。

我源对象和目标对象分别为SrcTgt,定义如下:

public class Src {
    int id;
    boolean flag;
    int count;
    // 构造方法、Getter和Setter方法......
}

public class Tgt {
    int id;
    int true_count;
    int false_count;
    // 构造方法、Getter和Setter方法......
}

现在我有一个名为srcListSrc对象列表。我想将这个列表合并为一个名为Tgt的对象,操作类似于统计列表中有多少个Src对象的flag为true或false。

在Java 8的Stream中,我尝试使用flatMapmap操作符来将列表转换为一个对象,代码如下:

public class TestSrc2Tgt {

    public List<Tgt> listSrc2ListTgt(List<Src> srcList){
        Tgt tgt = new Tgt();
        for(Src src : srcList) {
            tgt.setId(src.getId());
            if(src.isFlag()) {
                tgt.setTrue_count(tgt.getTrue_count + src.getCount());
            }
            else {
                tgt.setFalse_count(tgt.getFalse_count + src.getCount());
            }
        }
        // 这是我的问题,我不想使用列表
        // 我想要的是将List<Src>转换为一种合并的Tgt对象
        List<Tgt> tgtList = new ArrayList<>();
        tgtList.add(tgt);
        return tgtList;
    }

    public static void main(String[] args){

        TestSrc2Tgt testSrc2Tgt = new TestSrc2Tgt();

        List<Src> srcList = new ArrayList<>();
        srcList.add(new Src(1, true, 15));
        srcList.add(new Src(1, false, 20));
        srcList.add(new Src(1, false, 110));
        srcList.add(new Src(2, true, 40));
        srcList.add(new Src(2, false, 250));
        srcList.add(new Src(2, true, 420));

        // 1. 按id进行分组,生成id对应的Src列表
        Map<Integer, List<Src>> srcMap = srcList.stream()
                .collect(Collectors.groupingBy(Src::getId, Collectors.toList()));
        // 2. 通过listSrc2ListTgt方法统计true或false的Src数量
        // 我不喜欢这里的处理方式
        List<Tgt> tgtMaplist =
                srcMap.entrySet().stream().map(e -> {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
                        .map(item->item.get(0)).collect(Collectors.toList());

        tgtMaplist.stream().forEach(t -> {
            System.out.println(t.getId());
            System.out.println(t.getFalse_count());
            System.out.println(t.getTrue_count());
        });
    }
}

这段代码确实可以工作,但我不喜欢listSrc2ListTgt()方法以及在获取tgtMaplist时使用的.map(item->item.get(0))。我的想法是从List<Src>直接转换为Tgt,而不是先从List<Src>转换为List<Tgt>(仅包含一个元素),然后再从List<Tgt>获取Tgt。这是一种浪费和不完美的实现方式。

在理想情况下,代码应该是这样的:

public Tgt listSrc2ListTgt(List<Src> srcList){
    Tgt tgt = new Tgt();
    for(Src src : srcList) {
        tgt.setId(src.getId());
        if(src.isFlag()) {
            tgt.setTrue_count(src.getCount());
        }
        else {
            tgt.setFalse_count(src.getCount());
        }
    }
    return tgt;
}

在主方法中:

List<Tgt> tgtMaplist = srcMap.entrySet().stream()
                         .map(e -> {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
                         .collect(Collectors.toList());

这样更舒适。因为在获取tgtMaplist时没有使用.get(0)方法的中间过程,似乎更加简洁。然而,理想的代码无法通过编译。

英文:

First of all, this question is from my real work. I need to solve it and I do some trade off working to realize it unperfect. What's more, I abstract and simplify the basic data structure to avoid sensitive data.

My source and target Object which is Src and Tgt is as followed:

public class Src {
    int id;
    boolean flag;
    int count;
    // Constructor Getter and Setter methods......
}

public class Tgt {
    int id;
    int true_count;
    int false_count;
    // Constructor Getter and Setter methods......
}

Now I have a list of Src Object named srcList. I want to combine the list to one Object named Tgt which operation is like count how many flag in Src Object is true or false in the list.

In java8 Stream, I haved tried to use flatmap and map operator to convert List to one Object like followed code:


public class TestSrc2Tgt {

    public List&lt;Tgt&gt; listSrc2ListTgt(List&lt;Src&gt; srcList){
        Tgt tgt = new Tgt();
        for(Src src : srcList) {
            tgt.setId(src.getId());
            if(src.isFlag()) {
                tgt.setTrue_count(tgt.getTrue_count + src.getCount());
            }
            else {
                tgt.setFalse_count(tgt.getFalse_count + src.getCount());
            }
        }
        /* it is my question and I do not want to use List
            What I want is convert List&lt;Src&gt; to Tgt which is a kind of combine srcList
         */
        List&lt;Tgt&gt; tgtList = new ArrayList&lt;&gt;();
        tgtList.add(tgt);
        return tgtList;
    }

    public static void main(String[] args){

        TestSrc2Tgt testSrc2Tgt = new TestSrc2Tgt();

        List&lt;Src&gt; srcList = new ArrayList&lt;&gt;();
        srcList.add(new Src(1, true, 15));
        srcList.add(new Src(1, false, 20));
        srcList.add(new Src(1, false, 110));
        srcList.add(new Src(2, true, 40));
        srcList.add(new Src(2, false, 250));
        srcList.add(new Src(2, true, 420));

        // 1. Cluster the id and generate list by id
        Map&lt;Integer, List&lt;Src&gt;&gt; srcMap = srcList.stream()
                .collect(Collectors.groupingBy(Src::getId, Collectors.toList()));
        // 2. Count how many Src flag is true or false by listSrc2ListTgt method
        // Where I hate is here
        List&lt;Tgt&gt; tgtMaplist =
                srcMap.entrySet().stream().map( e -&gt; {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
                        .map(item-&gt;item.get(0)).collect(Collectors.toList());

        tgtMaplist.stream().forEach(t -&gt; {
            System.out.println(t.getId());
            System.out.println(t.getFalse_count());
            System.out.println(t.getTrue_count());
        });
    }
}

The code could really work but I hate the listSrc2ListTgt() method and .map(item-&gt;item.get(0)) in getting List&lt;Tgt&gt; tgtMaplist. My idea is a one way converting from List&lt;Src&gt; to Tgt but not convert from List&lt;Src&gt; to List&lt;Tgt&gt;(only one element) and then get Tgt from List&lt;Tgt&gt;. It is a wasted and unperfect way of realizing.<br>

In ideal situation

public Tgt listSrc2ListTgt(List&lt;Src&gt; srcList){
    Tgt tgt = new Tgt();
    for(Src src : srcList) {
        tgt.setId(src.getId());
        if(src.isFlag()) {
            tgt.setTrue_count(src.getCount());
        }
        else {
            tgt.setFalse_count(src.getCount());
        }
    }
    return tgt;
}

And in main method:

List&lt;Tgt&gt; tgtMaplist = srcMap.entrySet().stream()
                         .map( e -&gt; {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
                         .collect(Collectors.toList());

It is more comfortable. Because there is no intermediate process taken from the tgtlist by using .get(0) method, which seems less redundant. However the ideal code is unable to pass the compiler.

答案1

得分: 2

你的理想解决方案应该在 ListSrc2ListTgt 方法内部返回 tgt 而不是 tgtList

public Tgt ListSrc2ListTgt(List<Src> srcList) {
    Tgt tgt = new Tgt();
    for(Src src : srcList) {
        tgt.setId(src.getId());
        if(src.isFlag()) {
            tgt.setTrue_count(src.getCount());
        } else {
            tgt.setFalse_count(src.getCount());
        }
    }
    return tgt;
}

为了提高 main 方法中流的可读性和可用性:

  • 如果你不需要键,不要使用 entrySet(),而是使用 values()
  • 然后可以使用方法引用来缩短 lambda 表达式。
List<Tgt> tgtMaplist = srcMap.values().stream()
        .map(testSrc2Tgt::ListSrc2ListTgt)
        .collect(Collectors.toList());
英文:

Your ideal solution should work, if you return tgt instead of tgtList inside ListSrc2ListTgt:

public Tgt ListSrc2ListTgt(List&lt;Src&gt; srcList) {
    Tgt tgt = new Tgt();
    for(Src src : srcList) {
        tgt.setId(src.getId());
        if(src.isFlag()) {
            tgt.setTrue_count(src.getCount());
        } else {
            tgt.setFalse_count(src.getCount());
        }
    }
    return tgt;
}

And to improve the readability and usability of your stream in main method:

  • don't use entrySet() if you don't need the keys. use values() instead.
  • the lambda can then be shortened with a method reference.
List&lt;Tgt&gt; tgtMaplist = srcMap.values().stream()
        .map(testSrc2Tgt::ListSrc2ListTgt)
        .collect(Collectors.toList());

答案2

得分: 2

你可以先将你的Src映射到Tgt。然后使用toMapid进行映射,并使用合并函数将Tgt的列表合并为一个。然后将映射值获取到一个ArrayList中。

Map<Integer, Tgt> tgtMap = srcList.stream()
                .map(s -> new Tgt(s.getId(),
                                 (s.isFlag() ? s.getCount() : 0),
                                 (s.isFlag() ? 0 : s.getCount())))
                .collect(Collectors.toMap(Tgt::getId, e -> e,
                                    (a, b) -> new Tgt(a.getId(),
                                                      a.getTrue_count() + b.getTrue_count(),
                                                      a.getFalse_count() + b.getFalse_count())));

List<Tgt> tgtList = new ArrayList(tgtMap.values()); // 从映射值创建列表

在这里,使用map()Src对象转换为Tgt对象:

s -> new Tgt(s.getId(), (s.isFlag() ? s.getCount() : 0), (s.isFlag() ? 0 :s.getCount()))

然后使用Collectors.toMapTgt::getId进行映射,这是toMap的第一个参数。接下来的参数是作为整个Tgt对象的键的映射值,最后合并函数中的最后一个参数用于将两个Tgt对象合并为一个Tgt对象,从而合并相同键的所有值:

Collectors.toMap(Tgt::getId,
e -> e,
(a, b) -> new Tgt(a.getId(),
a.getTrue_count() + b.getTrue_count(),
a.getFalse_count() + b.getFalse_count()))

为了简化代码,带有解释的示例:

Map<Integer, Tgt> tgtMap = srcList.stream()
                    .map(s -> /*将其转换为Tgt*/ )
                    .collect(Collectors.toMap(Tgt::getId, e -> e, /*合并函数*/));
英文:

You can map your Src into Tgt first. Then using toMap map by id and merge the list of Tgt into one using the merge function. Then get the map values in an ArrayList.

Map&lt;Integer, Tgt&gt; tgtMap = srcList.stream()
.map(s -&gt; new Tgt(s.getId(),                     
(s.isFlag() ? s.getCount() : 0),
(s.isFlag() ? 0 : s.getCount())))
.collect(Collectors.toMap(Tgt::getId, e -&gt; e,    
(a, b) -&gt; new Tgt(a.getId(), 
a.getTrue_count() + b.getTrue_count(), 
a.getFalse_count() + b.getFalse_count())));
List&lt;Tgt&gt; tgtList = new ArrayList(tgtMap.values()); // Create list from map values

Here using map() transform Src object into Tgt object

s -&gt; new Tgt(s.getId(), (s.isFlag() ? s.getCount() : 0),(s.isFlag() ? 0 :s.getCount()))

And then using Collectors.toMap map by Tgt::getId which is first parameter of toMap.The next one is the key of the map e -&gt; e for the whole Tgt obj as key. And finally last parameter in the merge function will used for merging two Tgt objects into one Tgt object so that all values of the same key merged.

Collectors.toMap(Tgt::getId,
e -&gt; e,
(a, b) -&gt; new Tgt(a.getId(), 
a.getTrue_count() + b.getTrue_count(), 
a.getFalse_count() + b.getFalse_count()))

To simplify the code with explanations

Map&lt;Integer, Tgt&gt; tgtMap = srcList.stream()
.map(s -&gt; /*transform into Tgt*/)
.collect(Collectors.toMap(Tgt::getId, e -&gt; e, /*Merge function*/));

答案3

得分: 1

不是严格的对您确切问题的回答,而是一个更简单的方法来找到所要求的3件事情:

int id = srcList.get(srcList.size() - 1).getId();
int true_count = srcList.stream().filter(Src::getFlag).count();
int false_count = srcList.size() - true_count;
英文:

Not strictly an answer to your exact question, but a simpler approach to find the 3 things asked for:

int id = srcList.get(srcList.size() - 1).getId();
int true_count = srcList.stream().filter(Src::getFlag).count();
int false_count = srcList.size() - true_count;

答案4

得分: 1

这里是一个使用Java流的简单方法:

List<Src> srcList = new ArrayList<Src>();
srcList.add(new Src(1, true, 15));
srcList.add(new Src(1, false, 20));
srcList.add(new Src(1, false, 110));
srcList.add(new Src(2, true, 40));
srcList.add(new Src(2, false, 250));
srcList.add(new Src(2, true, 420));
List<Tgt> tgtList = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collectors.toList()))
.entrySet().stream()
.map(e -> 
new Tgt(e.getKey(), 
e.getValue().stream().mapToInt(s -> s.getFlag() ? s.getCount() : 0).sum(),
e.getValue().stream().mapToInt(s -> !s.getFlag() ? s.getCount() : 0).sum())
).collect(Collectors.toList());
System.out.println(tgtList);

这是输出结果:

[Tgt [id=1, true_count=15, false_count=130], Tgt [id=2, true_count=460, false_count=250]]

编辑:
我更倾向于不使用流,这样我们就不会多次遍历数据。

Map<Integer, Tgt> tgtMap = new HashMap<Integer, Tgt>();
for (Src src : srcList) {
Tgt tgt = new Tgt(src.getId(), src.getFlag() ? src.getCount() : 0, !src.getFlag() ? src.getCount() : 0);
tgtMap.compute(src.getId(), (k, v) -> 
v == null ? tgt
: new Tgt(src.getId(), 
tgt.getTrue_count() + v.getTrue_count(), 
tgt.getFalse_count() + v.getFalse_count()));
}
英文:

Here is a pretty simply way using Java streams:

List&lt;Src&gt; srcList = new ArrayList&lt;Src&gt;();
srcList.add(new Src(1, true, 15));
srcList.add(new Src(1, false, 20));
srcList.add(new Src(1, false, 110));
srcList.add(new Src(2, true, 40));
srcList.add(new Src(2, false, 250));
srcList.add(new Src(2, true, 420));
List&lt;Tgt&gt; tgtList = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collectors.toList()))
.entrySet().stream()
.map(e -&gt; 
new Tgt(e.getKey(), 
e.getValue().stream().mapToInt(s -&gt; s.getFlag() ? s.getCount() : 0).sum(),
e.getValue().stream().mapToInt(s -&gt; !s.getFlag() ? s.getCount() : 0).sum())
).collect(Collectors.toList());
System.out.println(tgtList);

Here is the output:

[Tgt [id=1, true_count=15, false_count=130], Tgt [id=2, true_count=460, false_count=250]]

EDIT:
I would prefer to not use streams so we don't loop over the data so many times.

Map&lt;Integer, Tgt&gt; tgtMap = new HashMap&lt;Integer, Tgt&gt;();
for (Src src: srcList) {
Tgt tgt = new Tgt(src.getId(), src.getFlag() ? src.getCount() : 0, !src.getFlag() ? src.getCount() : 0);
tgtMap.compute(src.getId(), (k, v) -&gt; 
v == null ? tgt
: new Tgt(src.getId(), 
tgt.getTrue_count() + v.getTrue_count(), 
tgt.getFalse_count() + v.getFalse_count()));
}

答案5

得分: 0

这段代码需要一个 Pair 类 - 可以从库中获取,或者您可以自己创建。您可以使用 Collectors.partitioningBy 替代自定义的收集器,不过这种方式更高效。

List<Tgt> tgtList = srcList.stream()
        .collect(Collectors.groupingBy(Src::getId, Collector.of(
                () -> new Pair<>(0L, 0L),
                (pair, src) -> {
                    if (src.isFlag())
                        pair.setKey(pair.getKey() + src.getCount());
                    else
                        pair.setValue(pair.getValue() + src.getCount());
                },
                (pairA, pairB) -> new Pair<>(pairA.getKey() + pairB.getKey(), pairA.getValue() + pairB.getValue())
        )))
        .entrySet()
        .stream()
        .map(entry -> new Tgt(entry.getKey(), (int) (long) entry.getValue().getKey(), (int) (long) entry.getValue().getValue()))
        .collect(Collectors.toList());
英文:

For this code you need a Pair class - either from a library, or you can create it yourself. Instead of custom collector you could use Collectors.partitioningBy, but this way it's more efficient.

List&lt;Tgt&gt; tgtList = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collector.of(
() -&gt; new Pair&lt;&gt;(0L, 0L),
(pair, src) -&gt; {
if (src.isFlag())
pair.setKey(pair.getKey() + src.getCount());
else
pair.setValue(pair.getValue() + src.getCount());
},
(pairA, pairB) -&gt; new Pair&lt;&gt;(pairA.getKey() + pairB.getKey(), pairA.getValue() + pairB.getValue())
)))
.entrySet()
.stream()
.map(entry -&gt; new Tgt(entry.getKey(), (int) (long) entry.getValue().getKey(), (int) (long) entry.getValue().getValue()))
.collect(Collectors.toList());

huangapple
  • 本文由 发表于 2020年7月25日 15:57:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/63085862.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定