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

2022-09-02 13:42:54

我们都知道长扩展 .那么为什么这不能编译呢?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)
  • 生成器中的 getNumber() 类型。MyInterface 是 Number,这与描述符的返回类型不兼容:Long

有关使用案例,请参阅:为什么在编译时不检查 lambda 返回类型


答案 1

此表达式:

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>

并且您传递了一个方法引用,该方法将被推断为这是关键 - 编译器应该如何预测您实际上想要从具有此类签名的函数返回Long它不会为您进行向下铸造。Function<MyInterface, Number>

由于 是 的 超类 并且不一定是 a(这就是为什么它不编译的原因) - 你必须自己显式地转换:NumberLongNumberLong

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

在方法调用期间显式地成为或传递泛型参数,就像您所做的那样:FFunction<MyIinterface, Long>

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

并且知道将被视为和代码将编译。RNumber


答案 2

错误的关键在于 类型为 : 的泛型声明。不起作用的语句是:首先,您有一个新的.因此,类的声明意味着 。根据 你的声明,必须是 a,在这种情况下是 a。因此,该参数必须采用 as 参数(由方法 references 和 满足)并返回 ,该参数的类型必须与函数的第二个参数相同。现在,让我们看看这是否适用于所有情况:FF extends Function<T, R>new Builder<MyInterface>().with(MyInterface::getNumber, 4L);Builder<MyInterface>T = MyInterfacewithFFunction<T, R>Function<MyInterface, R>getterMyInterfaceMyInterface::getNumberMyInterface::getLongRwith

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

除此之外,它主要是一个设计决策,哪个选项可以降低特定应用程序的代码复杂性,因此请选择最适合您的选项。

如果不强制转换就无法做到这一点,原因在于 Java 语言规范中的以下内容:

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

  • 从布尔类型到布尔类型
  • 从类型字节到类型字节
  • 从类型短到类型短
  • 从字符类型到字符类型
  • 从类型 int 到类型整数
  • 从键入长型到键入长型
  • 从类型浮点数到类型浮点数
  • 从双类型到双类型
  • 从空类型到空类型

正如您可以清楚地看到的那样,不存在从 long 到 Number 的隐式装箱转换,并且只有当编译器确定它需要 Number 而不是 Long 时,才会发生从 Long 到 Number 的加宽转换。由于需要 Number 的方法引用与提供 Long 的 4L 之间存在冲突,因此编译器(出于某种原因???)无法实现 Long 是一个数字的逻辑飞跃,并推断出它是一个 .FFunction<MyInterface, Number>

相反,我通过稍微编辑函数签名来设法解决了这个问题:

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

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

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

编辑:
在花费更多时间之后,很难强制实施基于getter的类型安全性。下面是一个使用 setter 方法来强制构建器的类型安全性的工作示例:

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

如果提供了构造对象的类型安全功能,希望将来的某个时候我们能够从生成器返回不可变的数据对象(可能通过向接口添加方法,并将生成器指定为 ),因此您甚至不必担心修改生成的对象。老实说,获得类型安全的字段灵活构建器需要付出如此多的努力,这绝对是一种耻辱,但是如果没有一些新功能,代码生成或令人讨厌的反射量,这可能是不可能的。toRecord()Builder<IntermediaryInterfaceType, RecordType>


推荐