为什么Try/Catch块会创建新的变量作用域?

61

例如:

try
{
    SomeObject someObject = new SomeObject();
    someObject.dangerousMethod();
}
catch(Exception e)
{
}
someObject.anotherMethod(); //can't access someObject!

但是你可以在try/catch块之前声明它,然后它就可以正常工作:

SomeObject someObject;
try
{
    someObject = new SomeObject();
    someObject.dangerousMethod();
}
catch(Exception e)
{
}
someObject.anotherMethod(); //works fine

我只是好奇这个设计的原因。为什么在try/catch块中创建的对象不在方法的作用域内?除了监视抛出的异常,也许我并没有深入理解try/catch的工作原理。

22
在Java中,每个代码块都定义了一个新的作用域,不仅仅是try代码块。如果不遵循这个通用规则,将非常不一致。 - JB Nizet
5个回答

61
在try/catch块内创建的对象与方法的其他部分具有相同的作用域。在try/catch块内声明的变量不在包含块的作用域内,原因是所有其他变量声明都局限于它们发生的作用域中:这就是规范所定义的方式。 :-)(下面还有更多内容,包括对您评论的回复。)以下是在try/catch中创建并在其外部可访问的对象示例:
SomeObject someObject = null;
try
{
    someObject = new SomeObject();
    someObject.dangerousMethod();
}
catch(Exception e)
{
}
someObject.anotherMethod(); // This is fine -- unless the SomeObject
                            // constructor threw the exception, in which
                            // case someObject will be null

注意区别。变量被声明的地方决定了它存在的范围,而不是对象被创建的地方。

但是根据上面的方法名称等,更有用的结构应该是:

SomeObject someObject = new SomeObject();
try
{
    someObject.dangerousMethod();
}
catch(Exception e)
{
}
someObject.anotherMethod();

关于您的评论:

我有点困惑为什么try / catch块甚至创建了另一个作用域。

在Java中,所有代码块都会创建新的作用域。例如if语句块、else语句块、while循环体等等,它们都会创建新的嵌套作用域:

if (foo) {
    SomeObject bar = new SomeObject();
}
bar.doSomething(); // <== Compilation error, `bar` is not defined
事实上,即使是没有任何控制结构的块也会创建一个作用域。如果你仔细想想,这是有道理的:有些块是有条件的,比如定义了if或while的主体的块。在上面的if中,根据foo的值,bar可能已经被声明了,也可能没有被声明,这是没有意义的,因为编译器当然没有运行时值的概念。因此,为了保持一致性,Java的设计人员选择让所有块都创建一个新的嵌套作用域。(JavaScript的设计者则采用了另一种方法——根本没有块作用域,尽管它正在被添加,这种方法也会让人感到困惑。)

1
抢我的台词了!这个作用域是为了防止异常被抛出(即使在声明对象的构造函数内部)。如果try语句从未到达该实例化语句,我们无法继续使用该对象。 - David B
1
我想我有点困惑,为什么在try/catch块中还需要创建另一个作用域。 - telkins
3
我认为他知道它是如何工作的;相反,他是在询问为什么要以那种方式做出决定。 - corsiKa
谢谢。我会接受这个作为最完整的答案,尽管corsiKa提供了一个不错的例子。 - telkins
@corsiKa,他的问题已经被David B回答了。 - Franklin Yu
显示剩余5条评论

9
在Java中,任何时候你遇到一对{},你都可以创建一个新的作用域。
考虑以下示例:
class ScopeTest {
    public static void main(String[] args) {
        int i = 0;
        { int j = 0; System.out.println(j); }
        { int j = 2; System.out.println(j); }
    }
}

try/catch遵循这种习惯用法,并强制创建一对{ }

为了回答您后续的非括号if语句,请考虑:

class MultiRTree {
    public static void main(String...args) {
        boolean b = args.length == 0;
        if(b) String s = new String("hello");
    }
}

造成
c:\files\j>javac ScopeTest.java
ScopeTest.java:4: not a statement
        if(b) String s = new String("hello");
              ^
ScopeTest.java:4: ';' expected
        if(b) String s = new String("hello");
                    ^
2 errors

然而,这将可以正常编译。
class ScopeTest {
    public static void main(String...args) {
        boolean b = args.length == 0;
        if(b) new String("hello");
    }
}

根据JLS第14章第9节,if被定义为“为了控制语句的执行,如果指定一个布尔表达式作为它的条件,则该布尔表达式必须首先进行求值。”
IfThenStatement:
    if ( Expression ) Statement

And Statement是指(14.5)。

Statement:
    StatementWithoutTrailingSubstatement
    LabeledStatement
    IfThenStatement
    IfThenElseStatement
    WhileStatement
    ForStatement

StatementWithoutTrailingSubstatement:
    Block
    EmptyStatement
    ExpressionStatement
    AssertStatement
    SwitchStatement
    DoStatement
    BreakStatement
    ContinueStatement
    ReturnStatement
    SynchronizedStatement
    ThrowStatement
    TryStatement

因此,块、表达式语句或空语句都是可以的。但是声明(在第6章中定义)不在语句的语法中。


如果你有一个if语句的单行代码,而你没有使用{ }对,那该怎么办呢?比如说if(true) SomeObject someObject = new SomeObject(),那么someObject会在方法的作用域内吗?还是编译器会为我插入隐含的大括号? - telkins
@Atlos - 它做了一些奇怪的事情。可以说,这是不允许的!我会进行编辑。 - corsiKa
1
基本上,new SomeObject(); 是允许的,因为它是一个语句,但 SomeObject so = new SomeObject() 不被允许,因为它是一个声明(即使它包含一个语句)。 - corsiKa

5
每次使用大括号 '{',无论是在 C++ 还是 Java 中,都会表示一个新的作用域。尝试操作需要一些内部设置和作用域名称,以便在不需要进行大量清理的情况下快速跳出 try 块。

有些语言允许您在作用域外访问这些作用域变量,只要没有名称冲突(如 Python 中),但这需要稍微不同的内部堆栈结构,并且仍可能增加 try catch 的成本。

此外,这只是 Java 中定义作用域的方式,就像其他答案中指出的那样。

4

变量或对象的作用域在它所定义的范围内(由花括号{}定义的范围)。

由于try catch会初始化一个新的作用域,可能会抛出一些错误,因此在try catch内部定义的对象在其作用域之外不可用。


2

try/catch 会创建一个新的作用域,因为它是一个块级元素。事实上,只要在方法中随意放置 {} 就可以创建一个具有自己本地作用域的代码块。


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