具有分配律的 Java 谓词

Posted

技术标签:

【中文标题】具有分配律的 Java 谓词【英文标题】:Java Predicates with distributive law 【发布时间】:2021-10-20 09:15:12 【问题描述】:

我有一个结构如下的布尔 Java 语句:

返回 A AND (B OR C OR D)

在 Java 中:

return A && (B || C || D);

我想通过使用链式谓词来重新表述这一点,但似乎我必须使用布尔代数的分配定律才能使其工作:

(A AND B) OR (A AND C) OR (A AND D) 这迫使我一直重复 A。

这就是我在 java 中得到的结果:

return A.and(B)
        .or(A.and(C))
        .or(A.and(D))
        .test(params)

这似乎不会导致相同的结果,因为当我使用谓词版本时,我的测试用例会失败。

在不使用分配律的情况下,Java Predicates 是不可能的,还是我做错了什么?

【问题讨论】:

为什么不A.and(B.or(C).or(D)).test(params) 【参考方案1】:

您可以完全按照自己的意愿编写语句:

A.and(B.or(C).or(D))

编辑:

这里有一个来自 cmets 的问题示例:

// here you tell Java that p1 is of type Predicate
// though you can use "and" and "or"
Predicate<String> p1 = s -> s.equals("foo");
p1.and(s -> !s.equals("bar"));

但您也可以创建自己的功能接口,使用相同的签名但不同的默认方法:

public interface MyCustomPredicate<T> 
    boolean test(T value);

    default MyCustomPredicate<T> and(MyCustomPredicate<? super T> other) 
        return (t) -> true;
    


// ...

// here you tell Java that p2 is of type MyCustomPredicate
// though you can't use "or"
// and MyCustomPredicate.and does not behave the same as Predicate.and
MyCustomPredicate p2 = s -> s.equals("bar");
p2.and(s -> !s.equals("bar"));

它与前 lambda Java 有点不同。 Predicate.and 的签名表示它接受 Predicate。但实际上这意味着它接受与T -&gt; boolean 匹配的任何 lambda。

Predicate<String> p1 = s -> s.equals("foo");
MyCustomPredicate p2 = s -> s.equals("bar");
// this works because MyCustomPredicate has the same T -> boolean signature as Predicate
p1.or(p2);

您也可以内联转换 lambda:

Predicate<String> p3 = (s) -> true;
p3.and(((Predicate<String>) x -> true).and(x -> true));

总结:如果你只写s -&gt; s.equals("bar"),Java 不知道这个lambda 是Predicate 还是任何其他具有相同签名T -&gt; boolean 的函数式接口。

【讨论】:

这行得通,但只能使 B 成为实际的 Predicate。当我键入 B 作为由括号括起来的直接 lambda 谓词时,它不起作用 - 编译器说 lambda expression not expected here。另一方面,C和D不一定是Predicate变量,可以直接插入(params) -&gt; doStuff。好像我这里的编译器不明白,你能解释一下吗?我在文档中查看了 andor 的签名,但它们似乎与我相同 x.x 如果你只写一个没有显式类型的 lambda,Java 不知道使用什么类型。 andorPredicate 接口中的默认方法。尽管您必须告诉 Java 您的 lambda 类型为 Predicate 才能使用这些方法。 当 A 已经是 Predicate 时,Java 是否应该知道使用什么类型? 我会在我的回答中添加一些解释【参考方案2】:

由于您在构建谓词后立即对其进行评估,因此您可以评估所有术语:

return A.test(params)
    && (B.test(params) || C.test(params) || D.test(params));

这避免了创建您几乎立即丢弃的对象。

【讨论】:

JVM 已针对生命周期较短的垃圾收集对象进行了高度优化。编写可读的代码总是比过早的优化要好。 @BenjaminM 只是指出了另一种选择。【参考方案3】:

你需要使用括号

A.and(B.or(C).or(D));

这是一个验证。

Function<Boolean[], Boolean> pred = ar -> 
    Predicate<Boolean[]> A = a -> a[0];
    Predicate<Boolean[]> B = b -> b[1];
    Predicate<Boolean[]> C = c -> c[2];
    Predicate<Boolean[]> D = d -> d[3];
    return A.and(B.or(C).or(D)).test(ar);
;

Function<Boolean[], Boolean> java = arr -> 
    boolean A = arr[0];
    boolean B = arr[1];
    boolean C = arr[2];
    boolean D = arr[3];
    return A && (B || C || D);
;


Random r = new Random();
for (int i = 0; i < 1_000_000; i++) 
    Boolean[] b = Stream.generate(() -> r.nextBoolean())
            .limit(4).toArray(Boolean[]::new);  
    if (java.apply(b) != pred.apply(b)) 
        System.out.println("Different results");
    

【讨论】:

以上是关于具有分配律的 Java 谓词的主要内容,如果未能解决你的问题,请参考以下文章

避免在具有多种值类型的映射中进行未经检查的分配?

java中的对象内存分配和访问

java分配内存空间

Java如何分配和回收内存?Java垃圾收集器如何工作?

Java 堆栈内存分配

Java 堆栈内存分配