Java 集合转列表,但指定预定义的前两个元素顺序。

4

我有一个List<Person>对象。我想从中获取所有id的列表,并且如果可用,我希望id“abc”和“bob”始终出现在列表的第0和第1个索引位置。是否可以使用Java流来实现这一点?

class Person {
   private String id;
}

List<Person> allPeople = ...
List<String> allIds = allPeople.stream().map(Person::id).collect(Collectors.toList());

我的方法是:

Set<String> allIds = allPeople.stream().map(Person::id).collect(Collectors.Set());
List<String> orderedIds = new ArrayList<>();
if(allIds.contains("abc")) {
   orderedIds.add("abc");
}
if(allIds.contains("bob")) {
   orderedIds.add("bob");
}
//Iterate through the set and all add all entries which are not bob and abc in the list.
4个回答

3
似乎这里更需要使用PriorityQueue而不是List,所以可以考虑如下操作:
PriorityQueue<String> pq = list.stream()
            .map(Person::getId)
            .distinct()
            .collect(Collectors.toCollection(() -> new PriorityQueue<>(
                    Comparator.comparing(x -> !"abc".equals(x))
                            .thenComparing(x -> !"bob".equals(x)))));

如果您仍需要一个“List”,只需将“pq”排入其中即可:
List<String> result = new ArrayList<>();
while (!pq.isEmpty()) {
   result.add(pq.poll());
}

太好了!我认为你可以在List.sort()或Stream.sorted()中使用这个比较器。实际上,还有一个类似的比较器,它使用相同的技术来比较Person对象。 - Stuart Marks
@StuartMarks 对,不知道为什么我一开始就用了 PriorityQueue。谢谢。 - Eugene

2

我假设列表中每个id只出现一次。基于这个前提,我会选择一个简单直接的解决方案:

List<Person> allPeople = ...;
List<String> allIds = allPeople.stream().map(Person::id).collect(toCollection(ArrayList::new));
boolean foundBob = allIds.remove("bob");
if (foundBob) allIds.add(0, "bob");
boolean foundAbc = allIds.remove("abc");
if (foundAbc) allIds.add(0, "abc");

请注意,"bob""abc"以相反的顺序移动到列表的开头。因此,"abc"最后成为第一个。
您可以创建一个小实用程序方法来移动元素:
static void moveToHead(List<String> list, String elem) {
  boolean found = list.remove(elem);
  if (found) list.add(0, elem);
}

通过这种方式,你的代码变得更加简单易懂:
```html

通过这种方式,你的代码变得更加简单易懂:

```
List<Person> allPeople = ...;
List<String> allIds = allPeople.stream().map(Person::id).collect(toCollection(ArrayList::new));
moveToHead(allIds, "bob");
moveToHead(allIds, "abc");

1
你应该使用 toCollection(ArrayList::new),因为 toList() 不能保证返回一个可变的 List - Holger

1

如果您想在“完全”的流水线中执行此操作,可以这样做:

allPeople.stream()
         .map(Person::id)
         .distinct()
         .collect(collectingAndThen(partitioningBy(s -> "abc".equals(s) || "bob".equals(s)), 
                    map -> Stream.concat(map.get(true).stream(), map.get(false).stream())));
         .collect(toList());

如果您想让"bob"前面总是有"abc",那么请更改。
map.get(true).stream()

map.get(true).stream()
    .sorted(Comparator.comparing((String s) -> !s.equals("abc")))

另一种解决方案是:
Set<String> allIds = allPeople.stream().map(Person::id).collect(toSet());
List<String> orderedIds = Stream.concat(allIds.stream()
                        .filter(s -> "abc".equals(s) || "bob".equals(s))
                        .sorted(Comparator.comparing((String s) -> !s.equals("abc"))),
                allIds.stream().filter(s -> !"abc".equals(s) && !"bob".equals(s)))
                .collect(toList());

这段代码基本上和上面的partitioningBy做的事情一样,只是采用了不同的方法。

最后,你可能会感到惊讶,但你的方法实际上是不错的,所以你可能想要补充以下内容:

Set<String> allIds = allPeople.stream().map(Person::id).collect(toSet());

List<String> orderedIds = new ArrayList<>();

if(allIds.contains("abc")) 
    orderedIds.add("abc");

if(allIds.contains("bob")) 
    orderedIds.add("bob");

orderedIds.addAll(allIds.stream().filter(s -> !"abc".equals(s) && ! "bob".equals(s)).collect(toList()));

很好。这很有趣。abc在Bob之前的保证是什么? - user1692342
如果您选择第一种方法并使用提供的比较器,则“abc”将始终在“bob”之前,而第二种方法也将实现相同的效果。 - Ousmane D.

1
受 Stuart Marks 的启发,这里有一个更简单的解决方案:
List<String> allIds = allPeople.stream()
      .map(Person::getId)
      .distinct()
      .sorted(comparing(x -> !"abc".equals(x)).thenComparing(x -> !"bob".equals(x)))
      .collect(Collectors.toList());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接