为什么本地变量没有在Java中初始化?
Java的设计者觉得局部变量不应该被赋予默认值吗? 严重的是,如果实例变量可以被赋予默认值,那么为什么我们不能为局部变量做同样的事情呢?
而且这也会导致这个评论在一篇博客文章中解释的问题:
那么当尝试关闭finally块中的资源时,这个规则是最令人沮丧的。 如果我在try里面实例化资源,但是试着在finally里关闭它,我得到这个错误。 如果我在try之外移动实例,则会得到另一个错误,说明它必须在try中。
非常令人沮丧。
局部变量主要是为了做一些计算。 所以它的程序员决定设置变量的值,它不应该采用默认值。 如果程序员错误地没有初始化一个局部变量,并且它采用默认值,那么输出可能是一些意想不到的值。 所以在局部变量的情况下,编译器会要求程序员在访问变量之前使用某个值进行初始化,以避免使用未定义的值。
你链接的“问题”似乎在描述这种情况:
SomeObject so; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { so.CleanUp(); // Compiler error here }
该评论者的抱怨是,编译器在finally
一节的行中声称可能是未初始化的。 然后评论提到了另一种编写代码的方式,可能是这样的:
// Do some work here ... SomeObject so = new SomeObject(); try { so.DoUsefulThings(); } finally { so.CleanUp(); }
这位评论员对这个解决方案感到不满,因为编译器接着说这个代码“一定在试一试”。 我想这意味着一些代码可能会引发一个不再被处理的异常。 我不确定。 我的代码版本都不处理任何异常,因此第一个版本中与异常相关的任何东西都应该在第二个版本中工作。
无论如何,这第二个版本的代码是写它的正确方法。 在第一个版本中,编译器的错误信息是正确的。 so
变量可能是未初始化的。 特别是,如果SomeObject
构造函数失败, so
将不会被初始化,所以尝试调用so.CleanUp
将是一个错误。 在获取了finally
一节完成的资源之后,请务必输入try
部分。
so
初始化后的try
– finally
块只是为了保护SomeObject
实例,以确保无论发生什么事情都清理干净。 如果还有其他的东西需要运行,但是它们与SomeObject
实例是否被分配属性SomeObject
,那么他们应该去另一个 try
– finally
块,可能是一个包装我所展示的那个。
要求在使用之前手动分配变量不会导致真正的问题。 这只会导致一些小麻烦,但是你的代码会更好。 你将有更有限的范围变量,并try
– finally
阻止,不要试图保护太多。
如果局部变量具有默认值,那么在第一个例子中是null
。 那真的没有解决任何事情。 而不是在finally
块中得到一个编译时错误,你将有一个NullPointerException
潜伏在那里,可能会隐藏在代码的“做一些工作”部分可能发生的任何其他异常。 (或者在finally
节中的异常会自动链接到以前的异常呢?我不记得了,即使如此,你还是会有一个例外。
此外,在下面的例子中,SomeObject构造中可能会抛出一个异常,在这种情况下,'so'变量将为null,对CleanUp的调用将抛出一个NullPointerException
SomeObject so; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { so.CleanUp(); // Compiler error here }
我倾向于这样做:
SomeObject so = null; try { // Do some work here ... so = new SomeObject(); so.DoUsefulThings(); } finally { if (so != null) { so.CleanUp(); // safe } }
请注意,最终的实例/成员变量默认情况下不会被初始化。 因为这些是最终的,之后不能在程序中改变。 这就是Java不给它们任何默认值并强制程序员初始化它的原因。
另一方面,非最终的成员变量可以稍后改变。 因此,编译器不会让它们保持未初始化,确切地说,因为这些可以在以后更改。 关于局部变量,局部变量的范围要窄得多。 编译器知道它何时被使用。 因此,迫使程序员初始化变量是有道理的。
你的问题的实际答案是因为方法变量通过简单地向堆栈指针添加一个数字来实例化。 把它们归零将是一个额外的步骤。 对于类变量,它们被放入堆中的初始化内存中。
为什么不采取额外的步骤? 退一步 – 没有人提到这种情况下的“警告”是一件很好的事情。
你不应该把你的变量初始化为零或者在第一遍时(当你第一次编码时)。 要么把它分配给实际的值,要么根本不分配它,因为如果你不这样做的话,那么Java会告诉你什么时候你真的搞砸了。 拿电僧的答案来说就是一个很好的例子。 在第一种情况下,它告诉你,如果try()因为SomeObject的构造函数抛出一个异常而失败,那么你最终会在最后得到一个NPE。 如果构造函数不能抛出异常,则不应该在try中。
这个警告是一个令人敬畏的多路径不好的程序员检查器,它使我从做愚蠢的东西,因为它检查每一个路径,并确保如果你在一些路径中使用的变量,那么你必须在每一个导致它的路径初始化。 我现在从来没有明确地初始化变量,直到我确定这是正确的事情。
最重要的是,明确地说“int size = 0”而不是“int size”是不是更好,让下一个程序员去弄清楚你打算为零?
另一方面,我不能拿出一个有效的理由让编译器初始化所有未初始化的变量为0。
我认为主要目的是保持与C / C ++的相似性。 但编译器会检测并警告您使用未初始化的变量,这会将问题降至最低点。 从性能的角度来看,让你声明未初始化的变量要快一些,因为编译器不需要写一个赋值语句,即使你在下一个语句中覆盖变量的值。
(在这个问题之后很长时间发布一个新的答案似乎很奇怪,但重复出现了。)
对我来说, 原因归结为这个:局部变量的目的不同于实例变量的目的。 局部变量在那里被用作计算的一部分; 实例变量在那里包含状态。 如果你使用一个局部变量而不分配一个值,这几乎肯定是一个逻辑错误。
也就是说,我完全可以避免要求实例变量总是显式初始化; 如果结果允许未初始化的实例变量(例如,未在声明中初始化,而在构造函数中未初始化),则会出现错误。 但这不是Gosling等人的决定。 在90年代初,所以我们在这里。 (我不是说他们打错电话了。)
不过,我不能落后于默认的局部变量。 是的,我们不应该依赖编译器来检查我们的逻辑,而不是编译器,但是当编译器捕获一个逻辑时它仍然很方便。 🙂
不初始化变量会更有效率,在局部变量的情况下,这样做是安全的,因为编译器可以跟踪初始化。
在需要初始化变量的情况下,你总是可以自己做,所以这不是问题。
Eclipse甚至会给你提供未初始化变量的警告,所以无论如何它变得非常明显。 我个人认为这是一个好的事情,这是默认的行为,否则你的应用程序可能会使用意外的值,而不是编译器抛出一个错误,它不会做任何事情(但也许给一个警告),然后你会抓你的头,为什么某些事情不像他们应该那样行事。
局部变量存储在堆栈中,但是实例变量存储在堆中,所以有可能堆中的前一个值将被读取而不是堆中的默认值。 出于这个原因,jvm不允许在不初始化的情况下使用局部变量。
实例变量将具有默认值,但本地变量不能具有默认值。 由于局部变量基本上是方法/行为,其主要目的是做一些操作或计算。 因此,为局部变量设置默认值并不是一个好主意。 否则,检查意想不到的答案的原因是非常困难和耗时的。
答案是实例变量可以在类的构造函数或任何类方法中初始化,但是在局部变量的情况下,一旦你定义了永远在类中的方法。
我可以想到以下两个原因
- 由于大部分答案都是通过设置初始化局部变量的约束来确定的,因此可以确保局部变量被程序员需要赋值,并确保计算预期的结果。
- 实例变量可以通过声明局部变量(同名)来隐藏 – 为了确保预期的行为,局部变量被迫初始化一个值。 (虽然完全避免这个)