首页 > 解决方案 > 使用方法引用和原始类型进行类型推断

问题描述

有没有办法告诉 Java 不要尝试从使用原始类型的方法引用中推断类型?

这是我写的一个方法,其原因现在无关紧要:

    public static <F, T> Predicate<F> isEquals(
            Function<F, T> aInMapFunction, T aInExpectedValue)
    {
        return aInActual -> Objects.equals(
                aInMapFunction.apply(aInActual), aInExpectedValue);
    }

现在,如果将方法引用传递给返回原始类型的“isEquals”会怎样?

Predicate<String> lLengthIs20 = isEquals(String::length, 20);

这一切都很好,很花哨,除了 Java 也会接受这种奇怪的用法:

Predicate<String> lLengthIs20 = isEquals(String::length, "what the heck?!?!?");

这是因为编译器会将类型参数 T 推断为“ Serializable & Comparable<? extends Serializable & Comparable<?>>”,它将接受 Integer 和 String 类型。

就我而言,这是不可取的,因为我想要一个编译错误而不是 Java 找出一些疯狂的类型参数。就我而言,我还可以显式覆盖方法“isEquals”以采用特定的原始类型。例如:

    public static <F> Predicate<F> isEquals(
            ToIntFunction<F> aInMapFunction, int aInExpectedValue)
    {
        return aInActual ->
                aInMapFunction.applyAsInt(aInActual) == aInExpectedValue;
    }

这很好用,当我传入一个返回原始 int 的方法时,会调用此方法而不是 Object 方法。问题是我仍然需要 Object 方法,我无法删除它,这仍然会导致编译器接受我上面列出的奇怪调用。

所以问题是:当方法引用返回原始类型时,我有没有办法告诉 Java 不要使用 isEquals 的 Object 版本?我找不到任何东西,我觉得我在这方面不走运。

(注意:isEquals 的对象版本的实际实现工作正常并且应该是安全的。这是因为 Object.equals 和 Object s .equals 接受 Object 参数,并且 String 对象永远不会等于 Integer 对象。然而,语义上,这看起来很奇怪)

编辑:在“paranoidAndroid”发表评论之后,我刚刚想到的一个想法是按以下方式包装方法引用:

    public static <T> Function<T, Integer> wrap(ToIntFunction<T> aInFunction)
    {
        return aInFunction::applyAsInt;
    }

现在,

Predicate<String> lLengthIs20 = isEquals(wrap(String::length), "what the heck?!?!?");

...生成编译错误。虽然仍然不是很好,也许有更好的方法。至少这比显式传递类型要好,哪种方式超出了目的。

编辑 2:我现在在 Java 8 中。Java 11 在这里可能表现不同,我没有测试。

编辑 3:我认为我们在这里无能为力,这只是类型推断在 Java 中如何工作的暗示。这是另一个例子:

    public static <T> boolean isEquals(T t1, T t2) {
        return Objects.equals(t1, t2);
    }

使用此方法,以下表达式完全有效:

System.out.println(isEquals(10, "20"));

这是可行的,因为 Java 将尝试根据公共上限解析 T 的类型。恰好 Integer 和 String 共享相同的上限Serializable & Comparable<? extends Serializable & Comparable<?>>

标签: javagenericstype-inference

解决方案


我认为这不是错误,而是类型推断的结果。OP已经提到过。编译器不会尝试匹配确切的类型,而是匹配最具体的类型。

让我们通过 OP 提供的示例来分析类型推断是如何工作的。

public static <F, T> Predicate<F> isEquals(Function<F, T> func, T expValue) {
    return actual -> Objects.equals(func.apply(actual), expValue);
}
Predicate<String> lLengthIs20 = isEquals(String::length, "Whud?");

这里的目标类型是Predicate<String>,并且根据方法的返回类型,也就是Predicate<F>(这里F是泛型类型),F绑定到一个String. 然后检查方法引用String::length是否适合方法参数Function<F, T>,在哪里F以及String一些T无界类型。这很重要:虽然方法引用String::length看起来像它的目标类型是Integer,但它也兼容Object. 同样,Object obj = "Hello".length()有效。它不需要Integer. 同样,两者Function<String, Object> func = String::length都是Function<String, Object> func = str -> str.length()有效的,并且不会发出编译器警告。

究竟什么是推理?

推理是将选择适当类型的工作推迟到编译器。你问编译器:“拜托,你能填写适当的类型,让它工作吗?” 然后编译器回答:“好的,但是我在选择类型时遵循某些规则。”

编译器选择最具体的类型。在 的情况下,和isEquals(String::length, 20)的目标类型都是,因此编译器照此推断。String::length20Integer

但是,在isEquals(String::length, "Whud?")编译器首先尝试推断Tan的情况下,Integer因为 的类型String::length,但由于第二个参数的类型,它没有这样做。然后编译器尝试找到 和 的最近Integer交集String

我可以帮助或绕过编译器吗?

旁路?不,不是。好吧,有时类型转换是一种绕过方式,如下例所示:

Object o = 23; // Runtime type is integer
String str = (String) o; // Will throw a ClassCastException

这里的类型转换是一个潜在的不安全操作,因为o可能是也可能不是String. 使用这种类型转换,您对编译器说:“在这种特定情况下,我比您更了解” – 有在运行时出现异常的风险。

尽管如此,并非所有类型转换操作都是允许的:

Integer o = 23;
String str = (String) o;
// Results in a compiler error: "incompatible types: Integer cannot be converted to String"

但是你当然可以帮助编译器。

键入见证

一种选择可能是使用类型 witness

Predicate<String> lLengthIs20 = YourClass.<String, Integer>isEquals(String::length, "what?");

此代码将发出编译器错误:

不兼容的类型:字符串不能转换为整数

添加一个Class<T>参数到isEquals

另一种选择是将参数添加isEquals

public static <F, T> Predicate<F> isEquals(Class<T> type, Function<F, T> func, T expValue) {
    return actual -> Objects.equals(func.apply(actual), expValue);
}
// This will succeed:
Predicate<String> lLengthIs20 = isEquals(Integer.class, String::length, 20);
// This will fail:
Predicate<String> lLengthIs20 = isEquals(Integer.class, String::length, "Whud?");

类型转换

第三种选择可能是类型转换。在这里你强制String::length转换为 a Function<String, Integer>,现在编译器被限制为F = String, T = Integer. 现在使用"Whud?"会引起麻烦。

Predicate<String> predicate = isEquals((Function<String, Integer>) String::length, "Whud?");

推荐阅读