新聞中心
1. 介紹
本篇內(nèi)容開(kāi)始介紹Groovy中的各種類型知識(shí)。將會(huì)分多篇文章詳細(xì)介紹和學(xué)習(xí)Groovy中的有關(guān)于類型的相關(guān)知識(shí)點(diǎn)。

創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),常山企業(yè)網(wǎng)站建設(shè),常山品牌網(wǎng)站建設(shè),網(wǎng)站定制,常山網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,常山網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
內(nèi)容來(lái)源于Groovy官方文檔中得到1.6.6. Typing中的相關(guān)知識(shí)點(diǎn)。
內(nèi)容比較多??梢酝ㄟ^(guò)目錄查詢想了解的模塊。
2. 可選類型-Optional typing
可選類型是指即使不在變量上設(shè)置顯式類型,程序也可以工作。作為一種動(dòng)態(tài)語(yǔ)言,Groovy自然實(shí)現(xiàn)了這一特性,例如,當(dāng)聲明一個(gè)變量時(shí):
String aString = 'zinyan.com' //聲明了一個(gè)變量字符串
//我們調(diào)用這個(gè)字符串的大小寫(xiě)轉(zhuǎn)換方法并輸出
println aString.toUpperCase() //輸出:ZINYAN.COM
在Groovy中,我們可以通過(guò)可選類型關(guān)鍵字:def 來(lái)代替:
def aString = 'zinyan.com' //聲明了一個(gè)變量字符串
println aString.toUpperCase()
兩種寫(xiě)法是一樣的。def不只是可以代替String,它可以代替任何的一種數(shù)據(jù)類型。
所以在這里使用顯式類型并不重要。當(dāng)我們將此功能與靜態(tài)類型檢查相結(jié)合時(shí),這尤其有趣,因?yàn)轭愋蜋z查器執(zhí)行類型推斷。
同樣,Groovy不強(qiáng)制在方法中聲明參數(shù)的類型:
String concat(String a, String b) {
a+b
}
println concat('zinyan','.com') //輸出:zinyan.com
可以使用def作為返回類型和參數(shù)類型來(lái)重寫(xiě),以便利用duck類型,如以下示例所示:
def concat(def a, def b) {
a+b
}
println concat('zinyan','.com') //輸出:zinyan.com
println concat(1,2) //輸出:3
我們通過(guò)def可選類型,就能實(shí)現(xiàn)動(dòng)態(tài)的參數(shù)處理了。擴(kuò)展了方法的使用范圍。
建議在這里使用def?關(guān)鍵字來(lái)描述一個(gè)方法的意圖,該方法應(yīng)該適用于任何類型,但從技術(shù)上講,我們可以使用Object?,結(jié)果是一樣的:在Groovy中,def?嚴(yán)格等同于使用Object。
最終,可以從返回類型和描述符中完全刪除該類型。但如果要從返回類型中刪除它,則需要為該方法添加顯式修飾符,以便編譯器可以在方法聲明和方法調(diào)用之間產(chǎn)生差異,如以下示例所示:
private concat(a,b) {
a+b
}
println concat('zinyan','.com') //輸出:zinyan.com
println concat(1,2) //輸出:3
我們直接將def給省略了。
在公共API的方法參數(shù)或方法返回類型中,省略類型通常被認(rèn)為是一種不好的做法。雖然在局部變量中使用def?并不是一個(gè)真正的問(wèn)題,因?yàn)樽兞康目梢?jiàn)性僅限于方法本身,但在方法參數(shù)上設(shè)置def?時(shí),def將在方法簽名中轉(zhuǎn)換為Object,這使得用戶很難知道哪種類型的參數(shù)是期望的類型。
PS:總結(jié)來(lái)說(shuō),我們可以將類型定義為def,然后還能將def給省略掉。但是不建議大家在對(duì)外提供的api中省略def。容易造成閱讀困難。
其次,def就是java中的Object對(duì)象。只是中間的各種轉(zhuǎn)換解析等功能Groovy在編譯器中幫我們進(jìn)行了轉(zhuǎn)換。
3. 靜態(tài)類型檢測(cè)-Static type checking
默認(rèn)情況下,Groovy在編譯時(shí)執(zhí)行最小的類型檢查。由于它主要是一種動(dòng)態(tài)語(yǔ)言,所以靜態(tài)編譯器通常無(wú)法在編譯時(shí)進(jìn)行的大多數(shù)檢查。通過(guò)運(yùn)行時(shí)元編程添加的方法可能會(huì)改變類或?qū)ο蟮倪\(yùn)行時(shí)行為。
通過(guò)示例介紹一下:
//創(chuàng)建一個(gè)對(duì)象
class Person {
String firstName
String lastName
}
//初始化該實(shí)例對(duì)象
def p = new Person(firstName: 'Zin', lastName: 'yan')
println p.formattedName
在動(dòng)態(tài)語(yǔ)言中,像上述示例這樣的代碼不拋出任何錯(cuò)誤是很常見(jiàn)的。在Java中,這通常會(huì)在編譯時(shí)失敗。
我們直接執(zhí)行上面的代碼就會(huì)輸出:
Caught: groovy.lang.MissingPropertyException: No such property: formattedName for class: Person
groovy.lang.MissingPropertyException: No such property: formattedName for class: Person
at zinyan.run(zinyan.groovy:8)
錯(cuò)誤提示,我們?nèi)绻胝_\(yùn)行,就需要執(zhí)行依賴運(yùn)行時(shí)元編程。也就是說(shuō)修改運(yùn)行時(shí)狀態(tài),執(zhí)行動(dòng)態(tài)特性:
Person.metaClass.getFormattedName = { "$delegate.firstName $delegate.lastName" }
完整示例為:
//創(chuàng)建一個(gè)對(duì)象
class Person {
String firstName
String lastName
}
//初始化該實(shí)例對(duì)象
Person.metaClass.getFormattedName = { "$delegate.firstName $delegate.lastName" }
def p = new Person(firstName: 'Zin', lastName: 'yan')
println p.formattedName
這意味著,一般來(lái)說(shuō),在Groovy中,除了聲明類型之外,我們不能對(duì)對(duì)象的類型做出任何假設(shè),即使我們知道它,也無(wú)法在編譯時(shí)確定將調(diào)用什么方法,或者將檢索哪個(gè)屬性。這個(gè)特性用在DSL和測(cè)試腳本編寫(xiě)中有不少的特性。這里就不展開(kāi)了。
然而,如果我們的程序不依賴動(dòng)態(tài)特性,并且來(lái)自靜態(tài)世界(特別是來(lái)自Java思維),那么在編譯時(shí)沒(méi)有捕捉到這樣的“錯(cuò)誤”可能會(huì)出現(xiàn)崩潰。正如我們?cè)谇懊娴氖纠锌吹降?,編譯器不能確定這是一個(gè)錯(cuò)誤。為了讓編譯器意識(shí)到這一點(diǎn),必須明確指示編譯器我們正在切換到類型檢查模式。這可以通過(guò)使用
@groovy.transform.TypeChecked注釋類或方法來(lái)完成。
當(dāng)激活類型檢查時(shí),編譯器將新增以下的工作:
類型推斷被激活,這意味著即使對(duì)局部變量使用def,類型檢查器也能夠從賦值中推斷出變量的類型.
方法調(diào)用在編譯時(shí)解析,這意味著如果沒(méi)有在類上聲明方法,編譯器將拋出錯(cuò)誤
通常,在靜態(tài)語(yǔ)言中查找的所有編譯時(shí)錯(cuò)誤都會(huì)出現(xiàn):方法未找到、屬性未找到、方法調(diào)用的不兼容類型、數(shù)字精度錯(cuò)誤等…
下面讓我們描述類型檢查器在各種情況下的行為,并解釋在代碼中使用@TypeChecked的限制。
3.1 @TypeChecked注解
在編譯時(shí)激活類型檢查。
我們可以將@groovy.transform.TypeChecked注解添加到類的開(kāi)頭,
讓編譯器編譯該類時(shí)啟用類型檢測(cè):
@groovy.transform.TypeChecked
class Calculator {
int sum(int x, int y) { x+y }
}
或添加到方法中:
class Calculator {
@groovy.transform.TypeChecked
int sum(int x, int y) { x+y }
}
在第一種情況下,所有方法、屬性、字段、內(nèi)部類… 注釋類的類型將被檢查,而在第二種情況下,只有方法和它包含的潛在閉包或匿名內(nèi)部類將被類型檢查。
我們同時(shí)也可以通過(guò)@TypeChecked(TypeCheckingMode.skip)對(duì)其進(jìn)行注釋來(lái)指示類型檢查器跳過(guò)類型檢測(cè):
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
//對(duì)GreetingService類的所有方法和類進(jìn)行類型檢測(cè)。
@TypeChecked
class GreetingService {
String greeting() {
doGreet()
}
//對(duì)doGreet方法跳過(guò)類型檢測(cè)。
@TypeChecked(TypeCheckingMode.SKIP)
private String doGreet() {
def b = new SentenceBuilder()
b.Hello.my.name.is.Zinyan
b
}
}
def s = new GreetingService()
assert s.greeting() == 'Hello my name is Zinyan'
在前面的示例中,SentenceBuilder?依賴于動(dòng)態(tài)代碼。沒(méi)有真正的Hello?方法或?qū)傩?,因此類型檢查器通常會(huì)發(fā)出異常,編譯將失敗。因?yàn)槭褂蒙善鞯姆椒ū粯?biāo)記為T(mén)ypeCheckingMode.SKIP,此方法跳過(guò)了類型檢查,因此即使類的其余部分進(jìn)行了類型檢查也會(huì)編譯代碼。
以下部分描述Groovy中類型檢查的語(yǔ)義。
3.2 類型檢查分配
類型A?的對(duì)象o?可以賦值給類型T的變量當(dāng)且僅當(dāng):
- T? 等于A。
- 或者T? 是以下幾種類型之一:String?, boolean?, Boolean? 或Class。
- 或者o? 是空的,T不是一個(gè)基本類型。
- 或者T和A? 是一個(gè)數(shù)組, A? 的組件類型可分配給 T 的組件類型。
- 或者T? 是一個(gè)數(shù)組,A? 是一個(gè)集合或流(stream ), A?的組件類型可分配給 T的組件類型。
- 或者T? 是 A 的超類。
- 或者T?是由 A 實(shí)現(xiàn)的接口。
- 或者T?或 A 是基本類型,它們的封裝類型是可賦值的。
- 或者T?extedns groovy.lang.Closure?是一個(gè)閉包,同時(shí)A 是SAM類型(單一抽象方法類型)。
- 或者T和A?源自java.lang. Number,并遵循下表:
|
T |
A |
Examples |
|
Double |
Any but BigDecimal or BigInteger |
? |
|
Float |
Any type but BigDecimal, BigInteger or Double |
? |
|
Long |
Any type but BigDecimal, BigInteger, Double or Float |
? |
|
Integer |
Any type but BigDecimal, BigInteger, Double, Float or Long |
? |
|
Short |
Any type but BigDecimal, BigInteger, Double, Float, Long or Integer |
? |
|
Byte |
Byte |
? |
3.3 List 和Map 的構(gòu)造函數(shù)
除了上面的賦值規(guī)則,如果賦值被認(rèn)為是無(wú)效的,在類型檢查模式下,如果滿足以下條件,List或Map A?可以賦值給類型T的變量:
賦值是一個(gè)變量聲明,A?是一個(gè)List,T有一個(gè)構(gòu)造函數(shù),其參數(shù)與List的元素類型匹配。
賦值是一個(gè)變量聲明,A是一個(gè)map,T有一個(gè)無(wú)參數(shù)構(gòu)造函數(shù),每個(gè)map鍵都有一個(gè)屬性。
具體示例如下:
@groovy.transform.TupleConstructor
class Person {
String firstName
String lastName
}
Person classic = new Person('Zin','yan')
可以使用“列表構(gòu)造函數(shù)”:
Person list = ['Zin','yan']
創(chuàng)建一個(gè)Person對(duì)象出來(lái),也可以使用Map構(gòu)造函數(shù)創(chuàng)建一個(gè)Person對(duì)象:
Person map = [firstName:'Zin', lastName:'yan']
如果使用Map構(gòu)造函數(shù),則會(huì)對(duì)映射的鍵進(jìn)行額外檢查,以檢查是否定義了同名的屬性。例如,以下代碼將在編譯時(shí)失敗:
@groovy.transform.TupleConstructor
class Person {
String firstName
String lastName
}
Person map = [firstName:'Zin', lastName:'yan', age: 1024]
就會(huì)觸發(fā)以下錯(cuò)誤:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{firstName=Zin, lastName=yan, age=1024}' with class 'java.util.LinkedHashMap' to class 'Person' due to: org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: No such property: age for class: Person
3.4 方法解析
在類型檢查模式下,方法在編譯時(shí)解析。解析通過(guò)名稱和參數(shù)工作。返回類型與方法選擇無(wú)關(guān)。參數(shù)類型與以下規(guī)則中的參數(shù)類型匹配:
類型A?的參數(shù)o?可以用于類型T的參數(shù),當(dāng)且僅當(dāng):
- T? 等于A。
- 或者T?是一個(gè)String,A?是一個(gè)GString。
- 或者o?為空,T不是基礎(chǔ)類型。
- 或者T?是一個(gè)數(shù)組,A?是一個(gè)數(shù)組,A?的組件類型可以分配給T的組件類型。
- 或者T是A的超類。
- 或者T是A實(shí)現(xiàn)的接口。
- 或者T或A是基本類型,它們的封裝類型是可賦值的。
- 或T?擴(kuò)展了groovy.lang.Closure?,而A是SAM類型(單一抽象方法類型)。
- 或者T和A?派生自java.lang.Number,并遵循與數(shù)字賦值相同的規(guī)則。
如果在編譯時(shí)沒(méi)有找到具有適當(dāng)名稱和參數(shù)的方法,則拋出錯(cuò)誤。下面的例子說(shuō)明了與“正常”Groovy的區(qū)別:
class MyService {
void doSomething() {
printLine 'Do something'
}
}
printLine是一個(gè)錯(cuò)誤,但由于我們處于動(dòng)態(tài)模式,錯(cuò)誤在編譯時(shí)不會(huì)被捕獲
上面的例子展示了一個(gè)Groovy能夠編譯的類。但是,如果嘗試創(chuàng)建MyService?的實(shí)例并調(diào)用doSomething?方法,那么它將在運(yùn)行時(shí)失敗,因?yàn)閜rintLine?不存在。當(dāng)然,我們已經(jīng)展示了Groovy如何使它成為一個(gè)完全有效的調(diào)用,例如通過(guò)捕獲MethodMissingException?或?qū)崿F(xiàn)一個(gè)自定義元類,但如果你知道你不是在這種情況下,@typecheck會(huì)派上用場(chǎng):
@groovy.transform.TypeChecked
class MyService {
void doSomething() {
printLine 'Do something'
}
}
僅僅添加@typecheck?就會(huì)觸發(fā)編譯時(shí)方法解析。類型檢查器將嘗試在MyService?類上找到一個(gè)接受String的方法printLine,如果找不到。它將編譯失敗,并顯示以下消息:
Cannot find matching method MyService#printLine(java.lang.String)
理解類型檢查器背后的邏輯很重要:它是一種編譯時(shí)檢查,因此根據(jù)定義,類型檢查器不知道我們所做的任何類型的運(yùn)行時(shí)元編程。這意味著如果激活類型檢查,沒(méi)有@TypeChecked也完全有效的代碼將不再編譯。如果你想到duck typing,這一點(diǎn)尤其重要:
class Duck {
void quack() {
println 'Quack!'
}
}
class QuackingBird {
void quack() {
println 'Quack!'
}
}
@groovy.transform.TypeChecked
void accept(quacker) {
quacker.quack()
}
accept(new Duck())
比如引入一個(gè)接口,但基本上,通過(guò)激活類型檢查,獲得了類型安全,但失去了語(yǔ)言的一些特性。希望Groovy能引入一些特性,比如流類型,以縮小類型檢查和非類型檢查Groovy之間的差距。
4. 小結(jié)
本篇內(nèi)容未完待續(xù)??梢酝ㄟ^(guò)下一篇了解完整內(nèi)容。
以上內(nèi)容參考Groovy 官方文檔:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_typing
當(dāng)前標(biāo)題:Groovy語(yǔ)法類型知識(shí)詳解,你明白了嗎?
本文來(lái)源:http://www.5511xx.com/article/coociih.html


咨詢
建站咨詢
