Groovy 基础语法


Groovy 基础语法

翻译自官方文档:Syntax
部分内容为了方便表述做了修改。

1.注释

1.1.单行注释

单行注释以//开头,可以放在行中的任意位置。从//开始知道行尾的内容都算作是注释的部分。

// 独立的单行注释
println "hello" //直到行尾的注释

1.2.多行注释

多行注释以/*开头,可以放在该行的任何位置。 /*后面的字符将被视为注释的一部分,包括换行符,直到第一个*/为止结束注释。 因此,多行注释可以放在语句的末尾,甚至可以放在语句的内部。

/* 一个独立的多行注释
    可以跨越两行*/
println "hello" /* 一个多行注释
                    从语句的结尾开始*/
println 1 /* one */ + 2 /* two */

1.3.GroovyDoc注释

与多行注释类似,GroovyDoc注释也是多行注释,但以/**开头,以*/结尾。第一行之后的注释行可以选择以*开头,,这些注释与以下内容相关:
- 类型定义(类,接口,枚举,注解)
- 变量和属性定义
- 方法定义

/**
* 类描述
*/
class Person{
    /** 名称*/
    String name

    /**
     * 创建一个greet方法
     * @param otherPerson the person to greet
     * @return a greeting message
     */
    String greet(String otherPerson){
        "Hello ${otherPerson}"
    }
}

Groovydoc遵循与Java的Javadoc相同的规约。因此,可以使用与Javadoc相同的标签。

另外,Groovy自3.0.0起就支持运行时Groovydoc,即GroovyDoc可以在运行时保留。默认情况下,GroovyDoc是禁用的,需要打开可以使用JVM选项设置-Dgroovy.attach.runtime.groovydoc=true来启用。

运行时GroovyDoc以/**@开头,以*/结尾,例如:

/**@
 * Foo类描述
 */
class Foo{

    /**@
     * bar方法描述
     */
    void bar(){
        println "Hello"
    }
}
println Foo.class.groovydoc.content.contains('Some class groovydoc for Foo') //获取类的运行时GroovyDoc
println Foo.class.getMethod('bar', new Class[0]).groovydoc.content.contains('Some method groovydoc for bar') // 获取方法的运行时GroovyDoc

1.4.Shebang line

除了单行注释,还有一个特殊行注释,通常在Unix系统下被称为shebang,它允许直接运行脚本只要已经安装了Groovy发行版并且在当前路径下可以使用Groovy命令。

#!/usr/bin/env groovy
println "Hello from the shebang line"

#字符必须是文件的第一个字符,并且任何缩进都可能导致编译的失败

2.关键字

as assert break case
catch class const continue
def default do else
enum extends false Finally
for goto if implements
import in instanceof interface
new pull package return
super switch this throw
throws trait true try
while

3. 标识符

3.1.普通标识符

标识符以字母,$或者下划线开头,不能以数字开头。
字母的可用范围是:

  • ‘a’-‘z’(小写ASCII字母)

  • ‘A’-‘Z’(大写ASCII字母)

  • ‘\u00D8’ 到”\u00F6”

  • ‘\u00F8’ 到”\u00FF”

  • ‘\u0100’ 到”\uFFFE”

后续的字符可以包含字母和数字

下面是几个有效标识符的示例(此处是变量名称):

def name
def item3
def with_underscore
def $dollarStart

以下是无效的标识符:

def 3tier
def a+b
def a#b

所有的关键字也可以是有效的标识符,只要在.之后:

foo.as
foo.assert
foo.break
foo.case
foo.catch

3.2.引用标识符

引用标识符在在.符号之后,例如person.name表达式的name部分可以用person."name"或者person.'name'来引用。这就使得可能会出现某些标识符包含了Java规范中禁止的非法字符,但在Groovy中确实允许使用的。例如破折号,空格,感叹号等字符。

def map = [:]
map."an identifier with a space and double quotes" = "ALLOWED"
map.'with-dash-signs-and-single-quotes' = "ALLOWED"

assert map."an identifier with a space and double quotes" == "ALLOWED"
assert map.'with-dash-signs-and-single-quotes' == "ALLOWED"

println map."an identifier with a space and double quotes"
println map.'with-dash-signs-and-single-quotes'

正如在后续的字符串章节看到的,Groovy提供了不同的字符串文本,所有类型的字符串都允许在.之后调用:

