《Groovy語言規範》-語法(一)
語法
本章節涵蓋了Groovy編程語言的語法。Groovy語言的語法源自Java語法,為Groovy增強了特定構造,允許一定程度上的簡化語法。
1.注釋
1.1.單行注釋
單行注釋以//開始,在一行中任何位置都可以被發現。//後麵的字符,直到一行的末尾都是注釋的一部分。
// a standalone single line comment
println "hello" // a comment till the end of the line
1.2.多行注釋
一個多行注釋以/*開始,在一行中的任何位置都可以發現,/*後麵的字符將被視為注釋的一部分,包括換行符,直到第一個*/關閉注釋。
多行注釋可以放在語句的末尾,甚至是在一個語句中。
/* a standalone multiline comment
spanning two lines */
println "hello" /* a multiline comment starting
at the end of a statement */
println 1 /* one */ + 2 /* two */
1.3 Groovy文檔注釋
與多行注釋相似,Groovy文檔注釋也是多行的,但是是以/**開始,以*/結尾。第一個Groovy文檔注釋後的行,可以以*開始。這些注釋與以下內容關聯:
- 類型定義 (類, 接口, 枚舉, 注解)
- 字段和屬性定義
- 方法定義
盡管編譯器不會抱怨Groovy文檔注釋沒有與上述語言元素關聯,你應該在這些語言元素之前使用注釋加上這些構造。
/** * A Class description */ class Person { /** the name of the person */ String name
/** * Creates a greeting method for a certain person. * @param otherPerson the person to greet * @return a greeting message */ String greet(String otherPerson) { "Hello ${otherPerson}" } }
Groovy文檔遵循與Java文檔相同的約定。因此你能使用與Java文檔一樣的標簽。
1.4.事務行
除單行注釋外,還有一種特殊的行注釋,在Unix係統下經常被稱作是事務行,且允許腳本直接從命令行運行,前提是你應將安裝了Groovy發布版,且在PATH中Groovy的命令行是可用的。
#!/usr/bin/env groovy
println "Hello from the shebang line"
#字符必須是文件的第一個字符。任何縮進都會產生編譯錯誤。
2.關鍵字
下麵列表表示了Groovy語言所有的關鍵字
表1.關鍵字 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 null package return super switch this throw throws trait true try while
3.標識符
3.1.正常標識符
標識符以字母,美元符號或下劃線開始。不能以一個數字開始。
一個字母可以在以下範圍:
- ‘a’ 至 ‘z’ (小寫ascii字母)
- ‘A’ 至 ‘Z’ (大寫ascii字母)
- ‘\u00C0′ 至 ‘\u00D6′
- ‘\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"
正如我們將在字符串章節看到的,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."Simson-${firstname}" = "Homer Simson"
assert map.'Simson-Homer' == "Homer Simson"
4.字符串
文本文字以字符鏈的形式表示被稱作字符串。Groovy可以讓你實例化java.lang.String對象,也可以實例化GString(groovy.lang.GString),在其他編程語言中被稱為插值字符串。
4.1.單引號字符串
單引號字符串是一係列被單引號包圍的字符。
'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
'''
你將要注意的是,這個字符串的結果包含一個換行轉義符作為第一個字符。它可以通過使用反斜杠換行符剝離該字符:
def strippedFirstNewline = '''\
line one
line two
line three
'''
assert !strippedFirstNewline.startsWith('\n')
4.3.1.轉義特殊字符
你可以使用反斜杠字符轉義單引號,避免終止字符串:
'an escaped single quote: \' needs a backslash'
你能使用雙反斜杠來轉義轉義字符自身:
'an escaped escape character: \\ needs a double backslash'
一些特殊字符使用反斜杠作為轉義字符:
轉義序列 字符 '\t' tabulation '\b' backspace '\n' newline '\r' carriage return '\f' formfeed '\\' backslash '\'' single quote (for single quoted and triple single quoted strings) '\"' double quote (for double quoted and triple double quoted strings)
4.3.2.Unicode轉義序列
對於那些鍵盤不能表示的字符,你能使用Unicode轉義序列:一個反斜杠,後麵跟著‘u’,然後是4個十六進製的數字。
例如,歐元貨幣標誌可以使用這個表示:
'The Euro currency symbol: \u20AC'
4.4 雙引號字符串
雙引號字符串是一些列被雙引號包圍的字符:
"a double quoted string"
如果沒有插值表達式,雙引號字符串是普通的java.lang.String,如果插值存在則是groocy.lang.GString實例。
為了轉義一個雙引號,你能使用反斜杠字符:”A double quote: \””。
4.4.1 字符串插值
任何Groovy表達式可以在所有字符文本進行插值,除了單引號和三單引號字符串。插值是使用占位符上的字符串計算值替換占位符的操作。占位符表達式是被${}包圍,或前綴為$的表達式。當GString被傳遞給一個帶有一個String參數的方法時,占位符的表達式被計算值,並通過調用表達式的toString()方法以字符串形式表示。
這裏是一個占位符引用局部變量的字符串:
def name = 'Guillaume' // a plain string
def greeting = "Hello ${name}"
assert greeting.toString() == 'Hello Guillaume'
而且任何Groovy表達式是合法的,正如我們在示例中使用算數表達式所見一樣:
def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5'
不僅任何表達式,實際上也允許${}占位符。語句也是被允許的,但一個語句等效於null。如果有多個語句插入占位符,那麼最後一個語句應該返回一個有意義的值被插入占位符。例如,”The sum of 1 and 2 is equal to ${def a = 1; def b = 2; a + b}”字符串是被支持的,也能如預期一樣工作,但一個好的實踐是在GString占位符插入一個簡單的表達式。
除了${}占位符以外,也可以使用$作為表達式前綴:
def person = [name: 'Guillaume', age: 36]
assert "$person.name is $person.age years old" == 'Guillaume is 36 years old'
但隻有a.b,a.b.c等形式的前綴表達式是合法的,而包含如方法調用的圓括號,閉包的花括號,算術操作符是非法的。給出如下數值變量定義:
def number = 3.14
如下語句將會拋出groovy.lang.MissingPropertyException,因為Groovy相信你在嚐試訪問一個不存在數字的toString屬性:
shouldFail(MissingPropertyException) {
println "$number.toString()"
}
你可以把”$number.toString()”用解釋器解釋為”${number.toString}()”。
如果在GString中你需要轉義$或${}占位符,使它們不出現插值,那麼你隻需要使用反斜杠字符轉義美元符號:
assert '${name}' == "\${name}"
4.4.2.插入閉包表達式的特殊情況
到目前為止,我們僅僅看到在${}占位符中插入任意表達式,但一個閉包表達式標記的特殊情況。當占位符包括一個箭頭,${->},這表達式實際是一個閉包表達式,你可以把它看做一個前麵緊靠美元符號的閉包:
def sParameterLessClosure = "1 + 2 == ${-> 3}" (1) assert sParameterLessClosure == '1 + 2 == 3'
def sOneParamClosure = "1 + 2 == ${ w -> w << 3}" (2) assert sOneParamClosure == '1 + 2 == 3'
(1)這是一個不攜帶參數的無參閉包
(2)這裏的閉包攜帶一個java.io.StringWrite參數,你能使用<<追加內容。在這兩處,占位符被嵌入閉包。
在外觀上,定義一個表達式被插入看著有一些冗長,但閉包相比表達式有一個有趣的優勢:延遲計算。
讓我們思考如下示例:
def number = 1 (1) def eagerGString = "value == ${number}" def lazyGString = "value == ${ -> number }"
assert eagerGString == "value == 1" (2) assert lazyGString == "value == 1" (3)
number = 2 (4) assert eagerGString == "value == 1" (5) assert lazyGString == "value == 2" (6)
(1)我們定義一個包含1的number變量,然後插入兩個GString之中,在eagerGString中作為表達式,在lazyGString中作為閉包。
(2)我們期望對於eagerGString得到的結果字符串是包含1的相同字符串
(3)lazyGString相似
(4)然後給變量賦一個新值
(5)使用純插值表達式,這值在GString創建時結合
(6)但使用閉包表達式,GString被強轉為Sring時,閉包被調用,並產生包含新數值的更新字符串。
一個嵌入的閉包表達式,攜帶超過一個參數,那麼在運行時將會產生一個異常。閉包僅僅允許攜帶0個或1個參數。
4.4.3.與Java的互操作性
當一個方法(無論是在Java或是Groovy中實現)預期需要一個java.lang.String,而我們傳遞了一個groovy.lang.GString實例,GString的toString()方法將會被自動透明的調用。
String takeString(String message) { (4) assert message instanceof String (5) return message }
def message = "The message is ${'hello'}" (1) assert message instanceof GString (2)
def result = takeString(message) (3) assert result instanceof String assert result == 'The message is hello'
(1)我們創建一個GSring變量
(2)我們仔細檢查GString實例
(3)我們將GString傳遞個一個攜帶String參數的方法
(4)takeString()明確說明它的唯一參數是一個String
(5)我們也驗證一個參數是String而不是GString
4.4.4.GString和String的hashCode
雖然插值字符串可以代替普通Java字符串,它們用一種不同的方式是字符串不同:它們的hashCode是不同的。普通Java字符串是不可變的,而一個GString依賴於插入的值,它的String是可變的。即使有相同的字符串結果,GString和String也沒有相同的hashCode。
assert "one: ${1}".hashCode() != "one: 1".hashCode()
GString和String有不同的hashCode值,應該避免使用GSring作為Map的鍵值,我們使用String代替GString取出一個關聯值。
def key = "a" def m = ["${key}": "letter ${key}"] (1)
assert m["a"] == null (2)
(1)map被一個初始化鍵值對創建,其鍵值是GString
(2)當我們嚐試使用String鍵值獲取值時,我們並沒獲取對應值,因為String和GString有不同的hashCode
4.5.三雙引號字符串
三雙引號字符串與雙引號字符串相同,增加多行,像三單引號字符串一樣。
def name = 'Groovy' def template = """ Dear Mr ${name},
You're the winner of the lottery!
Yours sincerly,
Dave
""" assert template.toString().contains('Groovy')
無論是雙引號還是單引號,在三雙引號字符串中需要被轉義。
最後更新:2017-05-22 12:01:26