我是否破坏了 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
我写了一个 "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