Java坑人面試題系列: 變量聲明(中級難度)

作用域規則與變量覆蓋面試題

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 變量聲明與作用域》 一節中對變量聲明的作用域範圍做了詳細說明。

有兩個需要着重強調的點:

  1. 方法參數的作用域範圍: 形參的作用域範圍,是方法,構造函數或lambda表達式對應的body。
  2. for循環初始化語句中聲明的局部變量, 作用域範圍包括: 初始化部分、初始化後面的條件判斷部分,遞增更新語句,以及循環體中的語句。

通過我們的分析可知,正確選項爲: B, C, E.

相關鏈接

原文鏈接: https://blogs.oracle.com/javamagazine/quiz-yourself-variable-declaration-intermediate

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章