--- title: 'Dart 語言:宣告、數據類型、操作符' disqus: kyleAlien --- Dart 語言:宣告、數據類型、操作符 === ## Overview of Content :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南**](https://devtechascendancy.com/comprehensive-guide-dart-lang/) ::: [TOC] ## 認識 Dart 語言 Dart 是單線程(單執行序),GC 回收使用 [**CMS 機制**](https://hackmd.io/KPXIegnbSA-6bFYE6cIMGw?view#%E8%80%81%E5%B9%B4%E4%BB%A3---CMS),也就是使用分代回收機制 (新生代、老年代) 以下會以 Java 語言進行比較,提及與 Java 的差異,Dart 與 Groovy 也有部分相像 ```groovy= // 先來個 Hello World 起手 void main() { print('Hello World'); } ``` > ![](https://i.imgur.com/BkkBkKi.png) :::info * 使用指令運行,該指令存在於 Flutter SDK 的資料夾內,將該路徑加入環境變數,重啟 AS 就可以在終端機下達指令 `dart xxx.dart` > Dart 指令路徑: `${flutter_sdk}\bin\cache\dart-sdk\bin` ::: ### Dart 語言、程式特性 * **Dart 語言的特性**: 1. 所有的東西都是物件,甚至包含 Java 的基礎類型 (eg. int) 2. Dart 是弱類型語言,不必指定數據類型 3. 指定數據類型、const 常量可以提高運行速度 4. **Dart 是 AOT (`Ahead Of Time`) 編譯**,在安裝前會先進行預編譯,編譯成本地代碼 (Dart to java or object-c) 5. **Dart 也可以 JIN (`Just In Time`) 編譯** 6. 以 60fps 運行的流暢動畫和轉場,可以在沒有鎖的時候進行物件分配 & 垃圾回收,**Dart 避免了線程的搶佔 (線程的特性與 Java 不同)** 7. **代碼即是布局**,不須另外使用另一個語言建構 (Android 使用 Xml 建構) * **Dart 程式特性**: 1. 統一的程序入口 `main()`,這一點如同 C、Java 語言 2. 並沒有 `public`、`protected`、`private` 的參數訪問限制概念,但 **私有可以變量可以使用下滑線 ( `_` ) 來表示** ```java= int hello; int _hello; // 私有 ``` 3. 支持異步處理 `anync`/ `await` 4. **所有函數都有函數返回值! 如果沒有返回值默認返回為 null** ### Dart 常用庫 * Dart 中常見的第三方庫有如下表所示: | 庫名 | 描述 | | - | - | | `dart:async` | 異步編成支持,提供 Future & Stream | | `dart:collection` | **針對 `dart:core` 提供更多的集合支持** | | `dart:convert` | 不同類型(`JSON`、`UTF-8`) 的字符轉換 | | `dart:core` | 基礎核心庫 | | `dart:html` | 網頁開發用到的庫 | | `dart:io` | 文件讀取的 IO | | `dart:math` | 函數、隨機算法等等 | | `dart:svg` | SVG 動畫 | ## Dart 變數宣告 Dart 變數有幾個特色 1. Dart 可使用「中文」命名 (但不建議) 2. Dart 語言中所有的東西都是物件,並沒有基礎類型的概念 ### 宣告變數:Object / var / dynamic * 一般我們在宣告類別時必須寫清楚類型,而 `dart` 可以直接使用 `var` 作為類型宣告 (Dart 的編碼風格),在 `Java 10`、`kotlin` 中也有 `var` | 宣告方式 | 解釋 | Example | | -------- | -------- | -------- | | `類型宣告` | 如同一般 Java 宣告,必須寫清楚是哪種類型的數據(之後會一一介紹 Dart 的類型) | Person person = new Person(); | | `Object` | 任意類型 | Object person = new Person(); | | `var` | **自動推導類型 (若是不清楚就要考慮少用),並且==推倒後就不可以在更改類型==** | var person = new Person(); | | `dynamic` | **運行時確定數據類型**,相對來說會慢一些,在沒有初始化時默認為 null | dynamic person = new Person(); | * **其中較為特別的是 `dynamic` 類型**: **它不在編譯期間定義,它在「運行期間才決定類型」**;以下是不同宣告方式的範例 ```java= void main() { print('Hello World'); // 使用 Object 宣告 Object o1 = 1; o1 = "1"; // 使用 var 宣告 var v1 = 1; //v1 = "1"; // v1 已定型不可再改(自動推導) // 使用 var 宣告 var v2; v2 = 1; v2 = "1"; // 兩個完全不同物件,v2 類似於 const 指標 // 使用 dynamic 宣告 dynamic d1 = 1; // 與 Object 差異是 dynamic 是在運行時確定 d1 = "1"; Object name1 = 'Alien'; var name2 = 'Pan'; dynamic name3 = 'Kyle'; print("$name1, $name2, $name3"); } // 函數入參,未聲明就是動態類型 dynamic,如下 void testPrint(a) { } void testPrint2(dynamic a) { } ``` > ![](https://i.imgur.com/YnUk6nT.png) :::info * **不同語言的 `var` 所造成的差異**(編譯期間) * 在 JS 中也有 `var` 這種宣告方式,但 **JS 是「動態類型語言」**(python 也是),可在賦予值後重新設定不同的類型 ```javascript= // JS var k = "Hello JS" k = 3; // okay ``` * Dart 則是「**靜態類型語言**」,在賦值時就被定義(自動推倒) ```java= // Dart 在賦值時就被定義類型 var k = "Hello Dart"; k = 10; // Err !!! var j; // 尚未被定義就是 null 也就是 object j = "Hello Dart"; j = 20; // Okay !!! ``` ::: ### final & const 描述 * Java 中我們可以看到 `final`,`C/C++` 中可以看到 const 描述,其實被這兩者描述過後,該變數都會變為不可再修改的值 (轉為常數),而 **final、const 仍然存在著差異** | 描述 | 作用時 | 範例 | | -------- | -------- | -------- | | final | **==運行期間== 確定** | final String FINAL_NAME = "Apple" | | const | **==編譯期間== 確定** | const String CONST_NAME = "Banana" | ```java= void main() { final String FINAL_NAME = "Apple"; const String CONST_NAME = "Banana"; //const String TEMP = FINAL_NAME; // Err: 運行時確定 final String TEMP_2 = CONST_NAME; // okay } class MyClass { final String TAG_1 = "MyClass"; // const String TAG_2 = "MyClass"; // Err: 如同 C++ 的 const 概念使用 // const 定義在類中,必須使用 static 描述 -> static const static const String TAG_3 = "MyClass"; } ``` :::warning * 運行時 `final` 不可以賦予給編譯時常量 `const`,因為 `const` 必須編譯期就確定,而相反操作過來就可以,因為 final 運行時再確認即可 > 所以被 `const` 描述的稱之為常量,它的效能往往會比被 `final` 描述的更好,因為它在編譯過後就被確定類型 * **`const` & `final` 不可以跟 `var` 一起使用** > ![](https://i.imgur.com/TsXLZAM.png) ::: ## Dart 數據類型 * Dart 是屬於 **強類型語言**,並且 **==沒有基礎數據類型== (Java 有 8 大基礎數據),`int`、`long`...都是實體的物件**,下圖是 dart 語言的 int 類 > ![](https://i.imgur.com/CEpBKIu.png) * Dart 有內置的七種類型 (**7 個仍是類**) | 內置類 | 介紹 & Java 比較 | 範例 | | -------- | -------- | -------- | | `num` | 分為整數、浮點數 | int、double | | `String` | **儲存大小為 UTF-16,可用單、雙引號,並可嵌套使用 (省去跳脫字元 `\`)** | String str = 'A' or "B" | | `bool` | 如同 Java 使用 | bool y = false; | | `List<E>` | 不使用 ArrayList,可直接使用 index 取值,增加數值使用 add | List list = new List(1); var list = List(1); | | `Map<K, V>` | 一樣是 Key & Value 相對,取值方式可以使用中括號 `[]`,**中括號內是放置 key 並非 index** | Map map = ['A': 1, 'B': 2] | | `Runes` | Unicode 字符,**將 32 位的 Unicode 編碼轉為字符串** | var a = '\u{1f9f99}' | | `Symbol` | 可以看做 C/C++ 的宏,編譯時的常量 | Symbol #Hello | ### num 類型 * **num 所有數的父類**,有 `int` & `double` 兩個子類 (但是並無 `short`、`float`、`long`... 其他數據類型) ```mermaid graph TB num --> int num --> double ``` > ![](https://i.imgur.com/OsH4FRJ.png) :::info * Dart 與 Java 語言對於數字類型的看待、差異 1. Dart 只有 `int`(整) & `double`(浮點數),而沒有 float、short... 2. 數字類型佔用字節數(`byte`)的差異:int 在 java 中占用 4 個字節,dart 則會依照平台而改變,屬於動態改變自節數 ```java= // Dart int i = 2; print('int cost byte: ${i.bitLength}'); // 可看當前需要多少個 bit ``` ::: ### String 類型:String 陷阱,代碼點、代碼單元 * String 類對於高級語言來說是個大家都通認的類型,這邊就不過多介紹,主要來比較一下 Dart 與 Java 語言對於 String 類型不同的地方 1. **基礎差異**:總體來說比起 Java 使用起來更加的自由 * Dart 對於字串的解釋方式可以使用「**字符串插值**」(`String Interpolation`),不再像是 Java 只能透過 `+` 號來串接字串 * 可以混用單、雙引號來表達字串 * 使用 `'''`、`"""` 可以包裹換行的字串 * 並且可以透過在字串前添加 `r` 來告訴 Dart 該字串是「元數據」(不需要解譯跳脫字符等等) ```java= void testString() { String s1 = "Apple"; // 使用 ${} 可直接引用其他字串,若是單個詞可省略大括號 {} String s2 = "This is $s1"; print(s2); String s2_1 = 'This is $s1'; // 單、雙引號都可引用,groovy 單引號則不行 print(s2_1); // 傳統跳脫字元 `\`,並且可以如同 Java 使用 `+` 號 String s3 = "This is \"${s1 + "!"}\" "; print(s3); // 嵌套使用 String s4 = 'This is "${s1 + "!"}" '; print(s4); // 三個單引號 or 雙引號可以使用換行字串 String s5 = """ Hello World EveryBody """; print(s5); String s6 = ''' Apple Banana Car '''; print(s6); // 原始字 raw -> r"" String s7 = r"\n"; // 跳脫字元不起作用 String s8 = "\n"; print(s7); String rType = r"\n"; // r 可輸出元形 "\\n" print(rType); } ``` > ![](https://i.imgur.com/B7KJx8d.png) 2. **特殊字元差異** Java 每個 String 大小不可超過 2 個 byte,若超過則必須分開(這是因為 Java 目前採用 `UCS-2` 編碼) ```java= // Java void main() { String str = "\uA388\uA388"; println(str); } ``` Dart 則可以超過 2 byte,超過只需使用 `{}` 即可 (若未超過則不用) ```java= // Dart void main() { var clapping = '\u{1f44f}'; print("$clapping"); var test = '\u1f44f'; // 超過未使用 其實就是 '\u1f44' + 字串 f print("$test"); var test2 = '\uf44f'; // 未超過 print("$test2"); } ``` > ![](https://i.imgur.com/M4fsDmC.png) :::danger * Dart String 字串長度的陷阱(關鍵字: 代碼點 & 代碼單元) **代碼單元**:String#length 只是大部分是「字符長度」,但並不完全代表代碼單元數量… 假設字符串是以 UTF-16 編碼存儲的(Dart、Java 就是如此),一個代碼單元是 16 位的整數 **代碼點**:Unicode 編碼中每個字符對應的一個整數值,也就是儘管超過程式語言的代碼編制長度,也會算成是一個整數值 範例如下 ```java= void main() { String testLen = "\u5566\u7788"; print("${testLen.length}"); } ``` > ![](https://i.imgur.com/e7G2zd8.png) ::: ### bool 類型 * Dart 的 bool 類型與 Java 並無太大的差異(`Java use boolean`),同樣使用 true/false… 這邊只需要特別注意 **對於 Dart 來說 bool 仍是物件**,而對於 Java 來講它只是基礎類型 ```java= void testBool() { bool flag = false; String msg = flag ? "Hello" : "World"; print(msg); } ``` ### List 類型 * Dart 的 List 如同 Array 可以使用下標(`index`)取值 **以下我們會特別加入 `const` 的使用,並與 C/C++ 比較 (有相似之處)**; * `const` 如果放在宣告物件的描述,會讓該物件的引用、內容皆不可修改 * 但如果 `const` 只放在建立物件實例的描述,則該物件的引用可以修改,而物件的實例內容則不能修改! ```java= void testList() { // 創建方法 1 List list1 = new List(); // 創建方法 2,省略 new List list2 = List(); // 創建方法 3 List list3 = [0,1,2,3]; // 下標 index 取值 print("list3[1]: ${list3[1]}"); list3.add(44); print("list3[4]: ${list3[4]}"); // 1. const 修飾引用 & 內容 const List list4 = [1,3,5,7,9]; // list4.add(123); Err: 編譯期間會錯誤 // list4[3] = 10; Err: 內容也會被修飾 // list4 = list3; Err: 引用指標也會被修飾 // 2. const 修飾內容 List list5 = const [9,7,5,3,1]; print(list5); list5 = list4; // 指標未被修飾,可以修改引用指標 // list5[0] = 333; Err: const 修飾內容 // list5.add(55); Err: const 修飾內容 print(list5); } ``` > ![](https://i.imgur.com/wdfaZ4t.png) :::success * 我們可以使用 C/C++ 的 const 修飾指標來表達相同的功效,這樣會看的更加清晰(當然這是對於學過 C/C++ 的人來說會看出兩者個相同之處) ```cpp= #include <stdio.h> int main() { int a = 10; int aa = 20; int * b = &a; // 一般指標 printf("b = %i\n", *b); const int * c = &a; // const 修飾內容 // *c = 20; Err 內容不可改 printf("c = %i\n", *c); c = &aa; // 指標可改 printf("change, c = %i\n", *c); int * const d = &a; // const 修飾指標 *d = 20; // 內容可改 printf("d = %i\n", *d); // d = &aa; // 指標不可改 printf("change, d = %i\n", *d); const int* const e = &a; // const 修飾指標 & 內容 printf("e = %i\n", *e); return 0; } ``` > ![](https://i.imgur.com/zwO8nZr.png) ::: ### Map 類型 * Map 使用跟 List 相似,也可以使用 `const` 修改(這裡就不特別說明 const 描述的位置造成的差異),特點是 **可以使用 `[]` 自動拓展**,不需要像是 Java 使用 `put()` 函數做添加 ```java= void testMap() { // Map 創建方法 1 Map map1 = new Map(); // Map 創建方法 2 Map map2 = Map(); // Map 創建方法 3 Map map3 = {'A': 1, 'B': 2, 'C': 3}; print("map3[A]: ${map3['A']}"); print("map3[B]: ${map3['B']}"); print("map3[C]: ${map3['C']}"); const Map map5 = {'A': 1}; // map5['A'] = 3; var map6 = const {'A': 1}; // 直接"自動"拓展 !!! map3['D'] = 4; print("map3[D]: ${map3['D']}"); } ``` > ![](https://i.imgur.com/UPB6bf7.png) ### Runes 類型 * Dart 的 Runes 類型是個特別的類型: Dart 特別分出兩個單詞:**代碼點**、**代碼單元** | 單詞 | 說明 | | - | - | | 代碼點 `codePointCount` | 可以取出目前輸入數的數量,**它不會按照字元的 Byte 數量來解釋字串長度** | | 代碼單元 `codeUnits` | 經過編碼後才返回的 Byte 數量 | ```java= void testRunes() { // \u{1f44f} 是超過 2Byte 的數據 String str = "\u{1f44f}"; print("String: $str, ${str.length}"); var r = "\u{1f44f}"; print("var: $r"); var clapping = '\u{1f44f}'; // 超過 unsigned 2 Byte 需要使用大括號 {} print(clapping); //👏 // 16位代碼單元 print(clapping.codeUnits); // 超過 16 位[55357, 56399] // 獲得完整的 32 位代碼單元 print(clapping.runes.toList()); //輸出 10 進位 [128079] // 取得 codePointCount print("Code point count: ${clapping.runes.length}"); //fromCharCode 根據字節碼(Byte)創建字符串 print( String.fromCharCode(128079)); print( String.fromCharCodes(clapping.runes)); print( String.fromCharCodes([55357, 56399])); print( String.fromCharCode(0x1f44f)); Runes input = new Runes( '\u2665 \u{1f605} \u{1f60e} \u{1f47b} \u{1f596} \u{1f44d}'); print("Runes: " + String.fromCharCodes(input)); } ``` > ![image](https://hackmd.io/_uploads/HkXO37w7C.png) :::warning * Java 如何儲存超過 2Byte 的字串? 以往 Java String 類型無法放置超過 2Byte 的數據,如果要儲存就要使用 `\u` 開頭的方式來表達,範例如下 ```java= public static void main(String[] args) { String over2Byte = "\uD83D\uDC4F"; System.out.println("length: " + over2Byte.length()); } ``` 而 `String#length()` 方法是在 `UCS-2`(`Unicode` 的其中一種)編碼下所返回的單元(Byte)數量 > ![image](https://hackmd.io/_uploads/H1C0omvm0.png) ::: ### Symbol 類型 * Symbol 標示符號,其概念類似於 C/C++ 的 `define` 宏概念,**它可以用來定義常數**;Dart 語言在使用 Symbol 類型時要在常數前加上 `#` 定義;範例如下… ```java= void testSymbol() { Symbol symbol1 = #Hello; switch(symbol1) { case #Hello: print(symbol1); break; case #World: print("World"); break; } Symbol symbol2 = new Symbol("Book"); print("#Book == symbol2: ${#Book == symbol2}"); } ``` > ![](https://i.imgur.com/mGANEMr.png) ## 操作符 操作符同樣是比較與 Java 相異、沒有的部分,至於與 Java 相同的操作符就不另外介紹 ### 類型判斷型 * Dart 的類型判斷可以使用下表的幾個關鍵字 | 關鍵字 | 解釋 & 比較 | Java 使用 | Dart 使用 | | - | - | - | - | | `as` | 類型轉換,Java 使用括號強制轉型 | int b = (int)a; | int b = a as int; | | `is` | 類型判定,Java 使用 instanceof 判斷 | if(a instanceof Person) | if(a is instanceof) | | `is!` | 反向判定,注意 `!` ++使用在後面++ | if(!(a instanceof Person)) | if(a is! instanceof) | ```java= void testDecide() { int a = 10; double b = 11.11; // as // int & double 無法互轉 //print("b as int: ${b as int}"); // is if(a is int) { print("a is int"); } // is! if(b is! int) { print("b is not int"); } } ``` ### 賦值操作符 `??=` * 在 Dart 中以下這些操作符號 `=`、`+=`、`-=`、`\=`、`*=` 都可以使用,而 **有一個較特別的是 `??=` 操作符,它會先判斷該物件是否為 null,若為 null 則賦值** 以 Java、Dart 兩種語言來比較: ```java= // 使用 Dart 表達… 若 b == null 則將 value 賦予 b,若有值則不改變 b ??= value; // 使用 Java 表達… 等同於上 ??= 操作符 if(b == null) { b = value; } ``` 操作符 `??=` 使用範例如下: ```java= void printMessage(String? msg) { msg ??= "No message"; print(msg); } void main() { printMessage('Hello world'); printMessage(null); } ``` > ![image](https://hackmd.io/_uploads/SkbjVMCtA.png) ### 條件表達式 `??` * 除了基礎的三元表達式 `condition ? todo1 : todo2`,Dart 還多了一種 `??` 表達,它會先執行第一個敘述,若第一個敘述反為 null 則執行第二個敘述 以 Java、Dart 兩種語言來比較: ```java= // 使用 Dart 表達… 先執行 expr1 如果為 null ,則換執行 expr2 返回 expr1 ?? expr2 // 使用 Java 表達… int function() { if(expr1 == null) { return expr2 } return expr1; } ``` 操作符 `??` 使用範例如下: ```java= void printMessage2(String? msg) { print(msg ?? "No message"); } void main() { printMessage2('Hello world'); printMessage2(null); } ``` > ![image](https://hackmd.io/_uploads/H1l6VMRtA.png) ### 安全操作符 `.?` * Dart 提供了安全操作符 `.?`,**若物件為 null 則直接返回 null,並不會往下執行函數** 以 Java、Dart 兩種語言來比較: ```java= // 使用 Dart 表達… 如果 expr1 不為 null,則執行 method 方法 expr1?.method(); // 使用 Java 表達… 功能同上 if(expr1 != null) { expr1.method(); } ``` 操作符 `.?` 使用範例如下: ```java= void printMessage3(String? msg) { final length = msg?.length; print('message len: $length'); } void main() { printMessage3('using operation `.?`'); printMessage3(null); } ``` > ![image](https://hackmd.io/_uploads/Bke88fCFC.png) ### 級聯操作符 `..` * 操作符號是 `..`,在同一個物件上可以連續調用該物件的函數、成員,**可以避免創建臨時變量 (可以使用 C++ 理解,必須透過複製建構函數創造臨時物件進行賦值)** 這種操作尤其可以使用在「[**建造者 builder 模式**](https://devtechascendancy.com/object-oriented_design_builder_dialog/)」 ```java= void testLink() { MyBuilder builder = MyBuilder(); // 可省略 new 關鍵字 builder..setName("Alien")..setId(123)..setPhone('456')..show(); } class MyBuilder { String? _name; int? _id; String? _phone; void setName(String name) { _name = name; // 一般 Java 製作 builder 模式就必須返回此類 this 物件 } void setId(int id) { // 使用級聯則不用 _id = id; } void setPhone(String phone) { // 可接續使用 _phone = phone; } void show() { print('name($_name}), id($_id), phone($_phone)'); } } ``` > ![image](https://hackmd.io/_uploads/Hyc0wGCKA.png) ### 解構元素 `...` * Dart 可以透過 `...` 符號來「**解構列表**」的元素,將其轉為個別的元素,範例如下 ```java= void main() { var message = ['Hello', 'World', '123']; print(['你好', '世界', '456', ...message]); } ``` > ![image](https://hackmd.io/_uploads/rkk-dG0tR.png) ## Appendix & FAQ :::info ::: ###### tags: `Flutter` `Dart`