使用方法引用和原始类型进行类型推断 键入见证人向Class<T>添加一个isEquals参数类型转换

问题描述

有没有办法告诉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方法,无法删除它,这仍将导致编译器接受我上面列出的怪异调用

所以问题是:当方法引用返回原始类型时,有没有办法让我告诉Java不要使用isEquals的对象版本?我什么也找不到,我觉得自己不走运。

(注意: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的类型。碰巧整数和字符串共享相同的上限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>,其中FStringT是某种无界类型。这很重要:尽管方法引用String::length的目标类型为Integer,但它也与Object兼容。同样,Object obj = "Hello".length()有效。 不需要Integer。同样,Function<String,Object> func = String::lengthFunction<String,Object> func = str -> str.length()均有效,并且不会发出编译器警告。

推理到底是什么?

推断是为了将选择适当类型的工作推迟到编译器。您问编译器:“请,您可以填写适当的类型,以便它起作用吗?”然后编译器回答:“好的,但是选择类型时我遵循某些规则。”

编译器选择最特定的类型。对于isEquals(String::length,20)String::length20的目标类型均为Integer,因此编译器将其推断为此类。

但是,在isEquals(String::length,"Whud?")的情况下,由于T的类型,编译器首先尝试将Integer推导到String::length,但由于这样做而失败了。第二个参数的类型。然后,编译器尝试找到IntegerString的最近交点。

我可以帮助还是绕过编译器?

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

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?");

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

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

Class<T>添加一个isEquals参数

另一种选择是isEquals添加参数

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

类型转换

第三个选项可能是 类型转换 。在这里,您将String::length强制转换为Function<String,Integer>,现在编译器被限制为F = String,T = Integer。现在使用"Whud?"会带来麻烦。

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

…我有办法告诉Java不要使用Object版本…

是的。用来告诉Java不要使用Object的术语“在泛型上下文中的 ”被称为:“ Specify an bound ”。

My experiment confirmed,将以下方法称为isEquals(String::hashCode,"What the theoretical fuck!&?*!?@!")将产生error: no suitable method found for isEquals(String::hashCode,String) ...

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(String::length,20) ...

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

...但是第一个叫isEquals(String::length,Integer.valueOf(42))

点击蓝色的 Execute 按钮in this demo可以正常使用。

,

就我而言,这对我来说就像一个真正的Java编译器错误。.编译器应该能够推断参数而无需分配变量,因为我们有Function<F,T> aInMapFunction应该强制执行T,因为编译器“知道” String::length返回一个整数。 但是我为您提出了一种解决方案:

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);