Overview of Content
在這篇文章中,我們將深入探討 Dart 語言的各個方面,首先,我們會帶領您認識 Dart 語言,了解其程式特性和常用庫
接著,會詳細介紹 Dart 變數宣告的方法,包括 Object
、var
、dynamic
以及 final
和 const
的使用
隨後,我們將深入分析 Dart 的七種數據類型,揭示其中的陷阱和特殊用法,如 String 類型的代碼點與代碼單元(這些知識對避免常見錯誤至關重要),以及 Runes 類型的細節
最後,我們會講解各種操作符,從類型判斷到安全操作符,幫助您更好地掌握 Dart 語言
寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀
個人程式分享時比較注重「縮排」,所以可能不適合手機的排版閱讀,建議切換至「電腦版」、「平板版」視窗看
認識 Dart 語言
Dart 是單線程(單執行序),GC 回收使用 CMS 機制,也就是使用分代回收機制 (新生代、老年代)
以下會以 Java 語言進行比較,提及與 Java 的差異,Dart 與 Groovy 也有部分相像
// 先來個 Hello World 起手
void main() {
print('Hello World');
}
● 使用指令運行,該指令存在於 Flutter SDK 的資料夾內,將該路徑加入環境變數,重啟 Android Studio 就可以在終端機下達指令
dart xxx.dart
Dart 指令路徑:
${flutter_sdk}\bin\cache\dart-sdk\bin
Dart 語言、程式特性
● Dart 語言的特性:
A. 所有的東西都是物件,甚至包含 Java 的基礎類型 (eg. int)
B. Dart 是弱類型語言,不必指定數據類型
C. 指定數據類型、const 常量可以提高運行速度
D. Dart 是 AOT (Ahead Of Time
) 編譯,在安裝前會先進行預編譯,編譯成本地代碼 (Dart to java or object-c)
E. Dart 也可以 JIN (Just In Time
) 編譯
F. 以 60fps 運行的流暢動畫和轉場,可以在沒有鎖的時候進行物件分配 & 垃圾回收,Dart 避免了線程的搶佔 (線程的特性與 Java 不同)
G. 代碼即是布局,不須另外使用另一個語言建構 (Android 使用 Xml 建構)
● Dart 程式特性:
A. 統一的程序入口 main()
,這一點如同 C、Java 語言
B. 並沒有 public
、protected
、private
的參數訪問限制概念,但 私有可以變量可以使用下滑線 ( _
) 來表示
int hello;
int _hello; // 私有
C. 支持異步處理 anync
/ await
D. 所有函數都有函數返回值! 如果沒有返回值默認返回為 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 變數有幾個特色
A. Dart 可使用「中文」命名 (但不建議)
B. 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
類型: 它不在編譯期間定義,它在「運行期間才決定類型」;以下是不同宣告方式的範例
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) {
}
● 不同語言的
var
所造成的差異(編譯期間)● 在 JS 中也有
var
這種宣告方式,但 JS 是「動態類型語言」(python 也是),可在賦予值後重新設定不同的類型// JS var k = "Hello JS" k = 3; // okay
● Dart 則是「靜態類型語言」,在賦值時就被定義(自動推倒)
// 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" |
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";
}
● 運行時
final
不可以賦予給編譯時常量const
,因為const
必須編譯期就確定,而相反操作過來就可以,因為 final 運行時再確認即可所以被
const
描述的稱之為常量,它的效能往往會比被final
描述的更好,因為它在編譯過後就被確定類型●
const
&final
不可以跟var
一起使用
Dart 數據類型:7 個類型
● 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
... 其他數據類型)
● Dart 與 Java 語言對於數字類型的看待、差異
A. Dart 只有
int
(整) &double
(浮點數),而沒有 float、short...B. 數字類型佔用字節數(
byte
)的差異:int 在 java 中占用 4 個字節,dart 則會依照平台而改變,屬於動態改變自節數// Dart int i = 2; print('int cost byte: ${i.bitLength}'); // 可看當前需要多少個 bit
String 類型:String 陷阱 | 代碼點、代碼單元
● String 類對於高級語言來說是個大家都通認的類型,這邊就不過多介紹,主要來比較一下 Dart 與 Java 語言對於 String 類型不同的地方
A. 基礎差異:總體來說比起 Java 使用起來更加的自由
● Dart 對於字串的解釋方式可以使用「字符串插值」(String Interpolation
),不再像是 Java 只能透過 +
號來串接字串
● 可以混用單、雙引號來表達字串
● 使用 '''
、"""
可以包裹換行的字串
● 並且可以透過在字串前添加 r
來告訴 Dart 該字串是「元數據」(不需要解譯跳脫字符等等)
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);
}
B. 特殊字元差異
Java 每個 String 大小不可超過 2 個 byte,若超過則必須分開(這是因為 Java 目前採用 UCS-2
編碼)
// Java
void main() {
String str = "\uA388\uA388";
println(str);
}
Dart 則可以超過 2 byte,超過只需使用 {}
即可 (若未超過則不用)
// Dart
void main() {
var clapping = '\u{1f44f}';
print("$clapping");
var test = '\u1f44f'; // 超過未使用 其實就是 '\u1f44' + 字串 f
print("$test");
var test2 = '\uf44f'; // 未超過
print("$test2");
}
● Dart String 字串長度的陷阱(關鍵字: 代碼點 & 代碼單元)
代碼單元:String#
length
只是大部分是「字符長度」,但並不完全代表代碼單元數量… 假設字符串是以 UTF-16 編碼存儲的(Dart、Java 就是如此),一個代碼單元是 16 位的整數代碼點:Unicode 編碼中每個字符對應的一個整數值,也就是儘管超過程式語言的代碼編制長度,也會算成是一個整數值
範例如下
void main() { String testLen = "\u5566\u7788"; print("${testLen.length}"); }
bool 類型
● Dart 的 bool 類型與 Java 並無太大的差異(Java use boolean
),同樣使用 true/false… 這邊只需要特別注意 對於 Dart 來說 bool 仍是物件,而對於 Java 來講它只是基礎類型
void testBool() {
bool flag = false;
String msg = flag ? "Hello" : "World";
print(msg);
}
List 類型
● Dart 的 List 如同 Array 可以使用下標(index
)取值
以下我們會特別加入 const
的使用,並與 C/C++ 比較 (有相似之處);
● const
如果放在宣告物件的描述,會讓該物件的引用、內容皆不可修改
● 但如果 const
只放在建立物件實例的描述,則該物件的引用可以修改,而物件的實例內容則不能修改!
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);
}
● 我們可以使用 C/C++ 的 const 修飾指標來表達相同的功效,這樣會看的更加清晰(當然這是對於學過 C/C++ 的人來說會看出兩者個相同之處)
#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()
函數做添加
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 數量 |
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));
}
● Java 如何儲存超過 2Byte 的字串?
以往 Java String 類型無法放置超過 2Byte 的數據,如果要儲存就要使用
\u
開頭的方式來表達,範例如下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 類型時要在常數前加上 #
定義;範例如下…
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}");
}
Dart 操作符
操作符同樣是比較與 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) |
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 兩種語言來比較:
// 使用 Dart 表達… 若 b == null 則將 value 賦予 b,若有值則不改變
b ??= value;
// 使用 Java 表達… 等同於上 ??= 操作符
if(b == null) {
b = value;
}
操作符 ??=
使用範例如下:
void printMessage(String? msg) {
msg ??= "No message";
print(msg);
}
void main() {
printMessage('Hello world');
printMessage(null);
}
條件表達式 ??
● 除了基礎的三元表達式 condition ? todo1 : todo2
,Dart 還多了一種 ??
表達,它會先執行第一個敘述,若第一個敘述反為 null 則執行第二個敘述
以 Java、Dart 兩種語言來比較:
// 使用 Dart 表達… 先執行 expr1 如果為 null ,則換執行 expr2 返回
expr1 ?? expr2
// 使用 Java 表達…
int function() {
if(expr1 == null) {
return expr2
}
return expr1;
}
操作符 ??
使用範例如下:
void printMessage2(String? msg) {
print(msg ?? "No message");
}
void main() {
printMessage2('Hello world');
printMessage2(null);
}
安全操作符 .?
● Dart 提供了安全操作符 .?
,若物件為 null 則直接返回 null,並不會往下執行函數
以 Java、Dart 兩種語言來比較:
// 使用 Dart 表達… 如果 expr1 不為 null,則執行 method 方法
expr1?.method();
// 使用 Java 表達… 功能同上
if(expr1 != null) {
expr1.method();
}
操作符 .?
使用範例如下:
void printMessage3(String? msg) {
final length = msg?.length;
print('message len: $length');
}
void main() {
printMessage3('using operation `.?`');
printMessage3(null);
}
級聯操作符 ..
● 操作符號是 ..
,在同一個物件上可以連續調用該物件的函數、成員,可以避免創建臨時變量 (可以使用 C++ 理解,必須透過複製建構函數創造臨時物件進行賦值)
這種操作尤其可以使用在「建造者 builder 模式」
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 可以透過 ...
符號來「解構列表」的元素,將其轉為個別的元素,範例如下
void main() {
var message = ['Hello', 'World', '123'];
print(['你好', '世界', '456', ...message]);
}
更多的 Flutter/Dart 語言相關文章
了解 Flutter 如何在跨平台開發中佔據重要地位,掌握快速上手的技巧與項目建置流程,開啟你的跨平台開發之旅!
探索跨平台與 Flutter 技術的未來:從認識到 Flutter 專案建置 | 3 種跨平台
Dart 語言基礎
● 探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南
快速掌握 Dart 語言的核心概念,包括變數宣告、數據類型及操作符,為 Flutter 開發奠定扎實基礎。
● Dart 函數與方法、異常處理、引用庫 | Java 比較
深入了解 Dart 的函數與異常處理特性,並與 Java 的處理方式進行比較,幫助你跨語言切換更加順暢。
● 深入解析 Dart 語言:命名慣例、類特性、建構函數與抽象特性
學習 Dart 類的設計邏輯及命名慣例,深入探索抽象類與 Mixin 的強大應用場景。
● 深入探索 Dart 的併發與異步處理:從 Isolate 到 Event Loop 的全面指南 | Future、Stream
徹底搞懂 Dart 的併發與異步處理,掌握 Isolate 與 Event Loop 的運行機制,助你提升應用效能!
深入 Flutter 框架
● 深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫
從常見問題到自定義解決方案,學會如何利用 Navigator 實現路由跳轉與流暢動畫效果。
● 深入理解 Flutter 中的數據共享:從普遍方案到 InheritedWidget | 3 種方案
探討數據共享的最佳實踐,了解 InheritedWidget 等三種主要方案,幫助你優化應用結構。
● 深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南
拆解 Flutter 的內部結構,全面了解 Widget、Element 和 RenderObject 之間的關係,提升你的 Flutter 開發技能!