自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Groovy 語法類型知識詳解最終篇

開發(fā) 前端
本篇是Typing相關(guān)知識的最后一篇。介紹關(guān)于類型的閉包和類型推斷關(guān)系,以及最終的類型靜態(tài)編譯相關(guān)知識點。

1. 介紹

本篇是Typing相關(guān)知識的最后一篇。介紹關(guān)于類型的閉包和類型推斷關(guān)系,以及最終的類型靜態(tài)編譯相關(guān)知識點。

2. 閉包和類型推斷

類型檢查器對閉包執(zhí)行特殊的推斷,在一邊執(zhí)行額外的檢查,在另一邊提高流暢性。

2.1 返回類型推斷

類型檢查器能夠做的第一件事是推斷閉包的返回類型。下面的例子簡單地說明了這一點:

@groovy.transform.TypeChecked
int testClosureReturnTypeInference(String arg) {
def cl = { "Arg: $arg" } //定義一個閉包,它返回一個GString字符串
def val = cl() //調(diào)用閉包并將結(jié)果賦值給一個變量
val.length() //類型檢查器推斷閉包將返回一個字符串,因此允許調(diào)用length()
}

正如上面所看到的,與顯式聲明其返回類型的方法不同,不需要聲明閉包的返回類型:它的類型是從閉包的主體推斷出來的。

2.2 閉包vs方法

返回類型推斷僅適用于閉包。雖然類型檢查器可以對方法執(zhí)行相同的操作,但實際上并不可取:通常情況下,方法可以被覆蓋,并且靜態(tài)地不可能確保所調(diào)用的方法不是被覆蓋的版本。所以流類型實際上會認(rèn)為一個方法返回一些東西,而在現(xiàn)實中,它可以返回其他東西,如下面的例子所示:

@TypeChecked
class A {
def compute() { 'some string' } //類A定義了一個方法compute,它返回一個字符串
def computeFully() {
compute().toUpperCase() //這將導(dǎo)致編譯失敗,因為compute的返回類型是def(又名Object)
}
}
@TypeChecked
class B extends A {
def compute() { 123 } //類B擴展了A并重新定義了compute,該類型返回一個整型
}

通過上面的示例可以知道,如果類型檢查器依賴于方法的推斷返回類型(使用流類型),則類型檢查器可以確定是否可以調(diào)用toUpperCase?。這實際上是一個錯誤,因為子類可以重寫compute?并返回不同的對象。這里,B.compute?返回一個整型,因此在B?的實例上調(diào)用computeFully將會看到一個運行時錯誤。編譯器通過使用方法的聲明返回類型而不是推斷返回類型來防止這種情況發(fā)生。

為了保持一致性,這種行為對于每個方法都是相同的,即使它們是靜態(tài)的或最終的。

2.3 參數(shù)類型推斷

除了返回類型外,閉包還可以從上下文推斷其參數(shù)類型。編譯器有兩種方法來推斷形參類型:

  • 通過隱式SAM類型強制
  • 通過API元數(shù)據(jù)

讓我們從一個由于類型檢查器無法推斷形參類型而導(dǎo)致編譯失敗的示例開始:

class Person {
String name
int age
}

void inviteIf(Person p, Closure<Boolean> predicate) { //inviteIf方法接受一個Person和一個閉包
if (predicate.call(p)) {
// send invite
// ...
}
}

@groovy.transform.TypeChecked
void failCompilation() {
Person p = new Person(name: 'Gerard', age: 55)
inviteIf(p) {
it.age >= 18 // No such property: age 它并不是靜態(tài)地稱為Person,因此編譯失敗
}
}

在這個例子中,閉包體包含了it.age?。對于動態(tài)的、非類型檢查的代碼,這是可行的,因為它的類型在運行時是Person?。不幸的是,在編譯時,沒有辦法知道它的類型,只能通過讀取inviteIf的簽名。

2.3.1 顯式閉包參數(shù)

簡而言之,類型檢查器在inviteIf?方法上沒有足夠的上下文信息來靜態(tài)確定it的類型。這意味著方法調(diào)用需要像這樣重寫:

