我是否破坏了 Java 的局部变量类型推断?

Did I break Java's local variable type inference?

我写了一个 "typifies" 和 String 的方法,并试图推断其中保存的数据类型。 (this gist). The method returns the inferred Class and the original String (possibly slightly modified -- surrounding whitespace trimmed, etc.) in a Map.Entry<Class, String> 的略微修改版本。例如,typify("3f") returns <Float, "3.0">typify(" c ") returns <Character, "c"> 等等。

我的下一步是编写第二个方法,"decodes" 这些方法返回 Map.Entry 个对象,因此可以将它们直接分配给推断类型的对象。例如:

Float f = decodeTypify(typify("3.14f"))
Boolean b = decodeTypify(typify("false"))

...等等。此代码如下:

  @SuppressWarnings("unchecked")
  public static <T> T decodeTypify (Entry<Class, String> entry) {

    // String
    if (entry.getKey() == String.class)
      return (T) entry.getValue();

    // Boolean
    else if (entry.getKey() == Boolean.class)
      return (T) (Boolean) Boolean.parseBoolean(entry.getValue());

    // Byte
    else if (entry.getKey() == Byte.class)
      return (T) (Byte) Byte.parseByte(entry.getValue());

    // Character
    else if (entry.getKey() == Character.class)
      return (T) (Character) entry.getValue().charAt(0);

    // Short
    else if (entry.getKey() == Short.class)
      return (T) (Short) Short.parseShort(entry.getValue());

    // Integer
    else if (entry.getKey() == Integer.class)
      return (T) (Integer) Integer.parseInt(entry.getValue());

    // Long
    else if (entry.getKey() == Long.class)
      return (T) (Long) Long.parseLong(entry.getValue());

    // Float
    else if (entry.getKey() == Float.class)
      return (T) (Float) Float.parseFloat(entry.getValue());

    // Double
    else if (entry.getKey() == Double.class)
      return (T) (Double) Double.parseDouble(entry.getValue());

    // LocalDateTime
    else if (entry.getKey() == LocalDateTime.class)
      return (T) (LocalDateTime) stringAsDate(entry.getValue());

    else return null;
  }

这似乎工作得很好,尤其是与 Java 的新局部变量类型推断结合使用时:

var f = decodeTypify(typify("literally anything"))

现在我根本不需要关心返回的类型,因为 Java 负责为 f 提供正确的类型。但是请注意,如果 decodeTypify()entry 参数有一个与大 if-else 树中的任何选项都不匹配的键,那么 decodeTypify() returns null。这是 jshell 中的 运行 方法 Java 11.0.1:

jshell> var x = decodeTypify(typify(null))
x ==> null

我将 null 值赋给了一个本地类型推断变量!这个。这个(看起来)的一个副作用是我实际上可以告诉 x 有任何类型,没有警告:

jshell> Object x = decodeTypify(typify(null))
x ==> null

jshell> String x = decodeTypify(typify(null))
x ==> null

jshell> Byte x = decodeTypify(typify(null))
x ==> null

请注意,非null returns不是这种情况:

jshell> var x = decodeTypify(typify("3"))
x ==> 3.0

jshell> Boolean x = decodeTypify(typify("3"))
|  Exception java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.Boolean (java.lang.Double and java.lang.Boolean are in module java.base of loader 'bootstrap')
|        at (#21:1)

我是不是弄坏了什么?如果不是,有人可以解释这里发生了什么吗?

你没有破坏任何东西。您不能直接分配 null,但通过方法调用间接分配它是完全可以的。

原因是,仅通过分配 null,编译器没有信息知道您想要什么类型。唯一可以做出的推断是最通用的可用类型,Object,如果这是正确的推断,那么只需明确声明它即可!多了3个字符。

当编译器有方法调用要使用时,它可以使用方法的 return 类型来进行类型推断。

public static String foo() {
    return null;
}

public static <T> T bar() {
    return null;
}

public static <T> T baz(Class<T> clazz) {
    return null;
}

public static void main(String[] args) {
   var a = null;  // compile error
   var b = foo(); // fine
   var c = bar(); // fine
   var d = baz(String.class); //fine
}

您可以将 null 值分配给本地类型推断变量。 您只是不能使用 null 初始值设定项实例化此类变量

我检查了您引用的要点,很明显,如果您将方法 'null' 作为输入(值,而不是字符串)提供,那么它会将内容设置为对象类型。 您正在初始化一个空对象,'var' 可以处理。至少,编译器知道您正在使用对象 class。此外,方法将具有 return 类型,因此 var 也可以使用它。

至于类型分配切换的副作用……转换 null 总是有效,所以泛型处理得很好并不奇怪:No Exception while type casting with a null in java