Java中未初始化的类成员不会发出任何编译器错误。 然而局部变量呢。 为什么?

请考虑Java中的以下代码段。 它不会编译。

package temppkg; final public class Main { private String x; private int y; private void show() { String z; int a; System.out.println(x.toString()); // Causes a NullPointerException but doesn't issue a compiler error. System.out.println(y); // Works fine displaying its default value which is zero. System.out.println(z.toString()); // Causes a compile-time error - variable z might not have been initialized. System.out.println(a); // Causes a compile-time error - variable a might not have been initialized. } public static void main(String []args) { new Main().show(); } } 

为什么在上面的代码片段中声明的类成员( x和y )不会发出任何编译时错误,即使它们没有显式初始化并且只需要初始化局部变量?

如有疑问,请检查Java语言规范 (JLS)。

在介绍中你会发现:

第16章描述了语言确保在使用前明确设置局部变量的精确方式。 虽然所有其他变量都自动初始化为默认值,但Java编程语言不会自动初始化局部变量,以避免屏蔽编程错误。

第16章第一段指出,

当对其值的任何访问发生时,每个局部变量和每个空白最终字段必须具有明确赋值。一个Java编译器必须执行特定的保守流分析,以确保每次访问局部变量或空白最终字段f,f在访问之前是明确分配的; 否则必须发生编译时错误。

默认值本身在4.12.5节中 。 该部分打开时:

每个类变量,实例变量或数组组件在创建时都使用默认值进行初始化。

…然后继续列出所有默认值。

JLS实际上并不难理解,我发现自己越来越多地使用它来理解为什么Java会做它的function……毕竟,它是Java圣经!

为什么他们会发出编译警告?,因为实例变量String将获得默认值null,int将获得默认值0。

编译器无法知道x.toString()会导致运行时exception,因为在运行时之后才会实际设置null的值。

类成员可能已在代码中的其他位置初始化,因此编译器无法查看它们是否在编译时初始化。

在构造(实例化)对象时,成员变量会自动初始化为其默认值。 即使手动初始化它们也是如此,它们将首先初始化为默认值,然后初始化为您提供的值。

这是一篇有点冗长的文章,但它解释了它: Java中的对象初始化

而局部变量(在方法中声明的变量)不会自动初始化,这意味着您必须手动执行,即使您希望它们具有默认值。

您可以在此处查看具有不同数据类型的变量的默认值。

引用类型变量的默认值为null 。 这就是为什么它在以下方面抛出NullPointerException

 System.out.println(x.toString()); // Causes a NullPointerException but doesn't issue a compiler error. 

在下面的例子中,编译器足够聪明,知道变量尚未初始化(因为它是本地的,你还没有初始化它),这就是编译问题的原因:

 System.out.println(z.toString()); // "Cuases a compile-time error - variable z might not have been initialized. 

通常,编译器无法确定类成员之前是否已初始化。 例如,您可以使用setter方法设置类成员的值,以及另一个访问该成员的方法。 编译器在访问该变量时不能发出警告,因为它无法知道之前是否已调用过setter。

我同意在这种情况下(成员是私有的,没有单一的方法来编写变量),它似乎可以从编译器发出警告。 好吧,实际上你仍然不确定该变量是否已经初始化,因为它可以通过reflexion访问。

使用局部变量更容易,因为它们不能从方法外部访问,甚至也不能通过reflection访问(afaik,如果错误请纠正我),因此编译器可以更有帮助并警告我们未初始化的变量。

我希望这个答案可以帮助你:)