2.3 Predicate接口

问题

用户希望使用 java.util.function.Predicate 接口筛选数据。

方案

使用 lambda 表达式或方法引用来实现 boolean test(T t) 方法。

讨论

Predicate 接口主要用于流的筛选。给定一个包含若干项的流,Stream 接口的 filter 方法传入 Predicate 并返回一个新的流,它仅包含满足给定谓词的项。

Predicate 接口包含的单一抽象方法为 boolean test(T t),它传入一个泛型参数并返回 truefalse。例 2-6 列出了 Predicate 接口定义的所有方法(包括静态和默认方法)。

例 2-6 Predicate 接口定义的方法

  1. default Predicate<T> and(Predicate<? super T> other)
  2. static <T> Predicate<T> isEqual(Object targetRef)
  3. default Predicate<T> negate()
  4. default Predicate<T> or(Predicate<? super T> other)
  5. boolean test(T t)

➊ 单一抽象方法

给定一个名称集合,可以通过流处理找出所有具有特定长度的实例,如例 2-7 所示。

例 2-7 查找具有给定长度的字符串

  1. public String getNamesOfLength(int length, String... names) {
  2. return Arrays.stream(names)
  3. .filter(s -> s.length() == length)
  4. .collect(Collectors.joining(", "));
  5. }

➊ 满足给定长度字符串的谓词

或者,我们也可能只需要返回以特定字符串开头的名称,如例 2-8 所示。

例 2-8 查找以给定字符串开头的字符串

  1. public String getNamesStartingWith(String s, String... names) {
  2. return Arrays.stream(names)
  3. .filter(str -> str.startsWith(s))
  4. .collect(Collectors.joining(", "));
  5. }

➊ 返回以给定字符串开头的字符串

如果允许客户端指定条件,Predicate 的通用性会更强。例 2-9 显示了其中一种应用。

例 2-9 查找满足任意谓词的字符串

  1. public class ImplementPredicate {
  2. public String getNamesSatisfyingCondition(
  3. Predicate<String> condition, String... names) {
  4. return Arrays.stream(names)
  5. .filter(condition)
  6. .collect(Collectors.joining(", "));
  7. }
  8. }
  9.  
  10. // 其他方法
  11. }

➊ 根据提供的谓词进行筛选

上述用法相当灵活,但依靠客户端自己编写所有谓词或许不太容易。一种方案是将常量添加到代表最常见情况的类中,如例 2-10 所示。

例 2-10 为常见情况添加常量

  1. public class ImplementPredicate {
  2. public static final Predicate<String> LENGTH_FIVE = s -> s.length() == 5;
  3. public static final Predicate<String> STARTS_WITH_S =
  4. s -> s.startsWith("S");
  5.  
  6. // 其余代码和之前一样
  7. }

提供谓词作为参数的另一个优点是,可以使用默认方法 andornegate,并根据一系列单个元素来创建复合谓词(composite predicate)。

例 2-11 的测试用例展示了各种方法的应用。

  1. import static functionpackage.ImplementPredicate.*;
  2. import static org.junit.Assert.assertEquals;
  3.  
  4. // 其他导入
  5.  
  6. public class ImplementPredicateTest {
  7. private ImplementPredicate demo = new ImplementPredicate();
  8. private String[] names;
  9.  
  10. @Before
  11. public void setUp() {
  12. names = Stream.of("Mal", "Wash", "Kaylee", "Inara", "Zoë",
  13. "Jayne", "Simon", "River", "Shepherd Book")
  14. .sorted()
  15. .toArray(String[]::new);
  16. }
  17.  
  18. @Test
  19. public void getNamesOfLength5() throws Exception {
  20. assertEquals("Inara, Jayne, River, Simon",
  21. demo.getNamesOfLength(5, names));
  22. }
  23.  
  24. @Test
  25. public void getNamesStartingWithS() throws Exception {
  26. assertEquals("Shepherd Book, Simon",
  27. demo.getNamesStartingWith("S", names));
  28. }
  29.  
  30. @Test
  31. public void getNamesSatisfyingCondition() throws Exception {
  32. assertEquals("Inara, Jayne, River, Simon",
  33. demo.getNamesSatisfyingCondition(s -> s.length() == 5, names));
  34. assertEquals("Shepherd Book, Simon",
  35. demo.getNamesSatisfyingCondition(s -> s.startsWith("S"),
  36. names));
  37. assertEquals("Inara, Jayne, River, Simon",
  38. demo.getNamesSatisfyingCondition(LENGTH_FIVE, names));
  39. assertEquals("Shepherd Book, Simon",
  40. demo.getNamesSatisfyingCondition(STARTS_WITH_S, names));
  41. }
  42.  
  43. @Test
  44. public void composedPredicate() throws Exception {
  45. assertEquals("Simon",
  46. demo.getNamesSatisfyingCondition(
  47. LENGTH_FIVE.and(STARTS_WITH_S), names));
  48. assertEquals("Inara, Jayne, River, Shepherd Book, Simon",
  49. demo.getNamesSatisfyingCondition(
  50. LENGTH_FIVE.or(STARTS_WITH_S), names));
  51. assertEquals("Kaylee, Mal, Shepherd Book, Wash, Zoë",
  52. demo.getNamesSatisfyingCondition(LENGTH_FIVE.negate(), names));
  53. }
  54. }

❶ 静态导入让使用常量更加简单

❷ 复合

❸ 否定

标准库还支持 Predicate 接口的其他一些用法。

Optional.filter(Predicate predicate)

  如果值存在且匹配某个给定的谓词,则返回描述该值的 Optional,否则返回一个空 Optional

Collection.removeIf(Predicate filter)

  删除集合中所有满足谓词的元素。

Stream.allMatch(Predicate predicate)

  如果流的所有元素均满足给定的谓词,则返回 trueanyMatchnoneMatch 方法的用法与之类似。

Collectors.partitioningBy(Predicate predicate)

  返回一个 Collector,它将流分为两类(满足谓词和不满足谓词)。

只要流仅返回特定的元素,Predicate 接口就很有用。希望本范例能对读者有所启发,理解这种接口的用法。

另见

有关闭包复合(closure composition)的讨论请参见范例 5.8,有关 allMatchanyMatchnoneMatch 方法的讨论请参见范例 3.10,有关分区和分组的讨论请参见范例 4.5。