有没有办法将此for循环改为使用Java流?

时间:2018-08-07 18:39:31

标签: java list java-8 set java-stream

Set<String> unique = new HashSet<>();
List<String> duplicates = new ArrayList<>();

for (Animal cat: animals) {
    if (!unique.add(cat.getName())) {
        duplicates.add(cat.getName());
    }
}
return duplicates;

我想知道是否有一种简化方法?我是Java流的新手,我尝试使用Map,但是我改用了传统的for循环。

6 个答案:

答案 0 :(得分:5)

  

我想知道是否有一种简化方法?我

流方式可能不是您所需要的,您的代码实际上很简单。

由于多个中间操作(过滤,映射,归约,流式处理a),流允许从输入(Stream<Foo>)传递到结果(FooList<String> ...)。收集的结果...)。
每个流操作都依赖于下一个操作的返回流:
为简化起见,将给出一条链:

  

a-> b(使用a)-> c(使用b)-> d(使用c)-> e(使用d)

实际上,您的代码不能依赖这种逻辑,因为返回的List<String>不需要仅返回具有重复名称的列表,可以这样写:

List<String> duplicates =     
    animals.stream()
           .collect(Collectors.groupingBy(Animal::getName))
           .entrySet().stream()
           .filter(e -> e.getValue().size() > 1)
           .map(Entry::getKey)
           .collect(Collectors.toList());

您要按出现的顺序返回每个重复出现的List
这意味着您不映射

之类的Stream<Animal>-> Stream<String>
  

a-> b(使用a)

因为如果没有在结果中添加动物名称,则需要过滤掉元素...但是没有将流设计为以增量方式填充结果。所以你被困住了。


您可以编写此代码,但是正如所说的,这实际上不是一种简化,但是它仍然没有应用相同的逻辑,因为dup名称的顺序与您的代码中的顺序不同:

List<String> duplicates = 
          animals.stream()
                 .collect(Collectors.groupingBy(Animal::getName, LinkedHashMap::new, Collectors.toList()))
                 .values().stream()
                 .flatMap(l-> l.stream().skip(1)) 
                 .map(Animal::getName)
                 .collect(Collectors.toList());

答案 1 :(得分:3)

您是否要根据名称Animal提取重复的字符串名称?尽管您的代码不涉及第一个找到的重复项,而是返回列表中n-1处重复项的列表,但它是:

Set<String> set = new HashSet<>();
List<String> names = animals.stream()
                            .map(cat -> cat.getName())      // Names to collect and compare
                            .filter(name -> !set.add(name)) // Collect duplicates
                            .collect(Collectors.toList());  // To the List

此解决方案基于您的for-loop并执行相同的操作。但是,Stream API的documentation指出,构造应为non-inferingstateless,这意味着它们独立于可能更改状态的源。


关于Stream-API文档,这是另一种工作方式-但有点复杂:

List<String> names = animals.stream()
    .collect(Collectors.groupingBy(
         Animal::getName, 
         Collectors.counting()))            // Collects to Map <name, count>
    .entrySet()                             // Gets the entries
    .stream()                               // Stream of the entries
    .filter(entry -> entry.getValue() > 1)  // Filters the duplicates
    .map(entry -> Collections.nCopies(      // Creates n-1 copies of the key as same as the
        entry.getValue().intValue() - 1,    //   OP's sample consumes the first duplication
        entry.getKey()))                    //   and includes the remainin ones
    .flatMap(List::stream)                  // Flattens the structure
    .collect(Collectors.toList());          // Results in the List

两种方法均来自输入:

List<Animal> animals = Arrays.asList(
    new Animal("A"), new Animal("A"), new Animal("A"), 
    new Animal("B"), new Animal("B"), new Animal("C"));

以下输出(无序):

  

[A,B,A]

答案 2 :(得分:1)

我不知道这是否可以被简化,但这是使用流实现的一种方法:

return animals.stream()
        .collect(Collectors.groupingBy(Animal::getName))
        .values()
        .stream()
        .flatMap(group -> group.stream().skip(1))
        .map(Animal::getName)
        .collect(Collectors.toList());

答案 3 :(得分:0)

请勿重新发明轮子,并使用commns-collection.substract()之类的图书馆

// I have not tested the code, but I think you get the idea
Set<> unique = new HashSet(animals)
Collection<> dublicates = CollectionUtil.subtract(animals, unique)

答案 4 :(得分:0)

总有一种方法-仍然不简单,但更短:

List<Animal> duplicates = animals.stream()
  .collect( Collectors.collectingAndThen( Collectors.groupingBy( Animal::getName ),
    map -> {
      map.entrySet().removeIf( e -> e.getValue().size() < 2 );
      return( map.values().stream().flatMap( List::stream ).collect( Collectors.toList() ) );
    } ) );

答案 5 :(得分:-1)

问题是“如何使用Streams?”但我认为一个不错的答案是“流并不总是一种简化”。

检测重复项的问题很经典,并且有一种规范的方法:

  • 对集合进行排序。
  • 进行迭代。
  • 每个与其前任相同的元素都是重复的。

因此,尽管它并不能真正回答问题,但是正确的解决方法是像这样的恕我直言:

List<Animal> animals =
        Arrays.asList(
                new Animal("Alice"), 
                new Animal("Alice"), 
                new Animal("Alice"), 
                new Animal("Bob"),
                new Animal("Charlie"), 
                new Animal("Bob"));

List<Animal> duplicates = new ArrayList<>();

animals.sort(Comparator.comparing(Animal::getName));
for (int i = 1; i < animals.size(); i++) {
    Animal current = animals.get(i);
    if (animals.get(i - 1).getName().equals(current.getName())
            //Bonus : Also compare to the previous-previous in order to avoid multiple duplicates
            && (i < 2 || !animals.get(i - 2).getName().equals(current.getName()))) {
        duplicates.add(current);
    }
}

duplicates.forEach(a -> System.out.println(a.getName()));

输出:

Bob
Alice

它可能并不容易理解(取决于您的经验),但要比使用Stream创建一个中间HashMap更为简洁。

因此,要么这样做(为了提高性能),要么像您已经做的那样(出于可读性)。