【问题标题】:Java stream filtering - use Predicate<String> instead of Predicate<Class>, to operate directly on a field of the classJava 流过滤 - 使用 Predicate<String> 而不是 Predicate<Class>,直接对类的字段进行操作
【发布时间】:2021-07-13 18:45:08
【问题描述】:

使用 java 流,我有一个 ClassA 列表,应用于流进行过滤,所以 list.stream().filter(predicate).etc。这样,谓词将需要 ClassA 的谓词才能应用,但我真的想要字符串的谓词,因为 ClassA.Field1 是字符串类型。因此,与其让谓词包含 f -> f.getField1().equals("foo"),我希望它是 f -> f.equals("foo"),直接在字段上操作。原因是因为我也有ClassB、ClassC、predicateA、predicateB、predicateC。我不想使用多个谓词,我想使用一个谓词,因为所有谓词的操作都相同,请检查字符串是否具有预期的内容。

这个缺少什么链接,我需要如何转换流来做到这一点?

对于其他上下文,过滤后我需要从过滤列表中提取第二个字段,因此目标是拥有 ClassA.Field2.val(例如,我需要提取与该项目名称相关联的项目 ID ,所以我按名称过滤)

另外:这是一个简化,实际需要使用startsWith,而不是equals。

【问题讨论】:

  • 在应用filter 运算符之前应用map 运算符,如list.stream().map(ClassA::getField1).filter(predicate)。缺点是流将不再属于 ClassA,而是属于 String
  • 听起来所有这些类都有共同的状态。您是否考虑过将其提取到超类中,然后简单地拥有一个 Predicate&lt;Superclass&gt;
  • @Slaw 对我们的复杂模型进行了大量重构,保持当前模型优先于仅使用一个谓词,但是是的,以后这样做是个好主意

标签: java java-8 java-stream


【解决方案1】:

映射流本身的建议通常会导致丢失您想要的实际类。相反,应用一个瘦转换包装器:

listOfA.stream()
  .filter(a -> sharedPredicate.test(a.getField1()))
  ...

【讨论】:

    【解决方案2】:

    受@bedrin 帖子的启发,这里提供了一种不需要新课程的方法

    假设你有这样的类:

    class ClassA {
        String name;
        //getter, setter ..
    }
    
    class ClassB {
        String name;
        //getter, setter ..
    }
    

    和列表,例如:

    List<ClassA> myAs = List.of(new ClassA("myA_1"), new ClassA("myA_1"), new ClassA("myA_42"));
    List<ClassB> myBs = List.of(new ClassB("myB_1"), new ClassB("myB_9"), new ClassB("myB_33"));
    

    然后您可以编写一个接受字段提取器函数和您要测试的字符串的方法:

    private static <T> Predicate<T> nameEquals( Function<? super T, ?> fieldExtractor, String toTestAgainst ) {
        return t -> toTestAgainst.equals(fieldExtractor.apply(t));
    }
    

    并使用上述方法过滤您的列表。示例用法:

    myAs.stream().filter(nameEquals(ClassA::getName, "myA_1")).forEach(System.out::println);
    myBs.stream().filter(nameEquals(ClassB::getName, "myB_1")).forEach(System.out::println);
    

    【讨论】:

      【解决方案3】:

      您需要分离从类中提取字符串和过滤这些字符串的逻辑:

      public class StringFieldPredicate<T> implements Predicate<T> {
          
          private final Function<T, String> extractStringFunction;
          private final Predicate<String> stringPredicate;
      
          public StringFieldPredicate(Function<T, String> extractStringFunction, Predicate<String> stringPredicate) {
              this.extractStringFunction = extractStringFunction;
              this.stringPredicate = stringPredicate;
          }
      
          @Override
          public boolean test(T t) {
              return stringPredicate.test(extractStringFunction.apply(t));
          }
          
      }
      

      可以这样使用:

      public static void main(String[] args) {
          List<Number> numbers = Arrays.asList(1,2,3,4,5);
          numbers.stream().filter(new StringFieldPredicate<>(Number::toString, it -> it.startsWith("1")));
      }
      

      【讨论】:

      • 为什么使用类而不是 lambda 来实现?
      • 真的没有什么特别的原因
      • 我已经将谓词作为“静态最终”常量保存在“FilterPredicate”类中,因此实现 Predicate 似乎是一个不错的策略。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多