map.'single quote'
map."double quote"
map.'''triple single quote'''
map."""triple double quote"""
map./slashy string/
map.$/dollar slashy string/$

纯字符字符串和Groovy的GStrings(内插字符串)是有区别的,因为在后者的情况下,内插值被插入得到最后的完整标识符。

def firstname = "Homer"
map."Simpson-${firstname}" = "Homer Simpson"

assert map.'Simpson-Homer' == "Homer Simpson"

4.Strings

字符串以字符链的形式表示。Groovy允许示例化java.lang.String对象,和GStrings(groovy.lang.GString),GStrings在其他变成语言中也被称为插值字符串。

4.1.单引号字符串

单引号字符串是一系列字符由单引号包裹:

def s = 'a single-quoted string'

单引号字符串是普通的java.lang.String,不支持插值

4.2.字符串连接

所有的Groovy字符串都可以用+操作符连接

assert 'ab' == 'a' + 'b'

4.3.三单引号字符串

三单引号字符串是一系列字符由三重单引号包裹:

'''a triple-single-quoted string'''

三单引号字符串是普通的java.lang.String,也不支持插值。

三单引号字符串可以跨越多行,字符串内容可以跨越行界而不需要把字符串分割成几段也不需要使用连接符或换行符

def aMultilineString = '''line one
line two
line three'''

如果你的代码是缩进的,例如在一个类的方法的主体中,你的字符串将包含缩进的空格。Groovy开发包中提供了String#stripIndent()方法和String#stripMargin()方法来剥离缩进的方法,该方法需要一个标识符来确定要从字符串的开头删除的文本。

当如下创建一个字符串时:

def startingAndEndingWithANewline = '''
line one
line two
line three
'''

println startingAndEndingWithANewline

运行这段代码输出如下:

(这里有一个空行)
line one
line two
line three

可以发现生成的字符串包含一个换行符作为第一个字符,打印字符串的ASCII码如下:

[10, 108, 105, 110, 101, 32, 111, 110, 101, 10, 108, 105, 110, 101, 32, 116, 119, 111, 10, 108, 105, 110, 101, 32, 116, 104, 114, 101, 101, 10]

可以看到第一个字符是10,也就是换行符
使用反斜杠\转义可以去除该字符

def strippedFirstNewline = '''\
line one
line two
line three
'''
println strippedFirstNewline
assert !strippedFirstNewline.startsWith('\n')

输出为:

line one
line two
line three

打印字符串ascii码为:

[108, 105, 110, 101, 32, 111, 110, 101, 10, 108, 105, 110, 101, 32, 116, 119, 111, 10, 108, 105, 110, 101, 32, 116, 104, 114, 101, 101, 10]

assert的结果通过

4.3.1转义特殊字符

你可以使用反斜杠\转义单个引号,以避免终止字符串文本:
'an escaped single quote: \' needs a backslash'

使用双反斜杠\\来转义转义字符本身
'an escaped escape character: \\ needs a double backslash'

一些特殊字符还使用反斜杠作为转义字符:

转义序列 字符
\b 退格
\f 进纸符
\n 换行
\r 回车
\s 空格
\t 制表
\\ 反斜杠
\' 单引号字符串中的单引号(三单引号和双引号字符串可选)
\" 双引号字符串中的双引号(三双引号和单引号字符串可选)

这里在自己测试时在IDEA(2020.1.3), Groovy(3.0.4)环境下会出现编辑器报错的情况:
s
但是能正常运行输出结果。

4.3.2Unicode转义序列

对于不存在与键盘的字符,可以使用Unicode转义:反斜杠\,后面跟上u,然后是4个16进制数字。
例如,欧元货币符号可以使用以下方式表示:
'The Euro currency symbol: \u20AC'

4.4双引号字符串

双引号字符串是一系列字符由双引号包裹:
"a double-quoted string"

如果没有插值表达式,双引号字符串即为普通java.lang.String,如果存在插值则为groovy.lang.GString实例
如果要转义双引号,可以使用反斜杠字符:"A double quote:\""

4.4.1字符串插值

除了单引号和三引号字符串外,任何Groovy表达式都可以在所有字符串字面值之中进行插值。插值是指在字符串求值时用真实值替换字符串中占位符的行为。
占位符表达式由${}包裹。对于点式调用,可以省略大括号只是用$符。如果一个GString被传递给一个接收字符串的方法,那么占位符中的表达式将被计算为字符串表达式(通过调用该表达式的toString()方法),并将最终字符串传递给该方法。

