为什么Java没有块级作用域变量声明?

10

下面的方法无法正常工作,因为内部块声明了与外部块中相同名称的变量。显然,变量属于它们被声明的方法或类,而不是它们被声明的块,因此我无法编写一个短小的临时块进行调试,该临时块会将外部作用域中的变量暂时覆盖:

void methodName() {
  int i = 7;
  for (int j = 0; j < 10; j++) {
    int i = j * 2;
  }
}

我使用的几乎每种块级作用域语言都支持此功能,包括我在学校为解释器和编译器编写的一些简单的小语言。Perl、Scheme、甚至C都可以做到这一点。甚至PL/SQL也支持此功能!

那么Java为什么要做出这个设计决策呢?

编辑:正如有人指出的那样,Java确实具有块级作用域。那么我所问的概念名称是什么?我希望我能记得更多关于语言设计的课程。

6个回答

26

严格来说,Java确实有块级作用域的变量声明;因此,这是一个错误:

void methodName() {
  for (int j = 0; j < 10; j++) {
    int i = j * 2;
  }
  System.out.println(i); // error
}

'i'只存在于for循环块中,因此无法在该块外部访问。

问题在于Java不允许您创建与同一方法的外部块中声明的另一个变量名称相同的变量。正如其他人所说,这样做是为了防止难以识别的错误。


19
因为有些作者故意这样做,但他们会因忘记现在有两个同名变量而搞砸。他们会更改内部变量名称,但保留使用该变量的代码,这会无意中使用先前被屏蔽的变量。这会导致程序仍然可以编译,但执行时会出现错误。
同样地,不小心遮盖变量并改变程序的行为也是常见的。无意中屏蔽现有变量与我上面提到的取消屏蔽变量一样容易改变程序。
允许这种屏蔽带来的好处少得可怜,它们已经将其排除为过于危险。说真的,只需将新变量命名为其他名称,问题就解决了。

2
我认为更进一步说,根本没有任何理由这样做。使用影子变量无法做到没有它们做不到的事情。 - JavadocMD
2
我唯一喜欢在构造函数参数中使用它们的时候,这样我就可以做“this.foo = foo”。为了构造函数而进行名称混淆是丑陋的,非常难看。"foo","foo"之类的。 - Derek Park

14

我认为这样做的理由是,大多数情况下,这不是故意的,而是编程或逻辑缺陷。

在像你这样简单的例子中,很明显,但在大量代码中,意外重新声明变量可能不明显。

估计时间:它也可能与Java中的异常处理有关。我认为这个问题的一部分是在与try部分中声明的变量为什么在catch/finally范围内不可用的相关问题中讨论过了。


我最初在异常处理中看到了这个。当时我并没有想太多,只是认为实现内部可能排除了它的存在。直到我注意到它影响了普通块,我才真正开始思考“为什么”? - skiphoppy

10

我猜这会导致难以发现的错误,C#中也有类似的情况。

Pascal不支持这个功能,因为你必须在函数体上面声明变量。


1

这个问题的基本假设是错误的。

Java确实具有块级作用域。但它也有一种作用域层次结构,这就是为什么您可以在for循环内引用i,但不能在循环外部引用j的原因。

public void methodName() {
  int i = 7;
  for (int j = 0; j < 10; j++) {
    i = j * 2;
  }

  //this would cause a compilation error!
  j++;
}

我真的想不出为什么你会希望作用域以其他方式运行。在 for 循环内部确定你所引用的 i 是哪一个将是不可能的,而且我敢打赌99.999%的时间你希望引用方法内部的 i。

我确实想引用方法内部的i。这是C、Perl和Scheme的运作方式。那么为什么这会使确定我所需的i变得不可能呢? - skiphoppy

0
另一个原因:如果允许这种变量声明,人们将需要一种访问外部块变量的方法。可能会添加类似于“outer”关键字的东西。
void methodName() {
    int i = 7;
    for (int j = 0; j < 10; j++) {
        int i = outer.i * 2;
        if(i > 10) {
            int i = outer.outer.i * 2 + outer.i;
        }
    }
}

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