【问题标题】:Apply multiple filter to a map in Java [duplicate]将多个过滤器应用于Java中的地图[重复]
【发布时间】:2020-01-14 02:59:54
【问题描述】:

基于以下问题:filter Map in Java 8 Streams

public void filterStudents(Map<Integer, Student> studentsMap){
    Map<Integer, Student> filteredStudentsMap = 
        studentsMap.entrySet()
                   .stream()
                   .filter(s -> s.getValue().getAddress().equalsIgnoreCase("delhi"))
                   .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

这会过滤离开dehli 的学生。我如何过滤离开dehliamsterdamnew york 的学生?

有没有比将原始地图过滤三倍并将三个输出合并在一起更好的方法?

【问题讨论】:

  • -&gt; 之后的部分可以用任何你想要的 Java 表达式。所以编写一个任意复杂的语句来检查地址是否是任何所需的值并在这种情况下返回true

标签: java java-8 java-stream filtering predicate


【解决方案1】:

你也可以试试这个,如果你不确定在不久的将来有多少城市会进入过滤状态..

public void filterStudents(Map<Integer, Student> studentsMap){
final List<String> includedCities = List.of("DELHI", "NEW YORK", "AMSTERDEM", "SOME MORE");

Map<Integer, Student> filteredStudentsMap = 
    studentsMap.entrySet()
               .stream()
               .filter(s -> includedCities.contains(s.toUpperCase()))
               .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

更新(@JoachimSauer 在 cmets 之后):

这应该会好很多..

public void filterStudents(Map<Integer, Student> studentsMap){
final List<String> includedCities = List.of("DELHI", "NEW YORK", "AMSTERDEM", "SOME MORE");

Map<Integer, Student> filteredStudentsMap = 
    studentsMap.entrySet()
               .stream()
               .filter(s -> exclusiveCities.stream().anyMatch(s::equalsIgnoreCase))
               .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

更新(@Holger 在 cmets 之后):

更好..

public void filterStudents(Map<Integer, Student> studentsMap){
final Set<String> includedCities = new TreeSet<>(CASE_INSENSITIVE_ORDER);
Collections.addAll( includedCities , "DELHI", "NEW YORK", "AMSTERDEM", "SOME MORE");

Map<Integer, Student> filteredStudentsMap = 
    studentsMap.entrySet()
               .stream()
               .filter(includedCities::contains))
               .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

【讨论】:

  • 只要确保您的代码 never runs in Turkey: "delhi".toUpperCase().equals("DELHI") 在任何土耳其语言环境中都将评估为 false,因为 "delhi".toUpperCase() 将返回 "DELHİ"(是的,这是一个带有点在它上面)。当你真正想做equalsIgnoreCase()时,不要使用toUpperCase()toLowerCase()
  • @JoachimSauer 谢谢.. 我不知道这一点。 :)
  • 更好的解决方案是Set&lt;String&gt; excludedCities=new TreeSet&lt;&gt;(CASE_INSENSITIVE_ORDER ); Collections.addAll(excludedCities, "DELHI", "NEW YORK", "AMSTERDEM", "SOME MORE");,然后在流中使用.filter(exclusiveCities::contains)。这避免了@JoachimSauer 提到的问题,以及通过toUpperCasetoLowerCase 创建新字符串以及通过List 进行线性搜索。换句话说,效率更高。
  • 是的。在发布答案后,我曾想过使用 Set 而不是 List 作为数据结构。但从来没有想过这个。这就是我喜欢 StackOverflow 的原因。谢谢@Holger。 :)
  • 刚刚注意到excludedCities 实际上应该命名为includedCities...
【解决方案2】:
List<String> toFilter = Arrays.asList("delhi", "amsterdam", "new york");
Map<Integer, Student> filteredStudentsMap =
        studentsMap.entrySet()
                .stream()
                .filter(s -> toFilter.stream().anyMatch(f -> s.getValue().getAddress().equalsIgnoreCase(f)))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
【解决方案3】:

Predicate#or(Predicate)逻辑组成两个Predicates。

Predicate<Student> livesInDelhi = student -> "delhi".equalsIgnoreCase(student.getAddress());
Predicate<Student> livesInAmsterdam = student -> "amsterdam".equalsIgnoreCase(student.getAddress());
Predicate<Student> livesInNewYork = student -> "new york".equalsIgnoreCase(student.getAddress());

Predicate<Student> livesInAnyOfTheseThreeCities = livesInDelhi.or(livesInAmsterdam).or(livesInNewYork);

filter 调用看起来像

.filter(e -> livesInAnyOfTheseThreeCities.test(e.getValue()))

如何调整您链接过滤参数的第四行?

假设我们有一系列城市

final String[] cities = {"delhi", "amsterdam", "new york"};

对于每个Student,我们可以写一个Predicate&lt;Student&gt;,然后将它们减少Predicate::or

Predicate<Student> livesInAnyOfGivenCities = 
  Arrays.stream(cities)
    .map(city -> (Predicate<Student>) student -> city.equalsIgnoreCase(student.getAddress()))
    .reduce(Predicate::or)
    .orElseGet(() -> student -> false);

student -&gt; false 在没有给出城市时使用。

【讨论】:

  • @Naman 你的假设也很好,它会减少重复的student.getAddress() 调用。但是,我想强调从Studentboolean 的转换(不是String-&gt;boolean,因为我认为address 应该比String 更复杂)
  • 在你的例子中,你有三个谓词,但假设我不知道我会有多少(比如我收到了一系列城市,有时有 x 个城市,有时有 y 个)。我如何调整链接过滤参数的第四行?
【解决方案4】:

当然,您应该在这里使用普通的面向对象编程并创建一个具有有意义名称的单独方法:

public void filterStudents(Map<Integer, Student> studentsMap){
Map<Integer, Student> filteredStudentsMap = 
    studentsMap.entrySet()
               .stream()
               .filter(s -> s.getValue().liveIn("delhi", "amsterdam", "new york"))
               .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

当然这需要在Student类中创建相应的方法,但是为什么我们还需要对象和OOP呢?)

public class Student {

   // other methods of Student

   public boolean liveIn(String... cities) {
     return Arrays.stream(cities).anyMatch(this.city::equals);
   }
}

Array 只是一个例子——你可以使用 set、list 或任何你想要的。 这里的重点是创建一个可以在流 api 中使用的有意义的方法。

【讨论】:

  • “普通的面向对象编程”将是 Arrays.asList(cities).contains(city) 而不是 Arrays.stream(cities).anyMatch(this.city::equals)
【解决方案5】:

在多个条件下使用一个过滤器:

public void filterStudents(Map<Integer, Student> studentsMap){
    Map<Integer, Student> filteredStudentsMap = 
        studentsMap.entrySet()
                   .stream()
                   .filter(s -> s.getValue().getAddress().equalsIgnoreCase("delhi") ||
                                s.getValue().getAddress().equalsIgnoreCase("amsterdam") ||
                                s.getValue().getAddress().equalsIgnoreCase("new york"))
                   .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

或者您可以使用Set 进行简化:

public void filterStudents(Map<Integer, Student> studentsMap){
    Map<Integer, Student> filteredStudentsMap = 
        studentsMap.entrySet()
                   .stream()
                   .filter(s -> Set.of("delhi","amsterdam","new york").contains(s.getValue().getAddress().toLowerCase()))
                   .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

【讨论】:

    猜你喜欢
    • 2016-04-08
    • 1970-01-01
    • 2017-08-23
    • 2020-02-02
    • 2020-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多