作用域規則與變量覆蓋面試題
Java Magazine上面有一個專門坑人的面試題系列: https://blogs.oracle.com/javamagazine/quiz-2。
這些問題的設計宗旨,主要是測試面試者對Java語言的瞭解程度,而不是爲了用彎彎繞繞的手段把面試者搞蒙。
如果你看過往期的問題,就會發現每一個都不簡單。
這些試題模擬了認證考試中的一些難題。 而 “中級(intermediate)” 和 “高級(advanced)” 指的是試題難度,而不是說這些知識本身很深。 一般來說,“高級”問題會稍微難一點。
問題(中級難度)
下面哪些代碼是正確的寫法?
- A.
class C1 {
void foo(int a) {
for (int a = 0; a < 5; a++) { }
}
}
- B.
class C2 {
int a = 0;
{ int a = 1; }
}
- C.
class C3 {
{ int a = 0; }
{ int a = 1; }
}
- D.
class C4 {
{
int a = 0;
for (int a = 0; a < 5; a++) { }
}
}
- E.
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 變量聲明與作用域》 一節中對變量聲明的作用域範圍做了詳細說明。
有兩個需要着重強調的點:
- 方法參數的作用域範圍: 形參的作用域範圍,是方法,構造函數或lambda表達式對應的body。
for
循環初始化語句中聲明的局部變量, 作用域範圍包括: 初始化部分、初始化後面的條件判斷部分,遞增更新語句,以及循環體中的語句。
通過我們的分析可知,正確選項爲: B
, C
, E
.
相關鏈接
- Java坑人面試題系列: 包裝類(中級難度)
- Java坑人面試題系列: 比對while與for循環(中級)
- Java坑人面試題系列: 集合(高級)
- Java坑人面試題系列: 線程/線程池(高級)
- Java坑人面試題系列: 變量聲明(中級)
原文鏈接: https://blogs.oracle.com/javamagazine/quiz-yourself-variable-declaration-intermediate