3.8.3 变量的作用域和生命周期
到目前为止,使用的所有变量都是在main()方法开始时声明的。然而,Java允许在任何代码块中声明变量。正如在第2章中所解释的,代码块以开花括号开始并以闭花括号结束。代码块定义了作用域。因此,每当开始一个新的代码块时就创建了一个新的作用域。作用域决定了对象对程序其他部分的可见性,并且也决定了这些对象的生命周期。
许多其他计算机语言定义了两种通用的作用域类别:全局作用域和局部作用域。然而,这些传统的作用域不能很好地适应Java中严格的、面向对象的模型。虽然可以创建属于全局作用域的变量,但这只是例外,而不是规则。在Java中,两种主要的作用域分别是由类和方法定义的。尽管这种分类有些人为因素,但是,由于类作用域具有的一些独特属性和特性,不能应用于由方法定义的作用域,因此这种分类方法有一定的道理。由于存在这种差别,对类作用域(以及在其中声明的变量)的讨论将推迟到第6章,那时会介绍类的相关内容。现在,只分析由方法定义以及在方法中定义的作用域。
由方法定义的作用域从方法的开花括号开始。然而,如果方法具有参数,那么它们也会被包含到方法的作用域中。尽管本书在第6章会进一步分析参数,但是出于作用域讨论的目的,在此将它们与任何其他方法变量一样一并考虑。
作为通用规则,在作用域中声明的变量,对于在作用域之外定义的代码是不可见的(即不可访问)。因此,当在某个作用域中声明变量时,就局部化了该变量,并保护它免受未授权的访问和/或修改。实际上,作用域规则为封装提供了基础。
作用域是可以嵌套的。例如,每当创建一个代码块时,就创建了一个新的、嵌套的作用域。当遇到这种情况时,外层的作用域包围了内层作用域。这意味着在外层作用域中声明的对象对于内层作用域中的代码是可见的。然而,反过来就不是这样了,在内层作用域中声明的对象,在内层作用域之外是不可见的。
为了理解嵌套作用域的影响,分析下面的程序:
正如注释指出的,变量x是在main()作用域的开始处声明的,因此main()方法中的所有后续代码都可以访问变量x。变量y是在if代码块中声明的,既然代码块定义了作用域,所以只有对于if代码块中的代码y才是可见的。这就是为什么在if代码块之外,将“y=100;”注释掉的原因。如果删除前面的注释符号,就会发生编译时错误,因为在if代码块之外y不是可见的。在if代码块的内部可以使用x,因为代码块(即嵌套的作用域)中的代码可以访问在外部作用域中声明的变量。
在代码块中,可以在任意位置声明变量,但是只有在声明之后变量才是有效的。因此,如果在方法的开头定义变量,那么变量对于该方法的所有代码都是可见的。相反,如果在代码块的末尾声明变量,那么变量是无用的,因为没有代码能够访问该变量。例如,下面的代码片段是无效的,因为count在声明之前不能使用:
下面是另外一个重点:当进入变量的作用域时创建变量,当离开它们的作用域时销毁变量。这意味着一旦离开作用域,变量就不会再保持原来的值。所以,对于在方法中声明的变量来说,在两次调用该方法之间,变量不会保持它们的值。此外,对于在代码块中声明的变量来说,当离开代码块时会丢失它们的值。因此,变量的生命周期被限制在作用域之内。
如果变量声明包含初始化器,那么每当进入声明变量的代码块时都会重新初始化变量。例如,分析下面的程序:
|
这个程序产生的输出如下所示:
可以看出,每次进入内部的for循环时y都被重新初始化为−1。尽管随后y被赋值为100,但是这个值丢失了。
最后一点:尽管可以嵌套代码块,但是在内层代码块中不能声明与外层代码块具有相同名称的变量。例如,下面的程序是非法的: