---
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');
}
```
> 
:::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) {
}
```
> 
:::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` 一起使用**
> 
:::
## Dart 數據類型
* Dart 是屬於 **強類型語言**,並且 **==沒有基礎數據類型== (Java 有 8 大基礎數據),`int`、`long`...都是實體的物件**,下圖是 dart 語言的 int 類
> 
* 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
```
> 
:::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);
}
```
> 
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");
}
```
> 
:::danger
* Dart String 字串長度的陷阱(關鍵字: 代碼點 & 代碼單元)
**代碼單元**:String#length 只是大部分是「字符長度」,但並不完全代表代碼單元數量… 假設字符串是以 UTF-16 編碼存儲的(Dart、Java 就是如此),一個代碼單元是 16 位的整數
**代碼點**:Unicode 編碼中每個字符對應的一個整數值,也就是儘管超過程式語言的代碼編制長度,也會算成是一個整數值
範例如下
```java=
void main() {
String testLen = "\u5566\u7788";
print("${testLen.length}");
}
```
> 
:::
### 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);
}
```
> 
:::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;
}
```
> 
:::
### 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']}");
}
```
> 
### 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));
}
```
> 
:::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)數量
> 
:::
### 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}");
}
```
> 
## 操作符
操作符同樣是比較與 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);
}
```
> 
### 條件表達式 `??`
* 除了基礎的三元表達式 `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);
}
```
> 
### 安全操作符 `.?`
* 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);
}
```
> 
### 級聯操作符 `..`
* 操作符號是 `..`,在同一個物件上可以連續調用該物件的函數、成員,**可以避免創建臨時變量 (可以使用 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)');
}
}
```
> 
### 解構元素 `...`
* Dart 可以透過 `...` 符號來「**解構列表**」的元素,將其轉為個別的元素,範例如下
```java=
void main() {
var message = ['Hello', 'World', '123'];
print(['你好', '世界', '456', ...message]);
}
```
> 
## Appendix & FAQ
:::info
:::
###### tags: `Flutter` `Dart`