def name = ‘’

def name = 'Guillaume' // 一个显示的字符串
def greeting = "Hello ${name}"

assert greeting.toString() == 'Hello Guillaume'

//使用算术表达式
def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5'


//在点式调用前使用$
def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'

${}之间允许使用表达式也允许使用语句。但是一个语句的值是null的,因此,如果在占位符中插入了几个语句的话,最后一个语句应该要以某种方式返回要插入的有意义的值。例如:
"The sum of 1 and 2 is equal to ${def a = 1; def b = 2; a + b}"
也是允许的,并且可以按照预期返回值。
不过更好的做法是在占位符中插入一个更简单的表达式。

要注意的是只有a.ba.b.c等形式的点式表达式才有效,包含括号之类的表达式是无效的,例如方法调用,用于闭包的大括号,不属于属性表达式或算数表达式的点式调用。

def number = 3.14
println "$number.toString()"

上面的例子将会引发一个groovy.lang.MissingPropertyException异常,因为Groovy认为你正在尝试访问该数字的一个不存在的toString属性。
你可以把插值中的$number.toString()看作是${number.toString}()
相似的,如果一个表达式的含义是模糊的,就需要用大括号包裹:

String thing = 'treasure'
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
        "The x-coordinate of the $thing is represented by $thing.x"   // <= Not allowed: ambiguous!!
assert 'The x-coordinate of the treasure is represented by treasure.x' ==
        "The x-coordinate of the $thing is represented by ${thing}.x"  // <= Curly braces required

如果需要转义一个GString对象中的$${}符号,只需要使用反斜杠\完成转义。

assert '$5' == "\$5"
assert '${name}' == "\${name}"

4.4.2插值闭合表达式的特殊情况

目前为止,可以看到占位符中可以插入任意表达式,但是闭包表达式有一个特殊情况和表示法。当占位符中包含一个箭头,${->},这个表达式实际上是一个闭包表达式,你可以将其视为一个闭包,并在其前面加上一个$

def sParameterLessClosure = "1 + 2 == ${-> 3}" //无参闭包
assert sParameterLessClosure == '1 + 2 == 3'

def sOneParamClosure = "1 + 2 == ${ w -> w << 3}" //传递了一个 java.io.StringWriter类型参数,并用<<方式添加内容
assert sOneParamClosure == '1 + 2 == 3'

//以上两种方式都是嵌入式闭包

正如上面所看到的,这好像是更啰嗦的方式去定义一个表达式,但闭包比单纯的表达式有一个优势:惰性求值
让我们考虑以下的示例:

def number = 1 //我们定义一个包含1的数字变量,然后在两个GString中插值,作为eagerGString中的表达式和lazyGString中的闭包。
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"

assert eagerGString == "value == 1"
assert lazyGString ==  "value == 1"
//eagerGString和lazyGString中都得到了期望的值

number = 2 //然后修改number的值
assert eagerGString == "value == 1" //使用普通的内插表达式,该值实际上是在创建GString时绑定的。
assert lazyGString ==  "value == 2" //但是使用闭包表达式时,每次将GString强制转换为String时都会调用闭包,从而生成包含新数字值的新字符串。

另外要注意的是有多个参数的闭包会在运行时抛出异常。插值只允许零个或一个参数的闭包。

4.4.3与JAVA的互操作性

当一个方法(无论是否实现了JAVA或者Groovy)需要一个java.lang.String对象是,但是传递了一个groovy.lang.GString对象,那么GString的toString()方法会被自动隐式地调用。

String takeString(String message) { //方法签名明确表示其唯一参数是字符串
    assert message instanceof String //并验证字符串类型为java.lang.String
    return message
}

def message = "The message is ${'hello'}" //创建一个字符串,类型为groovy.lang.GString
assert message instanceof GString         //验证字符串类型为groovy.lang.GString
def result = takeString(message)          //将GString传递给takeString方法
assert result instanceof String
assert result == 'The message is hello'

代码运行通过,可见groovy确实会自动地把GString转换为String。

4.4.4.GString和String的哈希值