inviteIf(p) { Person it ->          //它的類型需要顯式地聲明                        
it.age >= 18
}

通過顯式聲明it變量的類型,可以解決這個問題,并使此代碼進行靜態(tài)檢查。

2.3.2 從單一抽象方法類型推斷出的參數(shù)

對于API或框架設(shè)計人員來說,有兩種方法可以使其對用戶來說更優(yōu)雅,這樣他們就不必為閉包參數(shù)聲明顯式類型。第一個方法,也是最簡單的方法,是用SAM類型替換閉包:

interface Predicate<On> { boolean apply(On e) }  //使用apply方法聲明SAM接口              

void inviteIf(Person p, Predicate<Person> predicate) {
if (predicate.apply(p)) {
// send invite
// ...
}
}

@groovy.transform.TypeChecked
void passesCompilation() {
Person p = new Person(name: 'Gerard', age: 55)

inviteIf(p) { //不再需要聲明it變量的類型了
it.age >= 18 //it.age正確編譯后,它的類型是從Predicate#apply方法簽名推斷出來的
}
}

通過使用這種技術(shù),我們利用了Groovy將閉包自動強制轉(zhuǎn)換為SAM類型的特性。

我們應(yīng)該使用SAM類型還是Closure的問題實際上取決于需要做什么。

在很多情況下,使用SAM接口就足夠了,特別是當(dāng)考慮Java 8中的功能接口時。

但是,閉包提供了功能接口無法訪問的特性。特別是,閉包可以有一個委托和所有者,并且可以在被調(diào)用之前作為對象進行操作(例如,克隆、序列化、curry等等)。它們還可以支持多個簽名(多態(tài)性)。

因此,如果需要這種操作,最好切換到下面描述的最高級的類型推斷注釋。

當(dāng)涉及到閉包參數(shù)類型推斷時,最初需要解決的問題是,Groovy類型系統(tǒng)繼承了Java類型系統(tǒng),而Java類型系統(tǒng)不足以描述參數(shù)的類型,也就是說,靜態(tài)地確定閉包的參數(shù)類型,而無需顯式地聲明它們。

2.3.3 使用@ClosureParams 注解

Groovy提供了一個注解@ClosureParams,用于完成類型信息。該注釋主要針對那些希望通過提供類型推斷元數(shù)據(jù)來擴展類型檢查器功能的框架和API開發(fā)人員。如果我們的庫使用閉包,并且也希望獲得最大級別的工具支持,那么這一點非常重要。

讓我們通過修改原始示例來說明這一點,引入@ClosureParams注釋:

import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FirstParam
void inviteIf(Person p, @ClosureParams(FirstParam) Closure<Boolean> predicate) { //閉包參數(shù)用@ClosureParams注釋
if (predicate.call(p)) {
// send invite
// ...
}
}
inviteIf(p) {
it.age >= 18 //沒有必要為它使用顯式類型,因為它是推斷出來的
}

@ClosureParams?注釋最少接受一個參數(shù),該參數(shù)被命名為類型提示。類型提示是一個類,它負(fù)責(zé)在閉包的編譯時完成類型信息。在本例中,使用的類型提示是groovy.transform.stc.FirstParam?,它向類型檢查器指示閉包將接受一個類型為方法第一個參數(shù)類型的參數(shù)。在本例中,方法的第一個參數(shù)是Person?,因此它向類型檢查器指示閉包的第一個參數(shù)實際上是Person。

第二個可選參數(shù)名為options。它的語義取決于類型提示類。Groovy提供了各種捆綁的類型提示,如下表所示:

類型提示

多態(tài)

描述和示例

??FirstParam??? ??SecondParam??? ??ThirdParam??

No

第一個(回復(fù)。第二,第三)參數(shù)類型的方法: ??import groovy.transform.stc.FirstParam void doSomething(String str, @ClosureParams(FirstParam) Closure c) { c(str) } doSomething('foo') { println it.toUpperCase() }``import groovy.transform.stc.SecondParam void withHash(String str, int seed, @ClosureParams(SecondParam) Closure c) { c(31*str.hashCode()+seed) } withHash('foo', (int)System.currentTimeMillis()) { int mod = it%2 }``import groovy.transform.stc.ThirdParam String format(String prefix, String postfix, String o, @ClosureParams(ThirdParam) Closure c) { "$prefix${c(o)}$postfix" } assert format('foo', 'bar', 'baz') { it.toUpperCase() } == 'fooBAZbar'??

