为什么Java不能推断超类型?

问题描述 投票:18回答:6

我们都知道Long扩展了Number。那么为什么不编译呢?

以及如何定义方法with,使程序无需任何手工强制转换就可以编译?

import java.util.function.Function;

public class Builder<T> {
  static public interface MyInterface {
    Number getNumber();
    Long getLong();
  }

  public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
    return null;//TODO
  }

  public static void main(String[] args) {
    // works:
    new Builder<MyInterface>().with(MyInterface::getLong, 4L);
    // works:
    new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
    // works:
    new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
    // works:
    new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
    // compilation error: Cannot infer ...
    new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
    // compilation error: Cannot infer ...
    new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
    // compiles but also involves typecast (and Casting Number to Long is not even safe):
    new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
    // compiles but also involves manual conversion:
    new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
    // compiles (compiler you are kidding me?): 
    new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);

  }
  static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
    return f;
  }

}

  • 无法推断<F, R> with(F, R)的类型参数
  • 来自类型Builder.MyInterface的getNumber()的类型为Number,这与描述符的返回类型不兼容:Long

有关使用案例,请参见:Why is lambda return type not checked at compile time

java type-inference
6个回答
9
投票

此表达式:

new Builder<MyInterface>().with(MyInterface::getNumber, 4L);

可以改写为:

new Builder<MyInterface>().with(myInterface -> myInterface.getNumber(), 4L);

考虑方法签名:

public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue)
  • [R将被推断为Long
  • [F将为Function<MyInterface, Long>

并且您传递了一个将被推断为Function<MyInterface, Number>的方法引用-这是关键-编译器应如何预测您实际上想从具有此类签名的函数中返回Long垂头丧气。

因为NumberLong的超类,并且Number不一定是Long(这就是为什么它不编译的原因-您必须自己显式地强制转换:

new Builder<MyInterface>().with(myInterface -> (Long) myInterface.getNumber(), 4L);

使F成为Function<MyIinterface, Long>或像您一样在方法调用期间显式传递通用参数:

new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);

并且知道R将被视为Number,并且代码将被编译。


4
投票

错误的关键是在FF extends Function<T, R>类型的通用声明中。无效的语句是:new Builder<MyInterface>().with(MyInterface::getNumber, 4L);首先,您有一个新的Builder<MyInterface>。因此,该类的声明暗含T = MyInterface。根据您对with的声明,F必须为Function<T, R>,在这种情况下为Function<MyInterface, R>。因此,参数getter必须以MyInterface作为参数(方法参考MyInterface::getNumberMyInterface::getLong满足),并返回R,该类型必须与函数[ C0]。现在,让我们看看这是否适用于所有情况:

with

您可以使用以下选项“解决”此问题:

// T = MyInterface, F = Function<MyInterface, Long>, R = Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L explicitly widened to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Long
// F = Function<T, not R> violates definition, therefore compilation error occurs
// Compiler cannot infer type of method reference and 4L at the same time, 
// so it keeps the type of 4L as Long and attempts to infer a match for MyInterface::getNumber,
// only to find that the types don't match up
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);

在这一点上,主要是设计决定,即哪个选项可降低特定应用程序的代码复杂性,因此请选择最适合的选项。

[不进行强制转换就无法执行此操作的原因在于以下// stick to Long new Builder<MyInterface>().with(MyInterface::getLong, 4L); // stick to Number new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L); // explicitly convert the result of getNumber: new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L); // explicitly convert the result of getLong: new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);

装箱转换将原始类型的表达式视为相应引用类型的表达式。具体来说,以下九种转换称为装箱转换

  • 从布尔类型到布尔类型
  • 从字节类型到字节类型
  • 从短类型到短类型>>
  • 从字符型到字符型
  • 从int类型到Integer类型>>
  • 从long型到long型
  • 从浮点类型到浮点类型
  • 从双精度类型到双精度类型
  • 从空类型到空类型
  • 您可以清楚地看到,没有从long到Number的隐式装箱转换,并且只有当编译器确定它需要一个Number而不是Long时,才可能发生从Long到Number的扩展转换。由于需要Number的方法引用与提供Long的4L之间存在冲突,因此编译器(由于某种原因?)无法做出Long is-a Number的逻辑飞跃,并推论出from the Java Language Specification为一个F

相反,我设法通过稍微编辑功能签名来解决此问题:

Function<MyInterface, Number>

此更改后,将发生以下情况:

public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
  return null;//TODO
}

编辑:

在花了更多时间之后,很难实现基于getter的类型安全。这是一个使用setter方法强制执行构建器的类型安全性的工作示例:
// doesn't work, as it should not work
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works, as it should work
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);