尽管插值字符串可以替代普通的Java字符串,但是他们与字符串在哈希代码上是不同的。Java字符串是不可改变的,而GString的结果会因为内插值的不同而变化。其实是相同的字符串,GString和String的哈希值也可能不同。

assert "one: ${1}".hashCode() != "one: 1".hashCode()

GString和String具有不同的哈希值,因此应该避免使用GString作为Map的key值,特别是尝试用Stirng而不是GString来检索关键值时。

def key = "a"
def m = ["${key}": "letter ${key}"] //这里的Map用GString作为key

assert m["a"] == null  //当以普通的String作为key获取对应的value值时,由于GString和String有不用的哈希值,导致找不到对应的value

4.5.三双引号字符串

三双引号字符串和双引号字符串相似,只是他们是多行的。

def name = 'Groovy'
def template = """
    Dear Mr ${name},

    You're the winner of the lottery!

    Yours sincerly,

    Dave
"""

无论是双引号还是单引号都不需要在三双引号字符串中转义。

4.6.斜杠字符串

除了普通的引号字符串外,Groovy还提供了斜杠字符串。斜杠字符串使用/作为字符串的开头和结束标记。斜杠字符串对于定义正则表达式和Patterns是有利工具,而且也不需要转义反斜杠\
斜杠字符串的示例如下:

def fooPattern = /.*foo.*/
assert fooPattern == '.*foo.*'

只有斜杠/本身需要被反斜杠\转义,因为斜杠字符串是以斜杠为开始和结束的标记的。

def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash'

斜杠字符串也可以是多行的:

def multilineSlashy = /one
    two
    three/

assert multilineSlashy.contains('\n')

另外斜杠字符串可以看作是GString的另一种形式,所以也是可以被插值的,只是他们的转移规则不同。

def color = 'blue'
def interpolatedSlashy = /a ${color} car/

assert interpolatedSlashy == 'a blue car'

4.6.1特殊情况

