2.5 变量的作用域和生命周期
直至现在,我们使用的所有变量都在main()方法开始处声明。但是,Java允许在块内声明变量。正如第1章介绍的那样,块以左花括号开始,以右花括号结束。块定义一个作用域(scope)。于是,每次开始一个新块时,就创建一个新的作用域。作用域决定哪些对象对程序的其他部分可见。它还决定这些对象的生命周期。
许多其他计算机语言定义两种类型的作用域:全局作用域和局部作用域。尽管这对Java也勉强成立,但是这并不是对Java作用域进行分类的最好办法。Java最重要的作用域是由类和方法定义的作用域。本书在后面介绍类的时候,会讨论类(及其内部定义的变量)的作用域。现在只讨论定义在方法内部的作用域。
由方法定义的作用域以左花括号开始。但是,如果某个方法有形参,它也在方法的作用域内。
一般而言,在作用域内定义的变量对定义域外的代码是不可见的(即不可访问的)。于是,当在一个作用域定义一个变量时,就将变量限制在局部,防止非法访问和修改。作用域的规则事实上提供封装的基础。
作用域可以嵌套。例如,每次创建一个代码块时,就创建一个新的、嵌套的作用域。这时,外部作用域包括内部作用域。这意味着,在外部作用域中声明的对象在内部作用域中的代码仍然可见。但是,反之不然。在内部作用域中声明的对象在其外部不可见。
为了了解嵌套作用域的效果,请考虑以下程序:
正如注释所述,变量x在main()方法开始的作用域内,main()方法里所有后续代码可以访问它。在if块中声明变量y。由于块定义作用域,因此只有块内的其他代码才可见到变量y。这也是为什么注释掉块外的代码(行y=100)的原因。如果去掉该行前的注释符号,就会有编译错误。这是因为变量y在作用域外部。if块内部可以使用变量x,这是因为块内的代码(即嵌套作用域)可以访问作用域内声明的变量。
在块内,可以在任何地方声明变量,但只能在声明过后才有效。于是,如果在方法前声明变量,它对方法内的所有代码都有效。反之,若在块的末尾声明一个变量,由于没有代码可以访问它,因此它是毫无用处的。
另外还有一个要点:在进入作用域时创建变量,在作用域结束时销毁变量。这意味着,一旦出了作用域,变量就不再有其曾经的值。于是,在方法内声明的变量在各次调用间不能保存变量的值。同时,在块结束时,块中声明的变量就失去了它的值。于是,变量的作用域限制了其生命周期。
如果变量的声明包括初始化,在每次进入声明该变量的块时都会对变量初始化。例如,下面的程序:
程序输出如下所示:
可以看到,每次进入for循环时变量y初始化为-1。尽管曾经将值100赋予它,但是这个值丢失了。
Java还有一个令人惊奇的怪癖:尽管块可以嵌套,但是在内部作用域声明的变量不能和外部作用域声明的变量名字相同。例如,下面的程序试图声明两个名字相同的变量,它不能通过编译:
从这点看,其他语言(比如,著名的C和C++语言)对内部作用域中声明变量的名字就没有限制。所以,在C或C++中,外层for循环块内的count的声明是完全有效的,这样的声明会使外层变量不可见。Java的设计者认为这种机制很容易导致编程错误,所以不允许这么做。