作用域规则与变量覆盖面试题
Java Magazine上面有一个专门坑人的面试题系列: https://blogs.oracle.com/javamagazine/quiz-2 。
这些问题的设计宗旨,主要是测试面试者对Java语言的了解程度,而不是为了用弯弯绕绕的手段把面试者搞蒙。
如果你看过往期的问题,就会发现每一个都不简单。
这些试题模拟了认证考试中的一些难题。 而 “中级(intermediate)” 和 “高级(advanced)” 指的是试题难度,而不是说这些知识本身很深。 一般来说,“高级”问题会稍微难一点。
下面哪些代码是正确的写法?
class C1 { void foo(int a) { for (int a = 0; a < 5; a++) { } } }
class C2 { int a = 0; { int a = 1; } }
class C3 { { int a = 0; } { int a = 1; } }
class C4 { { int a = 0; for (int a = 0; a < 5; a++) { } } }
class C5 { { for (int a = 0; a < 5; a++) { } int a = 0; } }
到底是哪些选项正确呢? 请先思考,再看下面的解答。
这道题主要考察变量的作用域以及优先级:同一作用域下如果存在两个相同的变量名称(标识符),那么会简单会指向一个变量而忽略另一个。
一般来说,局部变量会覆盖同名的类属性和实例属性,但方法作用域内的局部变量则不允许覆盖。
在编写程序代码时,一般规范都会要求明确指定类名或者 this
来引用对应的字段。
下面是一个简单的使用示例:
public class MyClass { static int x = 99; int y = 100; public static void showX() { int x = 9; System.out.println("x is " + x); // x is 9 System.out.println("MyClass.x is " + MyClass.x); // MyClass.x is 99 } public void showY() { int y = 10; System.out.println("y is " + y); // y is 10 System.out.println("this.y is " + this.y); // this.y is 100 } }
接下来依次解读试题中给出的选项。
先看选项 A
,
class C1 { void foo(int a) { for (int a = 0; a < 5; a++) { } } }
可以看到有一个变量 a
作为方法参数。
方法参数也是局部变量,其作用域范围从参数列表开始,一直到方法结束。
在方法内部的 for
循环中,也声明了一个名为 a
的局部变量。
因此,根据规则, 在某个作用域范围内,不允许存在多个同名的局部变量
, 所以这段代码无法编译, 选项A不正确
。
选项 B
,
class C2 { int a = 0; { int a = 1; } }
类中定义了实例变量 a
,在初始化语句块中又定义了一个局部变量 a
,类似于方法代码中的局部变量声明。
所以在初始化块的作用域范围内, 局部变量覆盖了实例属性。
这段代码可以正确编译并运行, 所以 选项B正确
。
选项 C
的内容如下,
class C3 { { int a = 0; } { int a = 1; } }
在两个单独的初始块中, 都声明了局部变量 a
, 但各自的作用域范围都被限制为代码块之中, 所以不会发生重叠,也就没有变量冲突。
当然,这段代码没什么实际的作用,因为在初始化块中声明的变量,在其他地方都不可看,在语句块执行完成后会被丢弃。
可能有些同学会觉得编译器会报错,但实际上这些代码块都是会执行的,并没有不可达代码。
既然语法没问题, 那么 选项C正确
。
看选项 D
,
class C4 { { int a = 0; for (int a = 0; a < 5; a++) { } } }
代码块中先是声明了一个局部变量, 然后 for
循环中又声明了同名的变量。
因为for循环中声明的局部变量,作用域范围从声明处开始,直到循环结束。
也就是说, 在循环体范围内,存在两个同名的局部变量,这是违反语法规定的。
跟选项 A
中的情况有点类似,区别只在于选项 A
中声明的是方法参数,而选项D中声明的是普通局部变量。
由此可知, 选项D不正确
。
最后看选项 E
,
class C5 { { for (int a = 0; a < 5; a++) { } int a = 0; } }
和选项 D
看起来有点像, 但这次是循环先声明自己的局部变量,循环结束后,后面的代码接着声明了一个同名的普通局部变量。
这两个局部变量的作用域并不重叠,也不冲突,所以代码有效, 选项E正确
。
在 《Java语言规范 - 6.3 变量声明与作用域》 一节中对变量声明的作用域范围做了详细说明。
有两个需要着重强调的点:
for
通过我们的分析可知,正确选项为: B
, C
, E
.