??FirstParam.FirstGenericType??? ??SecondParam.FirstGenericType??? ??ThirdParam.FirstGenericType??

No

第一個泛型類型(resp。第二,方法的第三)參數(shù) ??import groovy.transform.stc.FirstParam public <T> void doSomething(List<T> strings, @ClosureParams(FirstParam.FirstGenericType) Closure c) { strings.each { c(it) } } doSomething(['foo','bar']) { println it.toUpperCase() } doSomething([1,2,3]) { println(2*it) }???Variants for ??SecondGenericType??? and ??ThirdGenericType??? exist for all ??FirstParam???, ??SecondParam??? and ??ThirdParam?? type hints.

??SimpleType??

No

閉包參數(shù)的類型來自選項字符串的類型提示。??import groovy.transform.stc.SimpleType public void doSomething(@ClosureParams(value=SimpleType,options=['java.lang.String','int']) Closure c) { c('foo',3) } doSomething { str, len -> assert str.length() == len }??此類型提示支持單個簽名,并且使用完全限定類型名或基本類型將每個參數(shù)指定為options數(shù)組的值。

??MapEntryOrKeyValue??

Yes

一個專用的閉包類型提示,可以在??Map.Entry???的單個參數(shù),或者兩個參數(shù)分別對應(yīng)鍵和值。??import groovy.transform.stc.MapEntryOrKeyValue public <K,V> void doSomething(Map<K,V> map, @ClosureParams(MapEntryOrKeyValue) Closure c) { // ... } doSomething([a: 'A']) { k,v -> assert k.toUpperCase() == v.toUpperCase() } doSomething([abc: 3]) { e -> assert e.key.length() == e.value }???這個類型提示要求第一個參數(shù)是??Map???類型,并從??Map??實際的鍵/值類型推斷閉包參數(shù)類型。

??FromAbstractTypeMethods??

Yes

從某種類型的抽象方法推斷閉包參數(shù)類型。為每個抽象方法推斷一個簽名。??import groovy.transform.stc.FromAbstractTypeMethods abstract class Foo { abstract void firstSignature(int x, int y) abstract void secondSignature(String str) } void doSomething(@ClosureParams(value=FromAbstractTypeMethods, options=["Foo"]) Closure cl) { // ... } doSomething { a, b -> a+b } doSomething { s -> s.toUpperCase() }???如果像上面的例子一樣有多個簽名,那么只有在每個方法的元數(shù)不同的情況下,類型檢查器才能推斷出參數(shù)的類型。在上面的例子中,??firstSignature???接受2個參數(shù),??secondSignature??接受1個參數(shù),因此類型檢查器可以根據(jù)參數(shù)的數(shù)量推斷參數(shù)類型。但是請參閱下面討論的可選解析器類屬性。

??FromString??

Yes

從options參數(shù)推斷閉包參數(shù)類型。options參數(shù)由逗號分隔的非基元類型數(shù)組組成。數(shù)組中的每個元素都對應(yīng)一個簽名,元素中的每個逗號分別對應(yīng)簽名的參數(shù)。簡而言之,這是最通用的類型提示,選項映射的每個字符串都像簽名文字一樣被解析。雖然這種類型提示非常強大,但如果可以的話必須避免,因為它會由于解析類型簽名的必要性而增加編譯時間。??接受String的閉包的單個簽名::??import groovy.transform.stc.FromString void doSomething(@ClosureParams(value=FromString, options=["String","String,Integer"]) Closure cl) { // ... } doSomething { s -> s.toUpperCase() } doSomething { s,i -> s.toUpperCase()*i }???一個多態(tài)閉包,接受??String???或??String???, ??Integer???:??import groovy.transform.stc.FromString void doSomething(@ClosureParams(value=FromString, options=["String","String,Integer"]) Closure cl) { // ... } doSomething { s -> s.toUpperCase() } doSomething { s,i -> s.toUpperCase()*i }???一個多態(tài)閉包,接受一個T或一對T,T:??import groovy.transform.stc.FromString public <T> void doSomething(T e, @ClosureParams(value=FromString, options=["T","T,T"]) Closure cl) { // ... } doSomething('foo') { s -> s.toUpperCase() } doSomething('foo') { s1,s2 -> assert s1.toUpperCase() == s2.toUpperCase() }??

即使你使用FirstParam?, SecondParam或ThirdParam作為類型提示,這并不嚴(yán)格意味著將傳遞給閉包的參數(shù)將是第一個(resp。方法調(diào)用的第二個,第三個)參數(shù)。這只意味著閉包的參數(shù)類型將與第一個(resp。方法調(diào)用的第二個,第三個)參數(shù)。

PS: 上面的表格,從Groovy中直接賦值的。所以表格閱讀比較難看

簡而言之,在接受Closure?的方法上缺少@ClosureParams注釋不會導(dǎo)致編譯失敗。如果存在(它可以出現(xiàn)在Java源代碼中,也可以出現(xiàn)在Groovy源代碼中),則類型檢查器具有更多信息,并可以執(zhí)行額外的類型推斷。這使得框架開發(fā)人員對該特性特別感興趣。

第三個可選參數(shù)名為conflictResolutionStrategy。它可以引用一個類(從

ClosureSignatureConflictResolver擴展而來),如果在初始推斷計算完成后發(fā)現(xiàn)了多個參數(shù)類型,則該類可以執(zhí)行額外的參數(shù)類型解析。Groovy提供了一個默認(rèn)類型解析器,它什么都不做,另一個則在找到多個簽名時選擇第一個簽名。解析器僅在發(fā)現(xiàn)多個簽名時調(diào)用,并且被設(shè)計為后處理器。任何需要注入類型信息的語句都必須傳遞一個通過類型提示確定的參數(shù)簽名。解析器然后從返回的候選簽名中選擇。

類型檢查器使用@DelegatesTo?注釋推斷委托的類型。它允許API設(shè)計者指示編譯器委托的類型和委托策略。@DelegatesTo注釋將在其他內(nèi)容中進行專門的討論。這里就不擴展了。

3. 靜態(tài)編譯

3.1 動態(tài)與靜態(tài)

在類型檢查部分,我們已經(jīng)看到Groovy通過@TypeChecked?注釋提供了可選的類型檢查。類型檢查器在編譯時運行,并對動態(tài)代碼執(zhí)行靜態(tài)分析。無論是否啟用類型檢查,程序的行為都完全相同。這意味著@TypeChecked注釋對于程序的語義是中立的。盡管可能有必要在源中添加類型信息以使程序被認(rèn)為是類型安全的,但最終,程序的語義是相同的。

雖然這聽起來很好,但實際上有一個問題:在編譯時執(zhí)行的動態(tài)代碼的類型檢查,根據(jù)定義,只有在沒有發(fā)生特定于運行時的行為時才正確。例如,下面的程序通過了類型檢查:

class Computer {
int compute(String str) {
str.length()
}
String compute(int x) {
String.valueOf(x)
}
}

@groovy.transform.TypeChecked
void test() {
def computer = new Computer()
computer.with {
assert compute(compute('foobar')) =='6'
}
}

有兩種計算方法。一個接受String?并返回int?,另一個接受int?并返回String?。如果你編譯這個,它被認(rèn)為是類型安全的:內(nèi)部compute('foobar')?調(diào)用將返回一個int?,并且在這個int?上調(diào)用compute?將返回一個String。

現(xiàn)在,在調(diào)用test()之前,考慮添加以下行:

Computer.metaClass.compute = { String str -> new Date() }

使用運行時編程,我們實際上是在修改compute(String)?方法的行為,這樣它就不會返回所提供的參數(shù)的長度,而是返回一個Date。如果執(zhí)行該程序,它將在運行時失敗。因為這一行可以在任何線程的任何地方添加,所以類型檢查器絕對沒有辦法靜態(tài)地確保沒有這樣的事情發(fā)生。簡而言之,類型檢查器很容易受到猴子修補的攻擊。這只是一個例子,但它說明了對動態(tài)程序進行靜態(tài)分析本質(zhì)上是錯誤的。

Groovy為@typecheck?提供了另一種注釋,它實際上將確保被推斷為被調(diào)用的方法將在運行時有效地被調(diào)用。該注釋將Groovy編譯器轉(zhuǎn)換為靜態(tài)編譯器,其中所有方法調(diào)用都在編譯時解析,生成的字節(jié)碼確保實現(xiàn)這一點:注釋是@groovy.transform.CompileStatic。

3.2 @CompileStatic 注解

@CompileStatic?注釋可以添加到@TypeChecked?注釋可以使用的任何地方,也就是說,在類或方法上。沒有必要同時添加@TypeChecked和@CompileStatic?,因為@CompileStatic?執(zhí)行@TypeChecked所做的一切,但是還會觸發(fā)靜態(tài)編譯。

讓我們以失敗的例子為例,但這一次讓我們用@CompileStatic?替換@TypeChecked注釋:

class Computer {
int compute(String str) {
str.length()
}
String compute(int x) {
String.valueOf(x)
}
}

@groovy.transform.CompileStatic
void test() {
def computer = new Computer()
computer.with {
assert compute(compute('foobar')) =='6'
}
}
Computer.metaClass.compute = { String str -> new Date() }
test()

這是唯一的區(qū)別。如果我們執(zhí)行這個程序,這次就不會出現(xiàn)運行時錯誤。test?方法不再受猴子補丁的影響,因為在它的主體中調(diào)用的計算方法在編譯時是鏈接的,所以即使Computer的元類發(fā)生了變化,程序仍然按照類型檢查器的預(yù)期行事。

3.3 關(guān)鍵優(yōu)勢

在代碼中使用@CompileStatic有幾個好處:

  • 類型安全
  • 對猴子補丁(monkey patching)免疫
  • 性能改進

性能的提高取決于所執(zhí)行程序的類型。

如果它受I/O限制,靜態(tài)編譯代碼和動態(tài)代碼之間的區(qū)別幾乎不明顯。

對于高度CPU密集型的代碼,由于生成的字節(jié)碼與Java為等效程序生成的字節(jié)碼非常接近(如果不是相等的話),因此性能得到了極大的提高。

4. 小結(jié)

到這里關(guān)于類型的相關(guān)知識就介紹完畢了,以上內(nèi)容可以通過Groovy官方文檔:Groovy Language Documentation (groovy-lang.org)了解更多知識。

PS:類型知識的介紹更多的是從各種概念定義等方面詳細(xì)介紹各種類型推斷的過程。我們其實可以簡單了解。

責(zé)任編輯:武曉燕 來源: zinyan
相關(guān)推薦

2023-01-02 23:58:03

2022-12-28 08:03:02

Groovy語法GPath

2022-12-26 08:36:53

Groovy語法控制結(jié)構(gòu)

2010-04-22 22:36:21

F5負(fù)載均衡器

2023-01-05 08:09:27

GroovyDSL?

2023-04-06 07:49:23

Python數(shù)據(jù)類型

2023-01-06 08:06:52

Groovy類型擴展

2022-12-29 08:16:45

Groovy語法coercion

2011-07-06 11:19:45

Objective-C

2011-08-24 13:23:35

Access 2010

2012-07-02 10:43:49

JVMGroovyJava

2012-07-12 11:23:07

GroovyJVM

2011-08-23 13:16:41

SQLEXPLAIN

2024-06-21 09:37:02

DefPython函數(shù)

2010-09-06 13:15:48

CSS定位

2013-04-17 10:20:27

GroovyClassLoader

2010-09-17 14:49:04

Java數(shù)據(jù)類型

2009-08-18 12:52:33

C#枚舉類型

2009-12-18 15:06:10

Ruby常用庫

2010-11-11 10:18:59

select into
點贊
收藏

51CTO技術(shù)棧公眾號