提供了构造对象的类型安全能力,希望在将来的某个时候,我们能够从构建器中返回public class Builder<T> { static public interface MyInterface { //setters void number(Number number); void Long(Long Long); void string(String string); //getters Number number(); Long Long(); String string(); } // whatever object we're building, let's say it's just a MyInterface for now... private T buildee = (T) new MyInterface() { private String string; private Long Long; private Number number; public void number(Number number) { this.number = number; } public void Long(Long Long) { this.Long = Long; } public void string(String string) { this.string = string; } public Number number() { return this.number; } public Long Long() { return this.Long; } public String string() { return this.string; } }; public <R> Builder<T> with(BiConsumer<T, R> setter, R val) { setter.accept(this.buildee, val); // take the buildee, and set the appropriate value return this; } public static void main(String[] args) { // works: new Builder<MyInterface>().with(MyInterface::Long, 4L); // works: new Builder<MyInterface>().with(MyInterface::number, (Number) 4L); // compile time error, as it shouldn't work new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L); // works, as it always did new Builder<MyInterface>().with(MyInterface::Long, 4L); // works, as it should new Builder<MyInterface>().with(MyInterface::number, (Number)4L); // works, as you wanted new Builder<MyInterface>().with(MyInterface::number, 4L); // compile time error, as you wanted new Builder<MyInterface>().with(MyInterface::number, "blah"); } } (也许通过向接口添加immutable data object方法,并指定生成器作为toRecord()),因此您甚至不必担心会修改生成的对象。坦率地说,要获得类型安全的字段灵活的生成器需要大量的精力,这是绝对的耻辱,但是如果没有一些新功能,代码生成或令人讨厌的反射,这可能是不可能的。

似乎编译器使用值4L来确定R为Long,而getNumber()返回一个Number,它不一定是Long。

但是我不确定为什么值优先于方法...

Java编译器通常不擅长推断多个/嵌套的泛型类型或通配符。通常,如果不使用辅助函数来捕获或推断某些类型,我将无法编译某些东西。

但是,您真的需要将Builder<IntermediaryInterfaceType, RecordType>的确切类型捕获为Function吗?如果没有,那么也许可以用下面的方法(如您所见)处理F的子类型。

Function

最有趣的部分在于这两行之间的区别,我认为:

import java.util.function.Function;
import java.util.function.UnaryOperator;

public class Builder<T> {
    public interface MyInterface {
        Number getNumber();
        Long getLong();
    }

    public <R> Builder<T> with(Function<T, R> getter, R returnValue) {
        return null;
    }

    // example subclass of Function
    private static UnaryOperator<String> stringFunc = (s) -> (s + ".");

    public static void main(String[] args) {
        // works
        new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
        // works
        new Builder<String>().with(stringFunc, "s");

    }
}

[在第一种情况下,// works: new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L); // compilation error: Cannot infer ... new Builder<MyInterface>().with(MyInterface::getNumber, 4L); 明确为T,因此Number也是4L,没问题。在第二种情况下,Number4L,因此LongT,因此您的功能不兼容,并且Java无法知道您的意思是Long还是Number

带有以下签名:

Long

您的所有示例均进行编译,但第三个示例除外,后者明确要求该方法具有两个类型变量。

您的版本无法使用的原因是,因为Java的方法引用没有特定的类型。相反,它们具有给定上下文中所需的类型。在您的情况下,由于public <R> Test<T> with(Function<T, ? super R> getter, R returnValue) 推断RLong,但getter不能具有类型4L,因为在Java中,泛型类型的参数是不变的。


1
投票

似乎编译器使用值4L来确定R为Long,而getNumber()返回一个Number,它不一定是Long。


0
投票

Java编译器通常不擅长推断多个/嵌套的泛型类型或通配符。通常,如果不使用辅助函数来捕获或推断某些类型,我将无法编译某些东西。


0
投票

最有趣的部分在于这两行之间的区别,我认为:


0
投票

带有以下签名:

© www.soinside.com 2019 - 2024. All rights reserved.