函数而不是静态实用方法

Functions instead of static utility methods

尽管自 Java 8 以来 Java 中就存在函数,但我最近才开始使用它们。因此,这个问题可能听起来有点陈旧,请原谅。

首先,我说的是完全按照函数式编程定义编写的纯函数:确定性和不可变性。

比如说,我经常需要在字符串前加上另一个静态值。像下面这样的例子:

    private static Function<String, String> fnPrependString = (s) -> {
        return "prefix_" + s;
    };

在好的旧方法中,Helper class 及其静态方法会为我完成这项工作。

现在的问题是,我是否可以创建一次这些函数并像辅助方法一样重用它们。

一个威胁是线程安全。我用一个简单的测试来检查这个 JUnit 测试:

package com.me.expt.lt.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.function.Function;

import org.junit.jupiter.api.Test;

import com.vmlens.api.AllInterleavings;

public class TestFunctionThreadSafety {
    private static Function<String, String> fnPrepend = (s) -> {
        System.out.println(s);
        return new StringBuffer("prefix_").append(s).toString();
    };

    @Test
    public void testThreadSafety() throws InterruptedException {
        try (AllInterleavings allInterleavings = new AllInterleavings(
                TestFunctionThreadSafety.class.getCanonicalName());) {
            ConcurrentMap<String, Integer> resultMap = new ConcurrentHashMap<String, Integer>();
            while (allInterleavings.hasNext()) {
                int runSize = 5;
                Thread[] threads = new Thread[runSize];
                ThreadToRun[] ttrArray = new ThreadToRun[runSize];
                StringBuffer sb = new StringBuffer("0");
                for (int i = 0; i < runSize; i++) {
                    if (i > 0)
                        sb.append(i);
                    ttrArray[i] = new ThreadToRun();
                    ttrArray[i].setS(sb.toString());
                    threads[i] = new Thread(ttrArray[i]);
                }
                for (int j = 0; j < runSize; j++) {
                    threads[j].start();
                }
                for (int j = 0; j < runSize; j++) {
                    threads[j].join();
                }
                System.out.println(resultMap);
                StringBuffer newBuffer = new StringBuffer("0");
                for (int j = 0; j < runSize; j++) {
                    if(j>0)
                        newBuffer.append(j);
                    assertEquals("prefix_" + newBuffer, ttrArray[j].getResult(), j + " fails");
                }
            }
        }
    }

    private static class ThreadToRun implements Runnable {
        private String s;
        private String result;

        public String getS() {
            return s;
        }

        public void setS(String s) {
            this.s = s;
        }

        public String getResult() {
            return result;
        }

        @Override
        public void run() {
            this.result = fnPrepend.apply(s);
        }

    }

}

我正在使用 vmlens。我可以通过将 runSize 变量更改为我选择的一个数字来调整我的测试,以便检查随机性。 objective 是看这些使用相同函数的多个线程是否因为并发访问而混淆了它们的输入。 测试没有return任何阴性结果。也请对测试是否达到目标发表评论。

我还试图了解如何从 here 执行 lambda 的内部 VM 端。即使我寻找可以更快地理解这些细节的更简单的文章,我也没有找到任何说“Lambdas 将有线程安全问题”的文章。

假设测试用例符合我的目标,随之而来的问题是:

  1. 我们可以用像 fnPrepend 这样的函数变量不可变和确定性函数替换静态助手 classes 吗? objective 只是为了提供更具可读性的代码,当然也是为了摆脱对静态方法的“不太面向对象”的批评。

  2. 是否有关于 Lambdas 如何在虚拟机中工作的更简单解释的来源?

  3. 上面带有 Function<InputType, ResultType> 的结果是否也适用于 Supplier<SuppliedType>Consumer<ConsumedType>

更多地熟悉函数和字节码可能会帮助我回答这些问题。但是像这样的知识交流论坛可能会让我更快得到答案,问题可能会引发读者更多的想法。

提前致谢。 拉胡尔

作为用户,我真的不认为您需要竭尽全力证明 JVM 对 lambda 的保证。基本上,它们就像 JVM 的任何其他方法一样,没有特殊的内存或可见性影响:)

这是一个更短的函数定义:

private static Function<String, String> fnPrepend = s -> "prefix_" + s;

this.result = fnPrepend.apply(s);

...但不要仅仅为了它而使用 lambda - 这只是相同行为的额外开销。假设真实用例有一个Function的需求,我们可以使用Method References来调用静态方法。这让您两全其美:

  // Available as normal static method
  public static String fnPrepend(String s) {
    return "prefix_" + s;
  }
  
  // Takes a generic Function
  public static void someMethod(UnaryOperator<String> prefixer) {
    ...
  }

  // Coerce the static method to a function
  someMethod(Util::fnPrepend);