一个空的斜杠字符串不允许以两个斜杠(//)的形式出现,因为这会被Groovy解析为行注释。所以下面这个断言语句不会被编译,因为它看起来是一个未结束的语句。

assert '' == //

由于斜杠字符串主要是为了简化正则表达式使用而设计,所以在GString中不允许的错误写法例如$()$5可以在斜杠字符串中使用。

要记住,在斜杠字符串中使不需要转义反斜杠的。换个思路是实际上不支持转义。一个斜杠字符串/\t/实际上不会包含一个制表符,而是\t字面值。转义只对斜杠/本身有效。例如:/\/folder/会被处理成/folder。斜杠字符串的转义结果不能使斜杠字符串以反斜杠结尾。否则,将转义斜杠字符串的结束符。可以使用一个替代方案:/ends with slash ${'\'}/,但是最好的还是避免在这种情况下使用斜杠字符串。

这一段有点绕,反正意思就是使用斜杠字符串时,最后一个字符不要是反斜杠。

4.7.Dollar斜杠字符串

Dollar斜杠字符串是一个多行的GString,以$/开头,以/$结尾。转义字符是$,它可以转义另一个$符号,或者斜杠/。但是Dollar斜杠字符串并不需要转义$或者/。除非是GString占位符子序列开头中的$或者是Dollar斜杠字符串的结束符/$

def name = "Guillaume"
def date = "April, 1st"

def dollarSlashy = $/
    Hello $name,
    today we're ${date}.

    $ dollar sign
    $$ escaped dollar sign
    \ backslash
    / forward slash
    $/ escaped forward slash
    $$$/ escaped opening dollar slashy
    $/$$ escaped closing dollar slashy
/$

assert [
        'Guillaume',
        'April, 1st',
        '$ dollar sign',
        '$ escaped dollar sign',
        '\\ backslash',
        '/ forward slash',
        '/ escaped forward slash',
        '$/ escaped opening dollar slashy',
        '/$ escaped closing dollar slashy'
].every { dollarSlashy.contains(it) }
println dollarSlashy

Dollar斜杠字符串是为了克服斜杆字符串的转移规则而产生的,当你的字符串内容适合Dollar斜杠字符串转义规则时才使用Dollar斜杠字符串。例如:有一些不想要转义的斜杠时。

4.8.字符串小结表

String name String syntax Interpolated Multiline Escape character
Single-quoted '…' \
Triple-single-quoted '''…''' \
Double-quoted "…" \
Triple-double-quoted """…""" \
Slashy /…/ \
Dollar slashy $/…/$ $

4.9.字符

与Java不同,Groovy没有明确的字符文字,但是有三种方式明确地把Groovy字符变成变成一个实际的字符。

char c1 = 'A' //通过显式声明char类型
assert c1 instanceof Character

def c2 = 'B' as char //通过as运算符类型转换
assert c2 instanceof Character

def c3 = (char)'C' //通过强制类型转换
assert c3 instanceof Character

5.Numbers

Groovy支持多种类型的整型数字和十进制文本,支持Java的常用数字类型。

5.1.整型

整型和Java中相同

  • byte

  • char

  • short

  • int

  • long

  • java.lang.BigInteger

可以用以下的方式创建上述的整型:

// 基本数据类型
byte  b = 1
char  c = 2
short s = 3
int   i = 4
long  l = 5

// infinite precision
BigInteger bi =  6

如果使用def关键字声明变量,那么这些数字会自动选择可以容纳的类型

对于正数:

def a = 1
assert a instanceof Integer

// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer

// Integer.MAX_VALUE + 1
def c = 2147483648
assert c instanceof Long

// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long

// Long.MAX_VALUE + 1
def e = 9223372036854775808
assert e instanceof BigInteger

负数也一样:

def na = -1
assert na instanceof Integer

// Integer.MIN_VALUE
def nb = -2147483648
assert nb instanceof Integer

// Integer.MIN_VALUE - 1
def nc = -2147483649
assert nc instanceof Long

// Long.MIN_VALUE
def nd = -9223372036854775808
assert nd instanceof Long

// Long.MIN_VALUE - 1
def ne = -9223372036854775809
assert ne instanceof BigInteger

5.1.1.非10进制表示

数字也可以用二进制,八进制,十六进制和十进制表示。

$\color{orange}{二进制文本}$

二进制数字以0b前缀开头:

int xInt = 0b10101111
assert xInt == 175

short xShort = 0b11001001
assert xShort == 201 as short

byte xByte = 0b11
assert xByte == 3 as byte

long xLong = 0b101101101101
assert xLong == 2925l

BigInteger xBigInteger = 0b111100100001
assert xBigInteger == 3873g

int xNegativeInt = -0b10101111
assert xNegativeInt == -175

$\color{orange}{八进制文本}$

八进制数字以0开头:

int xInt = 077
assert xInt == 63

short xShort = 011
assert xShort == 9 as short

byte xByte = 032
assert xByte == 26 as byte

long xLong = 0246
assert xLong == 166l

BigInteger xBigInteger = 01111
assert xBigInteger == 585g

int xNegativeInt = -077
assert xNegativeInt == -63

$\color{orange}{十六进制文本}$

十六进制文本以0x开头:

int xInt = 0x77
assert xInt == 119

short xShort = 0xaa
assert xShort == 170 as short

byte xByte = 0x3a
assert xByte == 58 as byte

long xLong = 0xffff
assert xLong == 65535l

BigInteger xBigInteger = 0xaaaa
assert xBigInteger == 43690g

Double xDouble = new Double('0x1.0p0')
assert xDouble == 1.0d

int xNegativeInt = -0x77
assert xNegativeInt == -119

5.2.十进制文本

十进制文本类型和Java中的类型相同:

  • float

  • double

  • java.lang.BigDecimal

可以使用以下声明创建这些类型的十进制数:

// primitive types
float  f = 1.234
double d = 2.345

// infinite precision
BigDecimal bd =  3.456

十进制可以使用指数e与指数字母E,后跟一个可选符号(+,-),以及一个表示指数的值:

assert 1e3  ==  1000.0
assert 2E4  ==  20000.0
assert 3e+1 ==  30.0
assert 4E-2 ==  0.04
assert 5e-1 ==  0.5

对于十进制的浮点数,Groovy选择java.lang.BigDecimal作为十进制浮点数默认类型。此外,支持floatdouble,但需要显式的类型声明,类型强转或后缀。尽管java.lang.BigDecimal是默认的十进制数字的类型,但是同样支持方法或闭包接收一个floatdouble类型的值作为参数。

例如:

def plusOne(BigDecimal decimal){
    println decimal+1
}

plusOne(1.0F)

5.3.下划线数字

当有些数字字面长度较长时,可以在文本中使用下划线对数字分组,可以方便方便识别数字:

long creditCardNumber = 1234_5678_9012_3456L
long socialSecurityNumbers = 999_99_9999L
double monetaryAmount = 12_345_132.12
long hexBytes = 0xFF_EC_DE_5E
long hexWords = 0xFFEC_DE5E
long maxLong = 0x7fff_ffff_ffff_ffffL
long alsoMaxLong = 9_223_372_036_854_775_807L
long bytes = 0b11010010_01101001_10010100_10010010

5.4.数字类型后缀

类型 后缀
BigInteger G or g
Long L or l
Integer I or i
BigDecimal G or g
Double D or d
Float F or f

例如:

assert 42I == new Integer('42')
assert 42i == new Integer('42') // lowercase i more readable
assert 123L == new Long("123") // uppercase L more readable
assert 2147483648 == new Long('2147483648') // Long type used, value too large for an Integer
assert 456G == new BigInteger('456')
assert 456g == new BigInteger('456')
assert 123.45 == new BigDecimal('123.45') // default BigDecimal type used
assert 1.200065D == new Double('1.200065')
assert 1.234F == new Float('1.234')
assert 1.23E23D == new Double('1.23E23')
assert 0b1111L.class == Long // binary
assert 0xFFi.class == Integer // hexadecimal
assert 034G.class == BigInteger // octal

5.5.数学运算

尽管操作符后续才讲到,但先讨论数学运算的结果也很重要。
除法和幂二元运算除外。

  • byte,char,short和,int之间的二元计算结果为int

  • longbyte,char,short,int之间的二元计算结果为long

  • BigInteger和任何其他整数类型的二元计算结果都为BigInteger

  • BigDecimalbyte,char,short,int,BigInteger的二元运算结果都为BigDecimal

  • float,double,BigDecimal之间的二元运算结果都是double

  • BigDecimalBigDecimal之间的运算结果事BigDecimal

下面这个表格总结了规律:

byte char short int long BigInteger float double BigDecimal
byte int int int int long BigInteger double double BigDecimal
char int int int long BigInteger double double BigDecimal
short int int long BigInteger double double BigDecimal
int int long BigInteger double double BigDecimal
long long BigInteger double double BigDecimal
BigInteger BigInteger double double BigDecimal
float double double double
double double double
BigDecimal BigDecimal

得益于Groovy的操作符重载,普通的算术运算符也可以和BigInteger,BigDecimal一起使用给,不同于在Java中必须使用特定的方法进行运算。

5.5.1.除法运算符

如果两个运算数中任意一个为浮点数或双精度数,则除法运算符//=的结果将是一个double结果,否则将得到BigDecimal(两个运算数都是整型的short, char, byte, int, long, BigIntegerBigDecimal)。

如果是带精度的除法(即产生的可以相同高精度和小数位数范围内表示的结果),则使用divide()方法执行BigDecimal的除法,或者使用精度为两个操作数的最大精度加上额外精度10的MathContext
对于整数除法和Java相同,应该使用intdiv()方法,因为Groovy没有提供一个整数操作符。
(这一段不是很明白)

5.5.2.幂运算符

幂运算操作符使用**操作符,有两个参数:基数和指数。幂运算的结果取决于它的操作数以及操作的结果(特别是结果可以被表示为一个整数值)。

以下这些原则被用于决定Groovy幂运算操作结果的类型:

  • 如果指数是十进制

    • 如果结果可以表示为一个Integer,则返回Integer

    • 如果结果可以表示为一个Long,则返回Long

    • 否则返回一个Double

  • 如果指数是一个整数值

    • 如果指数是一个负数,则返回一个IntegerLong, Double如果结果得值匹配这些类型的话

    • 如果指数是一个整数或0

      • 如果底数是BigDecimal,则返回一个BigDecimal结果

      • 如果底数是BigInteger,则返回一个BigInteger结果

      • 如果底数是Integer,则返回一个Integer如果结果匹配该类型,否则返回一个BigInteger

      • 如果底数是Long,则返回一个Long如果结果匹配该类型,否则返回一个BigInteger

我们可以通过一些示例来说明这些规则:

// 底数和指数都是整型,结果可以被Integer容纳
assert    2    **   3    instanceof Integer    //  8
assert   10    **   9    instanceof Integer    //  1_000_000_000

// 底数是一个Long, 结果也也是一个Long
// 虽然这个结果integer就可以容纳
assert    5L   **   2    instanceof Long       //  25

// 结果不能被Integer或Long容纳,因此返回了BigInteger
assert  100    **  10    instanceof BigInteger //  10e20
assert 1234    ** 123    instanceof BigInteger //  170515806212727042875...

// 底数是BigDecimal并且指数是负整型,结果可以容纳在Integer中
assert    0.5  **  -2    instanceof Integer    //  4

// 底数是int并且指数是负的浮点数,结果可以容纳在Integer中
assert    1    **  -0.3f instanceof Integer    //  1

// 底数是int,指数是负的int
// 但是结果将被计算为Double
// (底数和指数都将被转换为double)
assert   10    **  -1    instanceof Double     //  0.1

// 底数是BigDecimal,指数是int,所以结果是BigDecimal
assert    1.2  **  10    instanceof BigDecimal //  6.1917364224

// 底数是float或double指数是int
// 结果可以容纳在Double中
assert    3.4f **   5    instanceof Double     //  454.35430372146965
assert    5.6d **   2    instanceof Double     //  31.359999999999996

// 指数是一个浮点数
// 结果可以被容纳在Double中
assert    7.8  **   1.9  instanceof Double     //  49.542708423868476
assert    2    **   0.1f instanceof Double     //  1.0717734636432956

6.Booleans

Boolean是一种用于表示真值(truefalse)的特殊的数据类型。使用这个数据类型用于标记真假条件。

Boolean可以保存在变量中,并分配给字段,就像其他任意数据类型。

def myBooleanVariable = true
boolean untypedBooleanVar = false
booleanField = true

truefalse是仅有的两个原始boolean值。但是使用逻辑运算符可以表示更复杂的Boolean表达式。

另外,Groovy具有特殊的规则,通常被称为Groovy Truth,用于将非Boolean值强制转为Boolean值。

7.列表

Groovy使用一个用方括号包裹的以逗号为分隔的值作为列表。Groovy列表是JDK的java.util.List,因为Groovy没有定义一个自己的集合类。在定义列表是默认的实现是java.util.ArrayList,除非另行指定,正如后面将会看到。

def numbers = [1, 2, 3] // 定义一个数字列表,以逗号分隔包裹在方括号中,赋值给变量

assert numbers instanceof List // 这个列表是Java的`java.util.list`的接口
assert numbers.size() == 3 // 列表的大小可以调用`size()`方法得到

上面的例子中,我们定义了一个同类元素的列表,但是同样也可以创建一个非同类元素的列表:

def heterogeneous = [1, "a", true] // 列表包含了一个数字,一个字符串以及一个Boolean值

上面提到过在默认情况下列表实际上是java.util.ArrayList的实例,但是如果使用as关键字类型强转或使用显式的类型声明,列表也可以是其他的备选类型。

def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList

def linkedList = [2, 3, 4] as LinkedList // 使用as显式地转化为java.util.LinkedList实现
assert linkedList instanceof java.util.LinkedList

LinkedList otherLinked = [3, 4, 5] // 也可以显式地声明列表的类型是java.util.LinkedList
assert otherLinked instanceof java.util.LinkedList

你可以访问列表的元素使用[]下标运算符(可用于取值和赋值),和正数索引或负数索引(从列表的末尾开始访问)。使用range也是一样。并且可以使用<<运算符追加元素到列表:

def letters = ['a', 'b', 'c', 'd']

assert letters[0] == 'a' // 访问列表的第一个元素,起始索引为0
assert letters[1] == 'b'

assert letters[-1] == 'd' // 用-1索引访问列表的最后一个元素,-1表示从列表的末尾开始第一个元素
assert letters[-2] == 'c'

letters[2] = 'C' // 给列表的第三个元素赋新值
assert letters[2] == 'C'

letters << 'e' // 使用<<符号追加一个元素到列表的末尾
assert letters[ 4] == 'e'
assert letters[-1] == 'e'

assert letters[1, 3] == ['b', 'd'] // 一次性访问两个元素,返回一个新的列表包含这两个元素
assert letters[2..4] == ['C', 'd', 'e'] // 使用一个range访问范围内的值,从范围的开始到结束

由于列表本质上是异构的,所以列表同样可以包含其他列表以创建一个多维列表

def multi = [[0, 1], [2, 3]] // 定义一个二维数字列表
assert multi[1][0] == 2 // 访问外部列表的第二项中的第一项的值

8.数组

Groovy复用了列表的表示法用来表示数组,但是需要显式地声明数组类型或强转为数组类型。

String[] arrStr = ['Ananas', 'Banana', 'Kiwi'] // 显式地定义一个字符串数组

assert arrStr instanceof String[] //断言确实创建了一个字符串数组
assert !(arrStr instanceof List)

def numArr = [1, 2, 3] as int[] // 用as操作符创建了一个int数组

assert numArr instanceof int[]
assert numArr.size() == 3

同样可以创建多维数组:

def matrix3 = new Integer[3][3] // 可以定义数组的大小
assert matrix3.size() == 3

Integer[][] matrix2 //或者定义一个不指定大小的数组
matrix2 = [[1, 2], [3, 4]]
assert matrix2 instanceof Integer[][]

访问数组的元素使用和列表相同的符号:

String[] names = ['Cédric', 'Guillaume', 'Jochen', 'Paul']
assert names[0] == 'Cédric' // 访问数组的第一个元素

names[2] = 'Blackdrag'
assert names[2] == 'Blackdrag' // 为数组的第三个元素重新赋值

Groovy不支持Java的数组初始化符号,因为可能和Groovy的闭包大括号混淆。

8.1Java风格的数组初始化

Groovy始终支持使用方括号定义数组或里列表,并避免使用Java风格的大括号定义数组,以避免和闭包的混淆。但是如果大括号紧跟在数组的类型定义之后,这样不会导致和闭包的混淆,这种情况下是支持Java风格的数组定义方式的。

def primes = new int[] {2, 3, 5, 7, 11}
assert primes.size() == 5 && primes.sum() == 28
assert primes.class.name == '[I'

def pets = new String[] {'cat', 'dog'} // 大括号跟在类型定义之后
assert pets.size() == 2 && pets.sum() == 'catdog'
assert pets.class.name == '[Ljava.lang.String;'

// traditional Groovy alternative still supported
String[] groovyBooks = [ 'Groovy in Action', 'Making Java Groovy' ]
assert groovyBooks.every{ it.contains('Groovy') }

9.Maps

Groovy Map在其他语言中可能被称为字典或关联数组。Map将键和值关联起来,用冒号分隔键和值,每个键值对用逗号分隔,所有的键值对用方括号包裹。

def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF'] // 定义一个Map,键为颜色字符串,值为对应的16进制html颜色

assert colors['red'] == '#FF0000' // 使用下标符号访问red键对应的内容
assert colors.green  == '#00FF00' // 使用属性符号来访问green键对应的值

colors['pink'] = '#FF00FF' // 相似的使用下标符号添加新的键值对
colors.yellow  = '#FFFF00' // 使用属性符号来添加键值对

assert colors.pink == '#FF00FF'
assert colors['yellow'] == '#FFFF00'

assert colors instanceof java.util.LinkedHashMap

Groovy的map实际上是java.util.LinkedHashMap的实例。

如果访问一个Map中不存在的key时,会得到一个null

assert colors.unknown == null

上面的例子中使用了字符串作为key,但也可以使用其他类型的值作为key

def numbers = [1: 'one', 2: 'two']

assert numbers[1] == 'one'

这里使用了数字作为key,由于数字可以明确地被识别为数字,所以Groovy不需要像上面地其他例子一样创建一个字符串key。但是如果要传递一个变量中的值作为key:

def key = 'name'
def person = [key: 'Guillaume'] // 与Guillaume关联的键实际上是key字符串,而不是key变量中的值

assert !person.containsKey('name') // map中不包含name键
assert person.containsKey('key') // map中包含key键

当使用名称作为key时,实际上在map中定义了字符串key。
如果需要传递变量的值作为key值,必须用括号包裹值或表达式:

def key = 'name'
person = [(key): 'Guillaume'] // 用引号包裹key变量,指示解析器我们传递的是变量的值,而不是变量的名称

assert person.containsKey('name') // map中包含name键
assert !person.containsKey('key') // map中不包含key键

你可以传递带引号的字符串作为key:["name":"Guillaume"]。如果key字符串不是一个有效标识,则必须添加引号。例如想要使用一个包含其他字符的字符串作为key时: ["street-name": "Main street"]。如果这个key值不带引号则不支持。


文章作者: suifeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 suifeng !
  目录