【问题标题】:Java 8 Streams: simplifying o1 -> Objects.equals(o1.getSome().getSomeOther(), o2.getSome().getSomeOther()) in a streamJava 8 Streams:在流中简化 o1 -> Objects.equals(o1.getSome().getSomeOther(), o2.getSome().getSomeOther())
【发布时间】:2017-05-01 19:16:47
【问题描述】:

给定以下代码:

stream.filter(o1 -> Objects.equals(o1.getSome().getSomeOther(),
                                   o2.getSome().getSomeOther())

这怎么可能简化?

是否有一些 equals-utility 可以让您首先提取密钥,就像 Comparator.comparing 接受密钥提取器功能一样?

请注意,代码本身 (getSome().getSomeOther()) 实际上是从模式生成的。

【问题讨论】:

标签: java java-8 java-stream


【解决方案1】:

编辑:(与同事讨论并重新访问后:Is there a convenience method to create a Predicate that tests if a field equals a given value?)

我们现在来到了以下可重用的功能接口:

@FunctionalInterface
public interface Property<T, P> {

  P extract(T object);

  default Predicate<T> like(T example) {
     Predicate<P> equality = Predicate.isEqual(extract(example));
     return (value) -> equality.test(extract(value));
  }
}

以及以下静态便捷方法:

static <T, P> Property<T, P> property(Property<T, P> property) {
  return property;
}

过滤现在看起来像:

stream.filter(property(t -> t.getSome().getSomeOther()).like(o2))

相对于之前的解决方案,我喜欢这个解决方案:它清楚地将属性的提取和Predicate 本身的创建分开,并且更清楚地说明了正在发生的事情。

以前的解决方案:

<T, U> Predicate<T> isEqual(T other, Function<T, U> keyExtractFunction) {
  U otherKey = keyExtractFunction.apply(other);
  return t -> Objects.equals(keyExtractFunction.apply(t), otherKey);
}

导致以下用法:

stream.filter(isEqual(o2, t -> t.getSome().getSomeOther())

但如果有人有更好的解决方案,我会更高兴。

【讨论】:

  • 您应该在 lambda 之外的 other 上应用该函数,以便在 other 上只调用一次。目前,您为每个t 调用它。
  • 我认为您问题中的代码比您的答案启用的代码要清晰得多。 isEqual 太模糊了。
  • @ruakh 我认为使用方法引用会更具可读性,例如isEqual(o2, MyObject::getSome)。但这不适用于链式调用。
  • @ruakh 对,不知何故......我不喜欢重复调用,正如@DidierL 所说,如果使用方法引用会更好。该功能的好处是,每当您比较过滤器中的某些内容时,您都可以只说isEqual(otherObject, ObjectType::getValueThatShouldBeEqual),并且可以在需要时轻松切换您想要比较的内容(仅编辑 1 个部分,而不是 2 个)。
  • 您应该点击链接,Didier L 已在this comment 发帖。答案的有趣之处在于,当您事先知道otherKey 时,您不需要Objects.equals。 otherKey==null? t -&gt; keyExtractFunction.apply(t)==null: t-&gt;otherKey.equals( keyExtractFunction.apply(t)) 使每个元素的评估更有效。事实上,既然您已经排除了常数的函数评估,剩下的任务是相同的。
【解决方案2】:

我认为您的问题的方法比您的答案更具可读性。而且我也认为使用内联 lambda 是可以的,只要 lambda 简单而简短。

但是,出于维护、可读性、调试和可测试性的原因,我总是将我在 lambda(谓词或函数)中使用的逻辑转移到一个或多个方法中。在你的情况下,我会这样做:

class YourObject {

    private Some some;

    public boolean matchesSomeOther(YourObject o2) {
        return this.getSome().matchesSomeOther(o2.getSome());
    }
}

class Some {

    private SomeOther someOther;

    public boolean matchesSomeOther(Some some2) {
        return Objects.isEqual(this.getSomeOther(), some2.getSomeOther());
    }
}

有了这些方法,你的谓词现在变得微不足道了:

YourClass o2 = ...;

stream.filter(o2::matchesSomeOther)

【讨论】:

  • 如果有意义的话,我确实会创建新方法。关于过滤器,我显然更喜欢一些仍然可以理解但也可重复使用的方法。我为我的答案添加了另一种方法,至少对我来说,对于只需要匹配属性的用例来说,它仍然是可以理解和可重用的。
  • @Roland 这是一个品味问题。我更喜欢将流(由流管道给出)与驱动管道的逻辑条件的提取分开。与映射和计算相同。您的新方法(现在您已经编辑了答案)看起来也不错。
  • @FedericoPeraltaSchaffner 是的,你是对的。这是一种违反LoD 原则的代码气味,所以你有我的赞成票。并且也许可以以 OO 方式设计 OP 的代码,以避免在 GOO 中引入的火车残骸代码。
  • 附带说明:getSome().getSomeOther() 的代码实际上是从模式生成的。这也是我寻找一种方法来提取这些属性而不是更正代码以变得更加面向对象的原因。我也可以为此使用包装器,但这只是我现在实际使用它的更多开销;-)
  • @Roland 现在说得通了 :) 你的方法是函数式风格,而 OO 和函数式在它们的原则和设计模式上并不总是匹配。
猜你喜欢
  • 2019-11-29
  • 2021-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多