我知道当我读到这个答案时,我会发现我忽略了一些东西 那是在我眼前。但我花了最后30分钟试图弄明白自己 没有结果。
所以,我在Java 6中编写了一个程序,并发现了一些(对我来说)奇怪的功能。 为了尝试隔离它,我做了两个小例子。 我首先尝试了以下方法:
private static int foo()
{
return null;
}
并且编译器拒绝它:类型不匹配:无法从null转换为int。
这对我很好,它尊重我熟悉的Java语义。 然后我尝试了以下内容:
private static Integer foo(int x)
{
if (x < 0)
{
return null;
}
else
{
return new Integer(x);
}
}
private static int bar(int x)
{
Integer y = foo(x);
return y == null ? null : y.intValue();
}
private static void runTest()
{
for (int index = 2; index > -2; index--)
{
System.out.println("bar(" + index + ") = " + bar(index));
}
}
这个编译没有错误!但是,在我看来,应该存在类型转换错误 在行
return y == null ? null : y.intValue();
如果我运行程序,我会得到以下输出:
bar(2) = 2
bar(1) = 1
bar(0) = 0
Exception in thread "main" java.lang.NullPointerException
at Test.bar(Test.java:23)
at Test.runTest(Test.java:30)
at Test.main(Test.java:36)
你能解释一下这种行为吗?
更新
非常感谢您提供的许多澄清答案。我有点担心因为 这个例子与我的直觉不符。令我不安的一件事是 将null转换为int,我想知道结果会是什么 是:0喜欢在C ++中?那会很奇怪。 很好,在运行时无法进行转换(空指针异常)。
答案 0 :(得分:59)
让我们来看看这一行:
return y == null ? null : y.intValue();
在? :
声明中,:
的两边必须具有相同的类型。在这种情况下,Java将使其具有类型Integer
。 Integer
可以null
,因此左侧可以。表达式y.intValue()
的类型为int
,但Java会自动将其设置为Integer
(请注意,您也可以编写y
来保存你这个autobox)。
现在,结果必须再次取消装箱到int
,因为方法的返回类型为int
。如果您取消Integer
null
NullPointerException
,则会获得? :
。
注意:Java语言规范的Paragraph 15.25解释了与{{1}}条件运算符相关的类型转换的确切规则。
答案 1 :(得分:17)
Guava使用MoreObjects.firstNonNull
提供了非常优雅的解决方案:
Integer someNullInt = null;
int myInt = MoreObjects.firstNonNull(someNullInt, 0);
答案 2 :(得分:6)
返回类型的类型由Java在这里推断。这就是问题..
http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25
这是实际问题 -
如果第二个和第三个操作数之一是null类型而另一个操作数的类型是引用类型,则条件表达式的类型是该引用类型。
因此,基本上编译器会将条件表达式的返回类型推断为Integer,这就是为什么它允许您成功编译。
编辑:参见评论中的规则
答案 3 :(得分:5)
如果您的项目中没有Guava,但已经使用了Apache Commons,您可以将Apache Lang3与ObjectUtils类一起使用。
用法与Guava基本相同:
Integer number = null;
int notNull = ObjectUtils.firstNonNull(number, 0);
请注意,Guava库中的此方法比Apache中的方法更快。 以下是我刚在笔记本电脑上进行的简短比较(Core i7-7500U 2.7 GHz),Oracle Java 8,多次运行,JVM预热,结果取平均值:
╔══════════════╦══════╦══════╦════════╦══════╗
║ Library/Runs ║ 1000 ║ 1mln ║ 100mln ║ 1bln ║
╠══════════════╬══════╬══════╬════════╬══════╣
║ Apache ║ 1 ║ 30 ║ 782 ║ 9981 ║
║ Guava ║ 1 ║ 22 ║ 120 ║ 828 ║
╚══════════════╩══════╩══════╩════════╩══════╝
结果以毫秒为单位。 我不认为你经常需要运行这种方法数十亿次,但是,进行性能比较总是好的
答案 4 :(得分:3)
这说明 human 读取代码和编译器读取代码的方式之间存在问题。
当你看到一个三元表达式时,你很可能会以if
/ else
语句的风格将它精神上分成两部分:
if (y == null)
return null;
else
return y.intValue();
你可以看到这是无效的,因为它会导致一个可能的分支,其中定义为返回int
的方法实际上正在返回null
(非法!)。
编译器看到的是表达式,它必须具有类型。它指出,三元操作一方包括null
,另一方包括int
;由于Java的自动装箱行为,它然后提出了关于表达式类型是什么的“最佳猜测”(我的术语,而不是Java):Integer
(这是公平的:它是唯一合法的null
{1}}或装箱int
)。
由于该方法应该返回int
,从编译器的角度来看这很好:返回的表达式求值为Integer
,可以自动取消装箱。
答案 5 :(得分:1)
自动装箱null
值的问题可能非常烦人。在您的示例中,它是三元运算符结果类型推断和自动装箱的组合(应该查询JLS,为什么它的行为类似)
但一般来说,你应该尽量避免使用包装类型。使用int
代替Integer
。如果您需要一个特殊值,意味着“没有结果”,那么您可以使用Integer.MAX_VALUE
作为例子。