java - 使用方法引用和原始类型进行类型推断
问题描述
有没有办法告诉 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<?>>
解决方案
我认为这不是错误,而是类型推断的结果。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::length
20
Integer
但是,在isEquals(String::length, "Whud?")
编译器首先尝试推断T
an的情况下,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?");
推荐阅读
- javascript - React JS - 如何根据 URL 路径显示/隐藏组件的某些部分?
- java - java中的深度克隆非可序列化对象
- python - 如何有效地循环遍历 DataFrame 并与字典进行比较并从中获取值?
- c# - 运行 SqlLite 集成测试时出现 EF Core 时间戳错误
- javascript - 打字时如何获取文本的音节数?
- python - 我对python测试很陌生。帮助这个方法解释单元测试
- real-time - 处理器带宽的含义
- node.js - 如何将调用'then'的结果用于下一个'then'?
- python-3.x - 面向对象的方法是否适合我的任务?如果是这样,关于如何实施它的粗略想法?
- git - git 在 OS X 用户之间共享,执行命令时混淆 SSH 密钥吗?