Dart中的可選類型是如何工作的
Dart 語言是動態(tài)類型的。你可以編寫、運行沒有類型標注的任何程序,就像你使用Javascript的方式。
51CTO推薦專題:Google Dart新結構化編程語言
你可以在程序中添加類型標注:
◆ 添加類型不會阻止你程序的編譯和運行——即使標注不完整或錯誤。
◆ 不論你添加了什么類型標注,你的程序都具有完全相同的語義。
然而,添加類型標注可以使你獲益。類型提供了下面這些好處:
◆ 給人看的文檔。明智地放置類型標注可以使別人更容易地閱讀你的代碼。
◆ 給機器看的文檔。工具可以有多種方式利用類型標注。特別是,它們可以在 IDE 中幫助提供很好的特性,如名稱補全和增強的導航。
◆ 早期的錯誤檢測。Dart 提供了靜態(tài)檢查器,它可以警告你潛在的問題,而不用你自己查。另外,在開發(fā)模式中,Dart 自動把類型標注轉換為運行時斷言檢查來輔助調試。
◆ 有時,在編譯到 Javascript 時,類型可以幫助改進性能。
靜態(tài)檢查器
靜態(tài)檢查器(static checker)行為很像C中的鏈接。它在編譯時警告你潛在的問題。這些警告中的很多是和類型相關的。靜態(tài)檢查器不會產(chǎn)生錯誤——不論檢查器說什么你總是可以編譯和運行你的代碼。
檢查器不會對每個可能的類型違反都敏感。它不是類型檢查器(typechecker),因為Dart并不是按照典型的類型系統(tǒng)那樣使用類型。檢查器會抱怨那些非??赡苁钦鎸崋栴}的地方,而不會強迫你去滿足心胸狹隘的類型系統(tǒng)。
例如,考慮這個:
- String s1 = '9';
- String s2 = '1';
- ...
- int n = s1 + s2;
- print(n);
這里明顯是個問題。這種情況下靜態(tài)檢查器會產(chǎn)生一個警告。注意代碼依然可以運行,n 被置為字符串'91'并打印出來。
然而,不像典型的強類型系統(tǒng),這樣的代碼:
- Object lookup(String key) {...} // a lookup method in a heterogenous table
- String s = lookup('Frankenstein');
檢查器不會抱怨。因為這種情況下代碼很有可能是對的,雖然缺少類型信息。你作為程序員通常知道程序的語義,而類型檢查器(typechecker)不知道。你知道'Frankenstein'這個key在表中存儲的是字符串,即使 lookup 方法聲明返回的是Object。
Dynamic類型
沒有提供類型的時候,Dart如何避免抱怨呢?這其中的關鍵就是 Dynamic 類型,這是程序員沒有明確給出類型時候的默認類型。使用 Dynamic 類型讓檢查器閉嘴。
偶爾,你可能想要明確地使用 Dynamic 。
- Map<String, Dynamic> m = {
- 'one': new Partridge(),
- 'two': new TurtleDove(),
- ...,
- 'twelve': new Drummer()};
我們本來也可以給m使用 Map<String, Object> ,但是那樣的話,當我們獲取內容的時候,它們將是Object的靜態(tài)類型,而它只有很少的信息。因為map的內容除了Object外沒有公共的super接口,我們可能更愿意使用 Dynamic 。如果我們像這樣調用map 的值的方法:
- pearTree = m['one'].container();
如果內容是Object類型,我們會得到警告,因為Object不支持container方法。如果我們使用Dynamic類型,就不會產(chǎn)生警告。
范型
Dart 支持具體化范型(reified generics)。就是說,范型類型的對象在運行時攜帶它們的類型參數(shù)。傳遞類型參數(shù)給范型類型的構造函數(shù)是運行時操作。這如何與可選類型的要求相一致呢?
好吧,如果你不想總是考慮類型,范型并不強迫你。你可以創(chuàng)建范型類的實例,而不需要提供類型參數(shù)。例如,這樣寫沒問題:
- new List();
當然,如果你想要,也可以這樣寫:
- new List<String>();
- new List();
是下面這樣的快捷方式:
- new List<Dynamic>();
在構造函數(shù)中,類型參數(shù)起到運行時角色。實際上,它們在運行時被傳遞,所以你可以做動態(tài)類型測試的時候使用它們。
- new List<String>() is List<Object> // true: every string is an object
- new List<Object>() is List<String> // false: not all objects are strings
Dart中的范型符合程序員的直覺。這是一些更有趣的情況:
- new List<String>() is List<int> // false
- new List<String>() is List // true
- new List<String>() is List<Dynamic> // same as line above
- new List() is List<Dynamic> // true, these are exactly the same
與此相反,類型標注(例如變量前添加的類型或者函數(shù)和方法的返回類型)起到非運行時角色并且不影響程序的語義。***一個值得學習的情況:
- new List() is List<String> // true as well!
你可以不用類型寫程序,但是你經(jīng)常要傳遞數(shù)據(jù)到有類型的庫中。為了防止類型妨礙你,沒有類型參數(shù)的范型類型被認為是任何其它范型類型的替代品(子類型)。
檢查模式
在開發(fā)過程中,Dart 程序可以在檢查模式(checked mode)下運行。如果你在檢查模式下運行程序,在參數(shù)傳遞、返回結果和執(zhí)行賦值時,系統(tǒng)將自動執(zhí)行某些類型的檢查。如果檢查失敗,程序將在該處停止執(zhí)行,并帶有清晰的錯誤信息。所以,
- String s = new Object();
將會停止執(zhí)行,因為Object不是String的子類型。然而,
- Object foo(){return "x";}
- String s = foo();
工作正常,因為foo在運行時返回的實際對象就是String,盡管其類型簽名說foo返回的是Object。當對象賦值給變量時,Dart 檢查對象的運行時類型是否為變量(靜態(tài))聲明類型的子類型。
本質上,檢查模式就像是在對每次賦值、返回等進行子類型檢查的調試器下運行。一些更復雜的例子:
- <int>[0,1, 1][2] = new Object(); // fails in checked mode
- bar(int n) { return n *2;}
- ...
- bar(3.2); // returns 6.4 in production, but fails in checked mode
在檢查模式下,每次把參數(shù)傳遞給函數(shù)時,都要檢查參數(shù)的運行時類型是否是形式參數(shù)聲明類型的子類型。我們可以很容易地糾正這個:
- bar(num n) { return n *2;}
- ...
- bar(3.2); // works fine
- int i_bar(num n) { return n *2;}
- ...
- i_bar(3.2); // fails in checked mode
- // because returned value is not an int
注意***一行。檢查發(fā)生在返回值上,即使函數(shù)的結果并沒有進行賦值。
讓我們回到之前的Frankenstein例子上。
- Object lookup(String key) {...} // a lookup method in a heterogenous table
- String s = lookup('Frankenstein');
如果我們假設的lookup方法返回一個String是正確的,那么檢查模式會平滑地執(zhí)行。如果不是,那么它將捕獲到我們的這個錯誤。在生產(chǎn)模式(production mode)下,代碼都會運行,不會抱怨。假設lookup方法真的返回了一個非String對象,一個Frankenstein類的實例。那么變量 s 將容納那個實例。Dart 絕不會神奇地強制它為一個字符串。如果Dart那樣做就會意味著類型標注正在改變我們程序的行為,類型就不再是可選的了。
當然,如果你根本就不用類型,檢查模式不會妨礙你。
- my_add(s1, s2) { return s1 + s2;}
- my_add(3, 4); // 7
- my_add("3", "4"); // "34"
所有這些檢查會帶來很大的性能損失,所以通常不能用在生產(chǎn)環(huán)境中。這些檢查的好處是它們可以在源頭上捕獲動態(tài)類型的錯誤,更容易地調試問題。雖然總可以在測試過程中發(fā)現(xiàn)大多數(shù)這類問題,但是檢查模式有利于縮小它們的范圍。
使用類型
如何使用類型取決于你。如果你討厭類型,你不必使用它們。你不會得到任何類型的警告,你可以用你在其它動態(tài)語言中感到舒適的方式開發(fā)。然而你依然可以從類型中獲益,因為Dart的庫中有類型簽名,它們告訴你它們期望什么和返回什么。如果你在檢查模式中運行,傳遞了錯誤的參數(shù)給類庫,檢查模式將在你犯錯的地方發(fā)現(xiàn)它們。
如果你喜歡類型,你可以在任何地方使用它們,很像是靜態(tài)類型語言。然而,即使那樣你也不會獲得同樣級別的靜態(tài)檢查。Dart的規(guī)則比較寬松。我們期望為這些人提供額外的工具來更加嚴格地解釋類型標注。
我們不建議太極端地使用方式。應該在有意義的地方使用類型。你能做的最有價值的事情是添加類型到你類庫中公有成員的聲明上。接下來,再對私有成員做同樣的事。即使沒有別人需要維護代碼,如果你離開代碼幾周或幾個月后又回來,你會發(fā)現(xiàn)它是有幫助的。在這兩種情況下,你不一定要在方法體或函數(shù)體中添加類型。庫的使用者從類型簽名中獲得價值,即使它們不是100%準確。
在函數(shù)體中,并不總是需要標注聲明。有時代碼足夠簡單,真的無所謂,類型反而可能會造成混亂。
通常,你應該設計代碼,別讓考慮類型影響你。在某些情況下,有幾種替代的設計,其中的某種比其它更適合使用類型。例如,你可以用傳遞函數(shù)替代它,而不是用傳遞字符串表示要調用的函數(shù)名,這樣代碼會更有效并更容易檢查類型。Dart 同樣防止以其他方式無端地使用反射(reflection)。然而,當真正有意義時,你應該毫不猶豫地使用反射。
原文:http://han.guokai.blog.163.com/blog/static/136718271201110194459405/
【編輯推薦】