Groovy: "def x = 0"中的"def"有什么作用?

198
在下面这段代码中(摘自Groovy 语义手册页面),为什么要用 def 关键字来前缀赋值操作?
def x = 0
def y = 5

while ( y-- > 0 ) {
    println "" + x + " " + y
    x++
}

assert x == 5

def关键字可以被移除,该代码片段将产生相同的结果。那么def关键字的作用是什么?

6个回答

301

这是基本脚本的语法糖。省略 "def" 关键字会将变量放入当前脚本的绑定中,并且 Groovy 将其(大多数情况下)视为全局作用域变量:

x = 1
assert x == 1
assert this.binding.getVariable("x") == 1

使用def关键字而不是其他关键字,不会将变量放入脚本绑定中:

def y = 2

assert y == 2

try {
    this.binding.getVariable("y") 
} catch (groovy.lang.MissingPropertyException e) {
    println "error caught"
} 

输出:"捕获错误"

在更大的程序中使用def关键字很重要,因为它有助于定义变量可以被找到的范围,可以帮助保护封装。

如果您在脚本中定义了一个方法,它将无法访问在主脚本体中用"def"创建的变量,因为它们不在范围内:

 x = 1
 def y = 2


public bar() {
    assert x == 1

    try {
        assert y == 2
    } catch (groovy.lang.MissingPropertyException e) {
        println "error caught"
    }
}

bar()

打印“捕获到错误”

函数内没有“y”变量的范围。 “x”在范围内,因为Groovy将检查当前脚本的绑定变量。正如我之前所说,这只是一种语法糖,可以更快地键入快速且不太规范的脚本(通常是一行代码)。

在较大的脚本中的良好实践是始终使用“def”关键字,以避免遇到奇怪的作用域问题或干扰您不想要的变量。


41

Ted的回答很适合用于脚本; Ben的回答是类的标准。

正如Ben所说,将其视为“Object”——但更酷的是它不会限制您使用Object的方法。这对于导入具有独特的意义。

例如,在此片段中,我必须导入FileChannel:

// Groovy imports java.io.* and java.util.* automatically
// but not java.nio.*

import java.nio.channels.*

class Foo {
    public void bar() {
        FileChannel channel = new FileInputStream('Test.groovy').getChannel()
        println channel.toString()
    }
}

new Foo().bar()

例如,只要所有内容都在类路径上,我就可以随意处理它。

// Groovy imports java.io.* and java.util.* automatically
// but not java.nio.*
class Foo {
    public void bar() {
        def channel = new FileInputStream('Test.groovy').getChannel()
        println channel.toString()
    }
}

new Foo().bar()

1
为什么您可以在没有导入的情况下使用 new FileInputStream('Test.groovy').getChannel() - Alexander Suraphel
4
只要所有东西都在类路径上,就可以了。 - Hanno

33
根据这个页面def是类型名称的替代品,可以简单地视为Object的别名(即表示您不关心类型)。

14

就这个单独的脚本而言,实际上没有什么区别。

然而,使用关键字“def”定义的变量被视为局部变量,即仅限于此脚本。没有在它们前面加上“def”的变量在第一次使用时存储在所谓的绑定中。您可以将绑定视为变量和闭包的通用存储区,这些变量和闭包需要在“脚本之间”可用。

因此,如果您有两个脚本并使用相同的GroovyShell执行它们,则第二个脚本将能够获取在第一个脚本中设置的所有没有“def”的变量。


9
"def" 的作用是告诉 Groovy 您打算在此处创建一个变量。这很重要,因为您绝不希望意外创建变量。在脚本中(Groovy 脚本和 groovysh 允许您这样做),这有些可以接受,但在生产代码中,这是您可能遇到的最大问题之一,这就是为什么您必须在所有实际 Groovy 代码(任何类内部)中使用 "def" 定义变量的原因。

以下是一个反例。如果您将以下代码复制并粘贴到 groovysh 中,则会运行此代码(而不会失败 assert):
bill = 7
bi1l = bill + 3
assert bill == 7

这种问题可能需要花费很长时间来查找和修复,即使它只在你的生涯中出现过一次,也比明确声明变量数千次花费更多的时间。而且,它还清晰地显示在哪里被声明,你不必猜测。
在不重要的脚本/控制台输入(如Groovy控制台)中,这在某种程度上是可以接受的,因为脚本的范围受到限制。我认为Groovy允许在脚本中这样做的唯一原因是支持DSL,就像Ruby一样(如果你问我的话,这是一个糟糕的权衡,但有些人喜欢DSL)。

5

实际上,我认为它不会表现得一样...

Groovy中的变量仍然需要声明,只是不需要类型声明,因为右侧通常包含足够的信息供Groovy对变量进行类型推断。

当我尝试使用未使用def或类型声明的变量时,会出现“无此属性”的错误,因为它假定我正在使用包含代码的类的成员。


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