在 JDK 11 中用方法引用替换 lambda 时,类 String 中的方法无法应用于给定类型

Mar*_*ies 4 java lambda java-stream method-reference

我面临一个奇怪的问题

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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

/**
 * Created by mklueh on 13/09/2022
 */
public class Reproducer {

    private static final String COMMA_SEPARATED_STRING = "2020-05-09,hello ,10     ,11.345 ,true    ,1000000000000";

    /**
     * Not working
     */
    @Nested
    public class WithReturn {

        public String withReturn(Stream<String> stream, String delimiter, Function<? super String, String[]> tokenizer) {
            return stream.map(s -> {
                             return Arrays.stream(tokenizer != null ? tokenizer.apply(s) : s.split(delimiter))
                                          .map(String::strip) //Comment out to make it compile
                                          .collect(Collectors.toList());
                         })
                         .flatMap(Collection::stream)
                         .collect(Collectors.joining(","));
        }

        @Test
        void usingDelimiter() {
            assertEquals(COMMA_SEPARATED_STRING.replaceAll(" ", ""),
                    withReturn(Stream.of(COMMA_SEPARATED_STRING), ",", null));
        }

        @Test
        void usingTokenizer() {
            assertEquals(COMMA_SEPARATED_STRING.replaceAll(" ", ""),
                    withReturn(Stream.of(COMMA_SEPARATED_STRING), null, s -> s.split(",")));
        }
    }

    /**
     * Working
     */
    @Nested
    public class WithReturnAndStripLambda {

        public String withReturnAndStripLambda(Stream<String> stream, String delimiter, Function<? super String, String[]> tokenizer) {
            return stream.map(s -> {
                             return Arrays.stream(tokenizer != null ? tokenizer.apply(s) : s.split(delimiter))
                                          .map(s1 -> s1.strip())
                                          .collect(Collectors.toList());
                         })
                         .flatMap(Collection::stream)
                         .collect(Collectors.joining(","));
        }

        @Test
        void usingDelimiter() {
            assertEquals(COMMA_SEPARATED_STRING.replaceAll(" ", ""),
                    withReturnAndStripLambda(Stream.of(COMMA_SEPARATED_STRING), ",", null));
        }

        @Test
        void usingTokenizer() {
            assertEquals(COMMA_SEPARATED_STRING.replaceAll(" ", ""),
                    withReturnAndStripLambda(Stream.of(COMMA_SEPARATED_STRING), null, s -> s.split(",")));
        }
    }


    /**
     * Working
     */
    @Nested
    public class WithMethodExpression {

        public String withMethodExpression(Stream<String> stream, String delimiter, Function<? super String, String[]> tokenizer) {
            return stream.map(s -> Arrays.stream(tokenizer != null ? tokenizer.apply(s) : s.split(delimiter))
                                         .map(String::strip)
                                         .collect(Collectors.toList())
                         )
                         .flatMap(Collection::stream)
                         .collect(Collectors.joining(","));

    }

    @Test
    void usingDelimiter() {
        assertEquals(COMMA_SEPARATED_STRING.replaceAll(" ", ""),
                withMethodExpression(Stream.of(COMMA_SEPARATED_STRING), ",", null));
    }

    @Test
    void usingTokenizer() {
        assertEquals(COMMA_SEPARATED_STRING.replaceAll(" ", ""),
                withMethodExpression(Stream.of(COMMA_SEPARATED_STRING), null, s -> s.split(",")));
    }

  }

}
Run Code Online (Sandbox Code Playgroud)

方法引用无效

error: incompatible types: invalid method reference
                            .map(String::strip)
                                 ^
    method strip in class String cannot be applied to given types
      required: no arguments
      found: long
      reason: actual and formal argument lists differ in length
Run Code Online (Sandbox Code Playgroud)

IntelliJ IDEA 甚至建议执行自动重构以摆脱 lambda 函数,转而使用方法引用

在此输入图像描述

以及用表达式 lambda 替换 return

在此输入图像描述

这里出了什么问题,为什么 IntelliJ 建议破坏代码/不认识到该建议会导致编译器错误?

Mic*_*ael 5

这似乎很可能是 中的一个错误javac。IntelliJ 建议的应该是安全且功能等效的替代品。

看起来是由JDK-8268312引起的,它在 Java 20 中被标记为已修复。


如果不是,这是我在摆弄您的具体示例时发现的其他一些有趣的行为:

除了方法引用失败之外,当您在 lambda 参数列表中显式指定类型为 时,它也会失败String,即String::strip变为(String sa) -> sa.trim()

这是一个比你的更简单的例子。请注意,我故意使三元组的两条路径Arrays.stream执行完全相同的操作。我还使用了trim它,以便它可以在 Java 8 以及更高版本的 JDK 上运行。

Stream.of("A").flatMap(s -> {
    return Arrays.stream(true ? s.split(",") : s.split(","))
        .map((String sa) -> sa.trim());
})
.collect(Collectors.joining(","));
Run Code Online (Sandbox Code Playgroud)

出于某种原因,如果flatMap参数是表达式 lambda,而不是语句 lambda,则它可以正常编译,这不会对类型系统产生影响。

Stream.of("A").flatMap(s ->
    Arrays.stream(true ? s.split(",") : s.split(","))
        .map((String sa) -> sa.trim())
)
.collect(Collectors.joining(","));
Run Code Online (Sandbox Code Playgroud)

如果将三元的结果转换为 ,它也可以工作String[],即使这已经是该表达式的类型,这意味着转换应该是多余的。

Stream.of("A").flatMap(s -> {
        return Arrays.stream((String[]) (true ? s.split(",") : s.split(",")))
            .map((String sa) -> sa.trim());
    })
    .collect(Collectors.joining(","));
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,这个示例在 JDK 8 上编译得很好,而错误报告的示例在 JDK 8 更新 60 左右停止编译。更有趣的是,按照错误消息的建议使用“-Xdiags:verbose”再次编译以获取更多信息,使得错误消失。但看起来,你找到了正确的错误报告,尽管兔子洞可能会更深。 (3认同)