当前位置: 首页 > 知识库问答 >
问题:

具有方法引用和基元类型的类型推断

方航
2023-03-14

有没有办法告诉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

在我的例子中,这是不可取的,因为我希望出现编译错误,而不是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和Objects.equals接受Object参数,String对象永远不会等于整数对象。然而,从语义上来说,这看起来很奇怪)

编辑:在“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:我现在在用Java8。Java11在这里的行为可能不同,我没有进行测试。

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

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

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

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

这是有效的,因为Java将尝试基于公共上限解析T的类型。只是碰巧整数和字符串共享相同的上限Serializable


共有3个答案

邵畅
2023-03-14

“我有没有办法告诉Java不要使用对象版本…”

对在泛型的上下文中,告诉Java不要使用对象的术语被称为:“指定边界”。

我的实验证实,将以下方法调用isEquals(String::hashCode),“理论上他妈的是什么!

public static <F extends String, T extends Number> Predicate<F> isEquals(Function<F, T> aFunction, T aValue)
{
    return input -> Objects.equals(aFunction.apply(input), aValue);
}  

如果上面的方法和下面的方法都在同一个类中,那么这个版本被调用为isEquals(字符串::长度,20)...

public static <F> Predicate<F> isEquals(ToIntFunction<F> aFunction, int aValue)
{
    return input -> aFunction.applyAsInt(input) == aValue;
}

...但是第一个是为isEquals(String::length,Integer.valueOf(42))调用的。

单击此演示中的蓝色执行按钮以查看其工作。

须新
2023-03-14

就我而言,这对我来说闻起来像一个真正的java编译器错误...编译器应该能够推断参数而不分配给变量,因为我们有Function

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

使用方法:

new PredicateBuilder<String, Integer>().isEquals(String::length, 5);

不会编译与其他参数类型,也不会编译,如果你尝试这样做:

new PredicateBuilder<>().isEquals(String::length, 5);

太叔昊苍
2023-03-14

我认为这不是一个bug,而是类型推断的结果。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?");

这里的目标类型是谓词

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

编译器选择最具体的类型。在isEquals(String::length,20)的情况下,String::length20的目标类型都是Integer,因此编译器将其推断为整数。

然而,在isEquals(字符串::长度,Whud?)编译器首先试图推断T整数,因为类型的String::长度,但由于第二个参数的类型,它无法这样做。编译器然后试图找到整数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"

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

一种选择可能是使用类型见证:

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

代码将发出编译器错误:

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

另一个选项是将参数添加到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转换为函数

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

 类似资料:
  • 本文向大家介绍再谈Javascript中的基本类型和引用类型(推荐),包括了再谈Javascript中的基本类型和引用类型(推荐)的使用技巧和注意事项,需要的朋友参考一下 一、基本类型和引用类型概述 js中数据类型的值包括:基本类型值和引用类型值 基本数据类型:undefined;null;boolean;number;string 引用类型值:保存在内存中,js不允许直接访问内存位置,因此时操作

  • 问题内容: 我了解编译器使用目标类型来确定使通用方法调用适用的类型参数。例如,在以下语句中: 其中的签名中具有类型参数 在这种情况下,推断出的类型参数是。 现在考虑以下几点: 在这种情况下,推断的类型是什么?是吗 还是因为通配符告诉编译器任何类型都是可能的? 问题答案: 通配符的每种用法都有与之关联的不同类型。(通常,JLS将此称为“新鲜类型”。)例如,这就是这样的编译器错误的工作方式: 因为在这

  • 我知道编译器使用目标类型来确定使泛型方法调用适用的类型参数。例如,在以下声明中: 其中

  • 为什么Java可以推断出多个上限类型的共同祖先,而不能推断出下限类型的共同祖先? 更具体地说,请考虑以下示例:

  • ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。 在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。第3 章讨论了5 种基本数据类型:Undefined、Null、Boolean、Number 和String。这5 种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的

  • J.Bloch在《Effective Java》中提到,将varargs方法与基元类型一起使用是不安全的。简而言之,的返回类型为,这听起来很合理。现在我试图自己复制这种行为,但做不到: 我的问题是,为什么类型被推导为,而不是像他所说的那样被推导为?这是否意味着,在Java8中,关于varargs的问题不再相关了,如果我们不太关心性能,我们可以在任何地方安全地使用它们。