块级作用域变量

17

这将会编译

class X
{  
    public static void main(String args[])
    {
        {
            int a = 2;
        }
        {
            int a = 3;
        }       
    }
}

这不会

class X
{  
    public static void main(String args[])
    {

        int a = 2;

        {
            int a = 3;
        }       
    }
}

我本来期望两者都能编译通过(也许这就是C语言的工作方式?)。为什么在一个块中声明与外部块中变量同名的变量是不可能的呢?

5个回答

22
简短的回答是:这是Java语言JLS §6.4定义的方式。
您可能从其他语言中习惯于允许所谓的variable shadowing,但是Java语言的发明人认为这是一个不方便的特性,他们不想在自己的语言中使用它:

这个限制有助于检测一些非常晦涩的错误。

然而,在Java中,您会在JLS的同一部分中发现影子变量的存在:

对于局部变量覆盖成员变量的类似限制被认为是不切实际的,因为在超类中添加成员变量可能会导致子类必须重新命名局部变量。相关考虑也使得对嵌套类的成员变量或在嵌套类内声明的局部变量覆盖局部变量的覆盖受到限制变得不太吸引人。

这意味着实际上以下代码是合法的:
class A {
   int x = 0;
   void m() {
     int x = 10; // Shadows this.x
   }
}

正如作者所描述的那样,可以通过声明一个与实例变量同名的方法局部变量来遮蔽它,因为有可能在某一天扩展 A 的功能,如果遮蔽是非法的,那么你将无法编译类 B

class B extends A {
   void m() {
     int x = 10; // Shadows A.this.x if A declares x
   }
}

如果你考虑像C这样允许变量遮蔽的语言,你可能会发现一些笨拙的代码,例如:
int x;
int main() 
{
  {
    int x = 0;
    {
      extern int x;
      x = 1;
    }
    printf("%d\n", x); // prints 0
  }
  printf("%d\n", x); // prints 1
  return 0;
}

这个程序不太容易理解,可能由于变量屏蔽导致结果与您的预期不同。

关于在上面的类A定义中的 int x = 10; // Shadows A.this.x if A declares x,这是否意味着当我在A中打印 x,它仍将打印 10 或者是 0(因为它实际上打印了 0)? - agent47
不用在意上面的 ^^^,我明白了 shadowing 的含义。如果我没记错的话,它的意思是使用相同名称覆盖外部作用域变量。 - agent47

9

Java不允许在同一作用域内有两个同名变量。

对于您的第二种情况:

int a = 2;

{
  // the outer 'a' is still in scope
  int a = 3; // so this is a redeclare <-- nooo!
} 

然而,在第一个情况中,每个 a 都包含在自己的范围内,所以一切都很好。

1
注意语句“Java不允许在彼此范围内具有相同名称的两个变量。”这仅适用于局部变量、方法、构造函数和异常参数 - Sotirios Delimanolis
@SotiriosDelimanolis,你引用的链接如何支持你的观点? - flow2k
1
@flow2k 它并不会影响作用域,只是定义了本地变量和参数。作用域的定义可以在这里找到:http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.3。 - Sotirios Delimanolis

4

原因在于第二种情况下,a 已经在静态块中定义了,所以你试图重新声明它。编译器不允许这样做:

public static void main(String args[]) {
    {
       int a = 2; //a is known only here
    }             //a will be freed
    {
       int a = 3; //you can declare it again here
    }       
}

1
public static void main(String args[])
{
    int a = 2; // I know a
    // I know a
    {
        // I know a
        int a = 3; // There can be only one a!
    }       
}

在上面的例子中,你在方法main()中声明了a。从声明到方法结束,a都是已经声明的。在这种情况下,你不能在代码块中重新声明a
在下面的代码中,你在一个代码块中声明了a。它只能在该代码块内部使用。
public static void main(String args[])
{
    { 
        int a = 2; // I know a
        // I know a
    }
    // Who is a?
    {

        int a = 3; // I know a!
    }       
}

-4
在Java中,所有的局部变量都将存储在堆栈上。因此,如果您编写
class X
{  
public static void main(String args[])
{
   int a = 2;    // At this point var 'a' is stored on Stack
   {
       /*
       Now as the prev. 'main method is not yet complete so var 'a' is still present on the Stack. So at this point compiler will give error "a is already defined in main(java.lang.String[])" 
        */
       int a = 3;   

   }
 }
}

希望这能帮到你。
谢谢。

2
这是完全错误的。编译后的代码不知道本地变量的名称,它只知道索引。此外,这些变量不一定同时位于堆栈上。每个方法帧都有一个操作数堆栈和一个本地变量数组。因此,“在Java中,所有本地变量都将存储在堆栈上”并不准确。 - Rafael Winterhalter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接