# Dart基礎
[TOC]
## 壹、目的
Dart語言指南
以下為Dart語言的簡單介紹,資料來源皆為網上取得且有附來源
因爲幾年前所編,當時作者我並不太會程式,且dart迭代很快,故有些資料可能已經不正確,如有誤可寄信給我或者留言,謝謝!
也歡迎大家一起學習
## 貳、資源
### 一、Dart 文檔
[官方](https://dart.dev/guides/language/language-tour#a-basic-dart-program)
https://www.itying.com/dart/dart01.html
https://dart.cn/guides
https://codingdict.com/article/21977
https://www.tutorialspoint.com/dart_programming/dart_programming_enumeration.htm
https://github.com/ducafecat/dart-learn
https://www.yiibai.com/dart
https://api.flutter.dev/index.html
https://ithelp.ithome.com.tw/users/20112906/ironman/3950?page=1
https://ithelp.ithome.com.tw/users/20129264/ironman/3048
### 二、Flutter 文檔
https://flutter.cn/docs
https://doc.flutterchina.club/docs/
https://docs.flutter.dev/
https://www.w3cschool.cn/evilg/
https://api.flutter.dev/index.html
### 三、Flutter 資源
#### (一)Flutter 社區中文資源
https://flutter-io.cn/
#### (二)Flutter codelabs
https://codelabs.flutter-io.cn/
#### (三)Flutter clone
[Flutter Facebook Clone Responsive UI Tutorial | Web and Mobile](https://youtu.be/HvLb5gdUfDE)
## 參、Install
### 一、安裝教學
[Flutter官方教學文檔](https://docs.flutter.dev/get-started/install)
[YT影片教學](https://www.youtube.com/watch?v=tun0HUHaDuE&ab_channel=TonyDavid)
### 二、常見安裝問題
#### (一)Android sdkmanager tool not found
解決方法:
Andrid Studio=>Settings=>Appearance and behavior=>System setting
=>Android sdk=>Sdk tools=>Android sdk command-line tools
[Android sdkmanager tool not found](https://stackoverflow.com/questions/52256569/android-sdkmanager-tool-not-found)
#### (二)Visual Studio not installed
解決方法:
安裝vscode時記得要安裝Desktop development with C++包
[Visual Studio not installed](https://stackoverflow.com/questions/71080518/visual-studio-not-installed-this-is-necessary-for-windows-development?fbclid=IwAR0d-07ueRhxnUyo0RJDEYV5cURVct9u8-Xu4FSvuUGGU8telNzvWSxToCI)
#### (三)Android toolchain Unable to locate Android SDK
解決方法:
1.檢查Android SDK是否有下載,[官網](https://developer.android.com/)
2.檢查Android SDK路徑是否正確,不正確則更新路徑
3.更新Android SDK
4.下載JDK
[Showing error in the "flutter doctor" about Android toolchain](https://stackoverflow.com/questions/60473230/showing-error-in-the-flutter-doctor-about-android-toolchain?fbclid=IwAR1sBBFzb6qilpn-5rqQ_20WBhE1yB5rD6IjKFWZMHLUqj4arITQZAP1_mM#60475429)
## Dart的一個基本程序
```dart=
// Define a function.
void printInteger(int aNumber) {
print('The number is $aNumber.'); // Print to console.
}
// This is where the app starts executing.
void main() {
var number = 42; // Declare and initialize a variable.
printInteger(number); // Call a function.
}
```
**// This is a comment**
這是一個單行注釋,Dart也支援多行注釋 [補充資料-Comments](https://dart.dev/guides/language/language-tour#comments)
例:
```dart=
/*
多
行
注釋
*/
```
**void**
一種特殊類型,表示不返回值,像printInteger()和main()這種沒有顯示返回值的函數具有void類型
**int**
一種類型,表示整數,一些額外內置類型像是String、List、bool
**42**
數字,數字文字是一種編譯常量
**print()**
一種顯示輸出的便捷方式
**'...' (or "...")**
字串文字
**$variableName (or ${expression})**
在字串文字中包含一個變數或是或表達式的等效字串
**main**
應用程序執行開始的特殊、必需的頂級函數 [補充資料-The main() function](https://dart.dev/guides/language/language-tour#the-main-function)
**var**
宣告變數不指定其類型的方法,此變量 (int) 的類型由其初始值 (42) 確定。
## 重要概念
建議直接看原文 [資料來源-Dart官方文件-Important concepts](https://dart.dev/guides/language/language-tour#important-concepts)
* Everything you can place in a variable is an object, and every object is an instance of a class. Even numbers, functions, and null are objects. With the exception of null (if you enable sound null safety), all objects inherit from the Object class.
* Although Dart is strongly typed, type annotations are optional because Dart can infer types. In the code above, number is inferred to be of type int.
* If you enable [null safety](https://dart.dev/null-safety), variables can’t contain null unless you say they can. You can make a variable nullable by putting a question mark (?) at the end of its type. For example, a variable of type int? might be an integer, or it might be null. If you know that an expression never evaluates to null but Dart disagrees, you can add ! to assert that it isn’t null (and to throw an exception if it is). An example: int x = nullableButNotNullInt!
* When you want to explicitly say that any type is allowed, use the type Object? (if you’ve [enabled null safety](https://dart.dev/null-safety#enable-null-safety)), Object, or—if you must defer type checking until runtime—the [special type dynamic](https://dart.dev/guides/language/effective-dart/design#avoid-using-dynamic-unless-you-want-to-disable-static-checking).
* Dart supports generic types, like List<int> (a list of integers) or List<Object> (a list of objects of any type).
* Dart supports top-level functions (such as main()), as well as functions tied to a class or object (static and instance methods, respectively). You can also create functions within functions (nested or local functions).
* Similarly, Dart supports top-level variables, as well as variables tied to a class or object (static and instance variables). Instance variables are sometimes known as fields or properties.
* Unlike Java, Dart doesn’t have the keywords public, protected, and private. If an identifier starts with an underscore (_), it’s private to its library. For details, see [Libraries and visibility](https://dart.dev/guides/language/language-tour#libraries-and-visibility).
* Identifiers can start with a letter or underscore (_), followed by any combination of those characters plus digits.
* Dart has both expressions (which have runtime values) and statements (which don’t). For example, the [conditional expression](https://dart.dev/guides/language/language-tour#conditional-expressions) condition ? expr1 : expr2 has a value of expr1 or expr2. Compare that to an [if-else statement](https://dart.dev/guides/language/language-tour#if-and-else) , which has no value. A statement often contains one or more expressions, but an expression can’t directly contain a statement.
* Dart tools can report two kinds of problems: warnings and errors. Warnings are just indications that your code might not work, but they don’t prevent your program from executing. Errors can be either compile-time or run-time. A compile-time error prevents the code from executing at all; a run-time error results in an [exception](https://dart.dev/guides/language/language-tour#exceptions) being raised while the code executes.
[資料來源-Dart官方文件-Important concepts](https://dart.dev/guides/language/language-tour#important-concepts)
## 肆、Dart Input and Output
Taking a string input from user
```dart=
import 'dart:io';
void main()
{
String? name = stdin.readLineSync(encoding: utf8);
print("Hello, $name");
}
```
Taking a integer value as input
```dart=
// Importing dart:io file
import 'dart:io';
void main()
{
// Asking for favourite number
print("Enter your favourite number:");
// Scanning number
int? n = int.parse(stdin.readLineSync()!);
// Here ? and ! are for null safety
// Printing that number
print("Your favourite number is $n");
}
```
## 伍、Dart Type
### 一、Dart Constant
#### (一)final
```dart=
final String language = "Dart";
final name = "Toby";
```
#### (二)const(a const variable is a compile-time constant)
```dart=
const name1 = 'Toby';
const String name2 = 'Lucy';
```
final和const差在哪?
兩者都是為了宣告一個不可變動的 property。
唯一的區別為『時機點』
final宣告的 property 為專案執行階段的常數
const宣告的 property 為專案編譯階段的常數
```dart=
final date1 = DateTime.now();
const date2 = DateTime.now(); //compile error
```
由於DateTime.now()會直到專案執行到該行時,才會去取得最終的日期時間,所以不符合 const 的規則。
```dart=
var a = 0;
const b = a; //const_initialized_with_non_constant_value
```
可以修改為
```dart=
const a =0;
const b = a;
```
```dart=
var a = 0;
final b = a;
```
簡而言之const更為嚴格,runtime才完成初始化是不允許的
[補充資料-Dart基礎 — 老子只做一次的Constant](https://medium.com/one-two-swift/dart-%E8%80%81%E5%AD%90%E5%8F%AA%E5%81%9A%E4%B8%80%E6%AC%A1%E7%9A%84constant-f5ce0333b926)
[補充資料-Day04 | Dart基本介紹 - 變數宣告與基本型別](https://ithelp.ithome.com.tw/articles/10265049)
[參考資料-const_initialized_with_non_constant_value](https://dart.dev/tools/diagnostic-messages?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=const_initialized_with_non_constant_value#const_initialized_with_non_constant_value)
### 二、Dart Variable and Data type
```dart=
String name = "Clay"; // Strings
int age = 18; // Numbers
bool fat = false; // Boolean
List hobbies = [ // Lists
"eating",
"playing",
"coding",
];
void main() {
//runtimeType to show the variable type
print("name: ${name} (${name.runtimeType})");
print("age: ${age} (${age.runtimeType})");
print("fat: ${fat} (${fat.runtimeType})");
print("hobbies: ${hobbies} (${hobbies.runtimeType})");
}
```
Or you can use var to declare variable just like JavaScript
```dart=
var name = "Clay"; // Strings
var age = 18; // Numbers
var fat = false; // Boolean
var hobbies = [ // Lists
"eating",
"playing",
"coding",
];
void main() {
print("name: ${name} (${name.runtimeType})");
print("age: ${age} (${age.runtimeType})");
print("fat: ${fat} (${fat.runtimeType})");
print("hobbies: ${hobbies} (${hobbies.runtimeType})");
}
```
#### 集合:
dart的集合有以下三種:
list
set
map
陣列和集合的差別:
1.陣列的長度是固定的,集合的長度是可變的
2.陣列只能儲存相同資料型別的資料
3.集合可以儲存不同資料型別的物件的引用
### 三、Number
#### (一)int
native platform:-2^63 ~ 2^63 - 1
Javascript:-2^53 ~ 2^53 - 1
```dart=
int x = 1
int y = 0xDEADBEEF; //3735928559(16進制)
```
#### (二)double
```dart=
double x = 1.1;
double y = -3.1234;
double z= 8e5; //800000(e後面的數字為補0的次數)
double z1 = 8e-3; //0.008
double z2 = 0.8e2; //80
```
double method
```dart=
double x = 2.3;
print(x.abs()); //2.3 取絕對值
print(x.ceil()); //3 無條件進位
print(x.floor()); //2 無條件捨去
```
如果不確定使用double或是int的話可以使用num
num可以同時為int和double
一旦與double一起運算則不再為int
```dart=
num x = 1;
print(x is int); //true
print(x is double); //true
x += 1.2;
print(x is int); //false
print(x is double); //true
```
與 swift 不同的是,dart 的 int 跟 double 是可以比較跟運算的
```dart=
int x = 1;
double y = 2.7;
print(x + y); //3.7
print(x < y); //true
```
### 四、String
```dart=
String name1 = '王小明'; //單引號
String name2 = "王大明"; //雙引號
String description = """//三引號
王小明的爸爸有塊地
王小明的爸爸有塊地
王小明的爸爸有塊地
""";
String name3 = name1 + name2; //王小明王大明 字串串接
int age = 5;
print('大家好,我叫$name1,今年${age}歲。');
//字串內引用單值,$ 或 ${} 都可以,在字串內使用運算結果使用 ${}。
```
#### (一)String 屬性
```dart=
String s ='1234';
print(s.codeUnits); //UTF-16 [49, 50, 51, 52]
print(s.hashCode); //轉換成hascode 輸出:759253758
print(s.isEmpty); //檢查是否為空 輸出:false
print(s.isNotEmpty); //檢查是否不為空 輸出:true
print(s.length); //字串長度 輸出:4
print(s.runes); //32位元 輸出:(49, 50, 51, 52)
print(s.runtimeType); //運行時Type 輸出:String
```
[補充資料-String class](https://api.flutter.dev/flutter/dart-core/String-class.html)
[補充資料-Dart语法基础系列五《String 源码API详解》](https://cloud.tencent.com/developer/article/1889373)
### 五、Boolean
```dart=
bool isBigger = 3 > 1; //true
bool isEmpty = ''.isEmpty; //true
bool combine = isBigger && isEmpty; //true
var superman;
bool isSuperManExist = superman != null; // false
```
### 六、Lists
List 就是其他語言中的陣列(Array)
#### (一)宣告
```dart=
List<int> list1 = [1,2,3,4];
var list2 = <int>[1,2,3,4];
var list3 = [1,2,3,4];
var list4 = [0, ...list3]; //[0,1,2,3,4]
var list5; //optional
var list6 = [0, ...list5]; //error
var list7 = [0, ...?list5];//[0]
List list8 = new List.from([1,2,3,4]); // [1, 2, 3, 4]
List list9 = new List.unmodifiable([0,1,2]); // 創建包含所有元素的固定列表
List list10 = new List<int>.generate(5,(int i){
return i;
}); //產生所有元素赋初始值[0, 1, 2, 3, 4]
List list11 = new List<int>.generate(5,(int i)=>i); //[0, 1, 2, 3, 4]
var list12 = [for(var i =0;i<5;i++)i]; //[0, 1, 2, 3, 4]
```
> Lists還有很多奇特的做法
#### (二)List常用
```dart=
var list = [1,2,3,4];
print(list.length); //取得list長度 4
print(list[0]); //取得索引值的元素 1
print(list.first); //取得第一個元素 1
print(list.last); //取得最後一個元素 4
print(list.isEmpty); //false 陣列不為空
print(list.isNotEmpty); //true 陣列不為空
print(list.reversed); //以倒敘的方式顯示
print(list.single); //超過一個元素則返回異常訊息,假如只有一個元素則顯示該元素
list.sort((a,b) => a.compareTo(b)); //排序,正序
list.sort((b,a) => a.compareTo(b)); //排序,倒序
```
#### (三)加入元素
```dart=
var englishList = ["a","b","c","d"];
englishList.add("e"); // 加入一個元素在最後面
var colorList = ["red","yellow","green"];
print(englishList.length); //5
print(colorList.length); //3
englishList.addAll(colorList); //加入多個元素在最後面
print(englishList); // [a, b, c, d, e, red, yellow, green]
```
#### (四)插入元素
```dart=
var numList = [1,2,3,4];
var num2List = [5,6,7,8];
var num3List = [0,1,2,3];
numList.insertAll(4, [2,3,4,5]); //插入多個元素(索引值,多個元素)
num2List.insert(1, 10); //插入一個元素(索引值,元素)
num3List.insertAll(2,num2List); //插入陣列(索引值,陣列名稱)
print(numList); //[1, 2, 3, 4, 2, 3, 4, 5]
print(num3List); //[0, 1, 5, 10, 6, 7, 8, 2, 3]
```
#### (五)移除元素
```dart=
var numList = [1,2,3,4,5,3];
numList.remove(4); //移除指定元素
print(numList); //[1, 2, 3, 5, 3]
numList.remove(3); //移除指定元素如果存在多個相同元素,則只移除第一個
print(numList); //[1, 2, 5, 3]
numList.removeAt(1); //移除指定索引值上元素
print(numList); //[1, 5, 3]
numList.removeLast(); //移除最後一個元素
print(numList); //[1, 5]
numList.removeRange(0,1); //(startIndex, endIndex) 移除startIndex開始到endIndex之前的所有元素
print(numList); //[5]
```
#### (六)替換元素
```dart=
List name = [1,2,3,4];
name.replaceRange(0,3,[3,2,1]); //(startIndex, endIndex,Iterable <items>) 用Iterable <items>替換startIndex開始到endIndex之前的所有元素
print(name); //[3, 2, 1, 4]
```
#### (七)加入if判斷
```dart=
var bool = false;
List<String> titleList = [
"title1",
if (bool) "title2",
"title3",
"title4",
if (bool) "title5",
if (!bool) "title6"
];
print(titleList); //[title1, title3, title4, title6]
```
#### (八)使用for
```dart=
List<int> list1 = [1,2,3,4,5];
var list2 = [
for (var i in list1) i+=1
];
print(list2); //[2,3,4,5,6]
```
#### (九)使用if和for
```dart=
List<int> list1 = [1,2,3,4,5];
var list2 = [
for (var i in list1) if(i > 2) i+=2
];
print(list2); //[5,6,7]
```
#### (十)使用map method
```dart=
List<int> list1 = [1,2,3,4,5];
var list2 = list1.map((element) => element += 3).toList();
print(list2); //[4, 5, 6, 7, 8]
```
#### (十一)list 多維
使用方法與一維一樣
```dart=
List room = [
{
'RoomNo':'8909',
'GuestInfo':[
{'Name':'Toby'},
{'Name':'Lucy'},
],
'GuestInfo1':[
{'Name':'CC'},
{'Name':'TT'},
],
},
{
'RoomNo':'8910',
'GuestInfo':[
{'Name':'JJ'},
{'Name':'LL'},
],
'GuestInfo1':[
{'Name':'DD'},
{'Name':'KK'},
],
}
];
for (int i = 0; i < room.length; i++) {
print(room[i]['RoomNo']);
print('---------------');
for (int j = 0; j <room[i]['GuestInfo'].length; j++){
print(room[i]['GuestInfo'][j]['Name']);
};
print('-------------');
for (int j = 0; j <room[i]['GuestInfo1'].length; j++){
print(room[i]['GuestInfo1'][j]['Name']);
};
print('---------------');
}
```
輸出
```
8909
---------------
Toby
Lucy
-------------
CC
TT
---------------
8910
---------------
JJ
LL
-------------
DD
KK
---------------
```
[補充資料-內建型態 (集合) -Lists](https://ithelp.ithome.com.tw/articles/10234282)
[補充資料-Dart中List数组:二维、多维、循环遍历](https://www.jianshu.com/p/c31f19bc5684)
### 七、Set
定義為「沒有索引值且不可重複的集合」
```dart=
Set<int> set1 = {1,2,3,4,5};
set1[0] = 2; //compile error,set 沒有排序功能,故不能指定位置。
```
我們可以用 {} 來做初始化並用逗號分隔每一個元素:
```dart=
var setA = {0,1,2,3,4};
print(setA); //{0,1, 2, 3, 4}
```
也可以利用 Set.from 放入一個可迭代的值來產生 Set
```dart=
var list0 = [0,1,2,2,2,2,3,4];
var setB = Set.from(list0);
print(setB); //{0,1, 2, 3, 4}
```
可以發現儘管list0有重複的值,但set不允許重複,所以最後輸出會把重複的值去掉
因為 Set 裡並沒有存放索引值,所以我們無法直接存取特定位置的值。但因為 Dart 底層實作的關係,其實還是有將 Set 的順序存入(LinkedHashSet)
[補充資料-內建型態(集合) - Sets](https://thelp.ithome.com.tw/articles/10234285)
### 八、Map
Map就是有key-value型式的資料結構,而且key不能重複,也因為了有了key所以我們有辦法直接存取Map。
#### (一)Map宣告
```dart=
var exMap = {
'Toby':'man',
'Lucy':'girl'
};
print(exMap.runtimeType); //_InternalLinkedHashMap<String, String>
```
若沒有指定類型,Dart 會自動判斷型別,上例會被指定為 LinkedHashMap<String, String>
也可以在初始化時指定類型
```dart=
var exMap = <String,String>{
'Toby':'man',
'Lucy':'girl'
};
print(exMap.runtimeType); //_InternalLinkedHashMap<String, String>
```
空Map,使用大括弧直接定義
```dart=
var exMap = {};
var secMap = <String ,String >{};
print(exMap.runtimeType); //_InternalLinkedHashMap<dynamic, dynamic>
print(secMap.runtimeType); //_InternalLinkedHashMap<String, String>
}
```
用Map()宣告
```dart=
var exMap = Map();
var secMap = Map<String ,String >();
print(exMap.runtimeType); //_InternalLinkedHashMap<dynamic, dynamic>
print(secMap.runtimeType); //_InternalLinkedHashMap<String, String>
```
putIfAbsent():插入元素,如果「鍵」不存在
```dart=
var exMap = <String,String>{
'Taiwan':'Taipei',
'Japan':'Tokyo',
'USA':'NewYork'
};
exMap.putIfAbsent('Italy', () => 'Rome');
print(exMap); //{Taiwan: Taipei, Japan: Tokyo, USA: NewYork, Italy: Rome}
exMap.putIfAbsent('Italy', () => 'xxx');
print(exMap); //{Taiwan: Taipei, Japan: Tokyo, USA: NewYork, Italy: Rome} Italy已存在所以沒有改變
```
移除
remove(key):若「鍵」有存在於 Map 中,則將元素移除
removeWhere((key, value) => 條件): 若key或value符合條件,則將元素移除
```dart=
var exMap = <String,String>{
'Taiwan':'Taipei',
'Japan':'Tokyo',
'USA':'NewYork'
};
exMap.remove('Taiwan');
print(exMap); //{Japan: Tokyo, USA: NewYork}
exMap.removeWhere((key, value) => key=='Japan' || value=='NewYork');
print(exMap); //{}
}
```
更新
update(key, value):若「鍵」存在,則更新「值」
update((key, value) => 條件): 更新所有值
```dart=
var exMap = <String,String>{
'Taiwan':'Taipei',
'Japan':'Tokyo',
'USA':'NewYork'
};
exMap.update('USA', (value) => 'XXX');
print(exMap); //{Taiwan: Taipei, Japan: Tokyo, USA: XXX}
exMap.updateAll((key, value) => value+'---OuO---' );
print(exMap); //{Taiwan: Taipei---OuO---, Japan: Tokyo---OuO---, USA: XXX---OuO---}
```
containsKey(key):若「鍵」存在,則ture,否則false
containsValue(value):若「值」存在,則ture,否則false
```dart=
var exMap = <String,String>{
'Taiwan':'Taipei',
'Japan':'Tokyo',
'USA':'NewYork'
};
print(exMap.containsKey('Taiwan')); //true
print(exMap.containsKey('OuO')); //false
print(exMap.containsValue('Tokyo')); //true
print(exMap.containsValue('xxx')); //false
```
取得索引值的元素
map_name[key]
```dart=
void main(){
var abc = {
1:'111',
2:'2222'
};
print(abc[2]); //2222
}
```
疊代
forEach
```dart-
var exMap = <String,String>{
'Taiwan':'Taipei',
'Japan':'Tokyo',
'USA':'NewYork'
};
exMap.forEach((key, value) {
print('$value is $key\'s city');
});
// Taipei is Taiwan's city
// Tokyo is Japan's city
// NewYork is USA's city
```
以下是待整理的東東
```dart=
//1.通過構建器來創建Map
Map map1 = new Map();
//添加值 賦值
map1["one"] = 'Android';
map1["two"] = 'IOS';
map1["three"] = 'Flutter';
print(map1); //輸出:{one: Android, two: IOS, three: Flutter}
//2.通過複製的形式
Map map2 = Map.of(map1); //從另一個map初始化新的
print(map2); //輸出:{one: Android, two: IOS, three: Flutter}
//3.跟上面形式一樣 Object.fromEntries() 函數傳入一個鍵值對的列表,並返回一個帶有這些鍵值對的新對象。
// 這個迭代參數應該是一個能夠實現@iterator方法的的對象,返回一個迭代器對象。它
// 生成一個具有兩個元素的類似數組的對象,第一個元素是將用作屬性鍵的值,第二個元素是與該屬性鍵關聯的值。
Map map3 = Map.fromEntries(map1.entries);
print(map3); //{one: Android, two: IOS, three: Flutter}
Map map4 = Map.castFrom(map3);
print(map4); //{one: Android, two: IOS, three: Flutter}
//5.直接聲明,直接賦值key為String類型的map
Map map5 = {'one':'Android',
'two':'IOS',
'three':'Flutter'};
print(map5); //輸出:{one: Android, two: IOS, three: Flutter}
//6.創建一個空的Map
Map map6 = Map.identity();
print(map6); //輸出:{}
//7.創建不可變的Map
Map map7 = const {'one':'Android','two':'IOS','three':'flutter'};
print(map7); //輸出:{one: Android, two: IOS, three: flutter}
//8.在目標的map7創建(複製)新的不可修改map8
Map map8 = Map.unmodifiable(map7);
print(map8); //輸出:{one: Android, two: IOS, three: flutter}
//9.創建key為int值得map
Map map9 = {1:'Android',
2:'IOS',
3:'Flutter'};
print(map9); //輸出:{1: Android, 2: IOS, 3: Flutter}
//10.根據list所提供的key value來創建map
List<String> keys = ['one','two'];
List<String> values = ['Android','IOS'];
Map map10 = Map.fromIterables(keys, values);
print(map10); //輸出:{one: Android, two: IOS}
Map map11 = Map.from(map10); //從另一個map初始化新的
print(map11); //輸出:{one: Android, two: IOS}
Map map12 = new Map();
map12.addAll({'1':'one','2':'two'});
print(map12); //輸出:{1: one, 2: two}
Map map13 = new Map();
map13.addEntries(map12.entries);
print(map13); //輸出:{1: one, 2: two}
```
以下是待整理的東東
```dart=
//創建Map key是int類型,value是String類型
var map1 = new Map<int,String>();
//對Map第一個位置賦值,中括號是key
map1[0] = 'Android';
//對Map第二個位置賦值
map1[1] = 'IOS';
//對Map第三個值賦值
map1[2] = 'flutter';
//對Map賦空值
map1[3] = '';
//因為Map中的鍵值是唯一的,當第二次輸入的key如果存在,Value會覆蓋之前
map1[2] = 'RN';
print(map1); //{0: Android, 1: IOS, 2: RN, 3: null}
//獲取Map的長度
print(map1.length); //輸出:4
//判斷Map是否為空
print(map1.isNotEmpty); //輸出結果:true
//判斷Map是否不為空
print(map1.isEmpty); //輸出結果:false
//檢索Map是否含有某個Key
print(map1.containsKey(1)); //輸出:true
//檢索Map是否包含某個Value
print(map1.containsValue('Android')); //輸出:true
//刪除某個鍵值對
map1.remove(0);
print(map1); //輸出:{1: IOS, 2: RN, 3: null}
Map map2 = new Map();
map2.addAll({'1':'one','2':'two','3':'three'});
map2.removeWhere((key, value) => key=='1' || value=='three' );
print(map2); //輸出:{2: two}
//獲取所有的key
print(map1.keys); //輸出:(1, 2, 3)
//獲取所有的values
print(map1.values); //輸出:(IOS, RN, null)
//循環打印
/*
key:1, value:IOS
key:2, value:RN
key:3, value:null
*/
map1.forEach((key,value) =>print("key:${key}, value:${value}"));
//update
Map map3 = new Map();
map3.addAll({'1':'one','2':'two','3':'three'});
map3.removeWhere((key, value) => key=='1' || value=='three' );
map3.update('2',((value) => 'five'));
print(map3); //{2: five}
map2.updateAll((key, value) => '1');
print(map3); // {1: 1, 2: 1, 3: 1}
```
[補充資料-內建型態(集合) - Maps](https://ithelp.ithome.com.tw/articles/10234587)
[補充資料-Dart中常用对象和数组的方法总结](https://cloud.tencent.com/developer/article/1759865)
[補充資料-Dart中Map的使用](https://blog.csdn.net/ffa_ijj/article/details/85062239)
[補充資料-dart-learn](github.com/ducafecat/dart-learn/blob/master/11-Map/map.dart)
[補充資料-Dart: How to Update a Map](https://www.kindacode.com/snippet/dart-how-to-update-a-map/)
[補充資料-Dart: How to remove specific Entries from a Map](https://www.kindacode.com/snippet/dart-how-to-remove-specific-entries-from-a-map/)
[補充資料-What is the HashMap.removeWhere method in Dart?](https://www.educative.io/answers/what-is-the-hashmapremovewhere-method-in-dart)
[補充資料-Dart 編程 - 地圖](https://www.tutorialspoint.com/dart_programming/dart_programming_map.htm)
### HashMap, LinkedHashMap, SplayTreeMap
#### HashMap
是無序的 Map,存放在 HashMap 內的元素,在迭代取出時,不會依照順序取出
#### LinkedHashMap
預設 Map 的實作,將元素存進 LinkedHashMap,LinkedHashMap 會自動將前後的元素串起來,所以在迭代的時候就可以按照原先加入的順序取出。
#### SplayTreeMap
存放在 SplayTreeMap 的元素,可以按照順序排列。
### 九、Runes
Dart使用Unicode UTF-16,如果要使用32位元Unicode
例如,心臟字符' ♥使用對應的unicode等效\u2665表示,這裡\u代表unicode,數字是十六進制,本質上是一個整數。如果十六進制數字多於或少於 4 位,請將十六進制值放在大括號 ({ }) 中。例如,笑的表情符號 ' 😆' 表示為 \u{1f600}
```dart=
var heart = '\u2665';
var laugh = '\u{1f600}';
print(heart); //♥
print(laugh); //😀
```
String.codeUnitAt()
```dart=
var heart = '\u2665';
print(heart.codeUnitAt(0)); //9829
```
String.codeUnits
```dart=
var heart = '\u2665';
print(heart.codeUnits); //[9829]
```
String.runes
```dart=
String name = 'Toby';
name.runes.forEach((element) {
var chr = new String.fromCharCode(element);
print(chr);
}
);
```
```dart=
Runes input = new Runes(' \u{1f605} ');
print(new String.fromCharCodes(input));
```
[補充資料-Dart – Runes](https://www.geeksforgeeks.org/dart-runes/)
[補充資料-Dart Programming - Runes](https://www.tutorialspoint.com/dart_programming/dart_programming_runes.htm)
[補充資料-Runes class ](https://api.dart.dev/stable/2.17.3/dart-core/Runes-class.html)
### 十、Symbols
String to Symbol
```dart=
Symbol name = Symbol('name');
print(name); //Symbol("name")
//or you can use this
Symbol name2 = Symbol('name');
print(name2); //Symbol("name")
```
Symbol to String
```dart=
Symbol name = new Symbol('name');
String name1 = MirrorSystem.getName(name);
print(name1); //name
```
[補充資料-Dart – Symbols](https://www.geeksforgeeks.org/dart-symbols/)
[補充資料-Dart符号(Symbol)](https://www.yiibai.com/dart/dart_programming_symbol.html)
### 十一、型態轉換
```dart=
// String -> int
var one = int.parse('1');
// String -> double
var onePointOne = double.parse('1.1');
// int -> String
String oneAsString = 1.toString();
// double -> String
String dobleAsString = 1.234.toString();
// double -> String 到小數點後第幾位
String piAsString = 3.14159.toStringAsFixed(2);
```
### 十二、遞增/遞減
```dart=
var a=10;
var b=a--;
print(a); //9
print(b); //10
```
```dart=
var a=10;
var b=a++;
print(a); //11
print(b); //10
```
```dart=
var a=10;
var b=++a;
print(a); //11
print(b); //11
```
```dart=
var a=10;
var b=--a;
print(a); //9
print(b); //9
```
### 十三、練習
#### (一)輸入n個字串並儲存進陣列
<details>
<summary>參考解答</summary>
<pre>
import 'dart:io';
import 'dart:convert';
void main() {
List<String> name =[];
int n=int.parse(stdin.readLineSync(encoding: utf8)!);
for(var i=0;i<n;i++)
name.add(stdin.readLineSync(encoding: utf8).toString());
print(name);
}
</pre>
</details>
### 十四、參考資料
[參考資料-Dart基礎 — Basic Types](https://medium.com/one-two-swift/flutter-讓我看看-dart-basic-types-489b40eb9aca)
[參考資料-Dart入門](https://lihsinplayer.medium.com/dart入門-3eb57099da48)
[參考資料-The flutter “const” Keyword demystified.](https://medium.com/flutter-community/the-flutter-const-keyword-demystified-c8d2a2609a80)
[參考資料-Dart 語言基本教學筆記](https://clay-atlas.com/blog/2021/02/25/dart-cn-tutorial/)
[參考資料-Flutter Course for Beginners – 37-hour Cross Platform App Development Tutorial](https://www.youtube.com/watch?v=VPvVD8t02U8&list=LL&index=12&t=45495s)
[參考資料-Dart 語言 - 開啟 Flutter 的鑰匙 系列](https://ithelp.ithome.com.tw/users/20129264/ironman/3048)
[參考資料-Google Dart - Dart內建資料型態(Build-In Data Type)](https://limitedcode.blogspot.com/2014/12/dart-dartbuild-in-data-type.html)
[參考資料-字串資料型態](https://tw-hkt.blogspot.com/2019/07/flutter_18.html)
[參考資料-关于自增 自减运算符](https://www.itying.com/dart/dart10.html)
## 陸、Dart 控制流程(Control flow statements)
### 一、if-else
```dart=
var state = 1;
if(state==1){
print('y');
}else{
print('n');
}
```
或者可以使用if-else if -else
```dart=
var point=15;
if(point<10){
print("point<10");
}else if(point<20){
print("10<point<20");
}else{
print("point>=20");
}
```
> 注意:避免使用多個if
### 二、for
```dart=
var list = [1,2,3,4];
for (int i =0;i<list.length;i++){
print(list[i]);
}
```
#### for-in
```dart=
var list = [1,2,3,4];
for(var item in list){
print(item);
}
```
### 三、while/do-while
條件成立則會一直執行while
```dart=
var i = 0;
while(i<=10){
print(i);
i++;
}
```
#### do-while
會先執行一次,接者才會執行 while 迴圈
```dart=
var state = false;
do{
print('state is $state');
}while(state);
//state is false
```
### 四、break
```dart=
var i = 0;
while(i<=10){
print(i);
i++;
if(i == 4) break;
}
//0
//1
//2
//3
```
```dart=
var list = ['Tiger', 'Lion', 'Eagle'];
for(var i=0, i<list.length; i++){
print(list[i]);
if(list[i]=='Lion') break;
}
//Tiger
//Lion
```
### 五、continue
```dart=
for(var i=0; i<10; i++){
if(i%2 == 1) continue;
print(i);
}
//0
//2
//4
//6
//8
```
Also can use in while loop
### 六、switch / case
var sport = 'baseball';
```dart=
switch (sport){
case 'baseball':
playBaseball();
break;
case 'basketball':
playBasketball();
break;
default:
print('$sport is not ready');
break;
}
```
> 注意:switch/case 表達式必須是常量(constant)
> 補充:除了關鍵字 break 可以跳出 switch 之外,關鍵字 continue、throw 或是 return 都可以跳出 switch
### 七、enum
* 增加程式可讀性
* 代替常數、控制語句
* 在flutter配合擴展聽說很好用?
```dart=
enum myList{
Toby,
Lucy,
EDG,
RNG,
LOL
}
void main(){
for(myList values in myList.values){
print(values);
}
}
```
switch/case
```dart=
enum myList{
Toby,
Lucy,
EDG,
RNG,
LOL
}
void main(){
var yourName = myList.Toby;
switch(yourName){
case myList.Toby:
print('my name is Toby');
break;
case myList.Lucy:
print('my name is Lucy');
break;
case myList.EDG:
print('my name is EDG');
break;
case myList.RNG:
print('my name is RNG');
break;
case myList.LOL:
print('my name is LOL');
break;
}
}
```
```dart=
enum Dept{
IT,
Accounts
}
class employee {
final String name;
final int age;
final Dept dept;
employee(this.name,this.age,this.dept);
}
final employee1 = employee("Toby", 18, Dept.IT);
void main() {
if(employee1.dept == Dept.IT){
print("you are from IT department");
}else{
print("you are from Accounts department");
}
}
//you are from IT department
```
```dart=
enum Dept{
IT,
Accounts
}
void main() {
Dept.values.forEach((element) => print(element));
/*
Dept.IT
Dept.Accounts
*/
}
```
Enums extension
```dart=
enum Weather {
sunny,
cloudy,
rainy,
}
extension WeatherExt on Weather {
//custom message for each weather type
static const weatherMap = {
Weather.sunny: "What a lovely weather",
Weather.cloudy: "Scattered showers predicted",
Weather.rainy: "Will be raining today",
};
//prints enum index and custom message
void console() {
print("${this.index} ${this.about}");
}
//about property returns the custom message
String? get about => weatherMap[this]; // map[key]=value
}
void main() {
//#3. Enum extensions. Using extension method console
Weather.values.forEach((w) => w.console());
}
/*
0 What a lovely weather
1 Scattered showers predicted
2 Will be raining today
*/
```
[補充資料-Dart Enums](https://ptyagicodecamp.github.io/dart-enums.html)
### 八、default
在Switch裡面,可以把剩下沒定義的用default來定義
```dart=
const a=1;
switch(a){
case a:
print('a is 1 ');
break;
default:
print('a is not 1');
break;
//a is 1
}
```
### 九、assert
assert只會在debug模式下生效,一般執行沒有效,主要是用來偵錯的
```dart=
int age = 22;
bool result = age < 0;
print(result);
assert(result);
print(age);
}
```
### 十、練習
#### 泡沫排序
<details>
<summary>參考解答</summary>
<pre>
//努力編輯中...
</pre>
</details>
#### 插入排序
<details>
<summary>參考解答</summary>
<pre>
//努力編輯中...
</pre>
</details>
#### (四)數字回文
說明:
判斷數字是否為回文
例如:`
```
12321 //true
12312 //false
```
<details>
<summary>參考解答</summary>
<pre>
//努力編輯中...
</pre>
</details>
#### (五)list合併,相同元素只儲存一個
<details>
<summary>參考解答</summary>
<pre>
//努力編輯中...
</pre>
</details>
### 十一、參考資料
[參考資料-控制流程語句 (Control flow statements)](https://ithelp.ithome.com.tw/articles/10235935)
[參考資料-Dart Programming Dart Enum](https://linuxhint.com/dart-enum/)
[參考資料-Flutter - Dart -assert断言、try、catch、finally异常处理](https://juejin.cn/post/6844904202339090446)
## 柒、Dart 例外處理
### 一、try-catch
使用try-catch可以讓程式出現例外時不會停止,並且利用獲得例外詳細訊息
```dart=
var list0 = [0,1,2,3];
try{
for(var i=0;i<=list0.length;i++){
print(list0[i]);
}
}catch(error){
print(error);
}
//RangeError (index): Invalid value: Not in inclusive range 0..3: 4
```
### 二、try-on...catch
使用於特定例外或錯誤
```dart=
int x = 12;
int y = 0;
int res;
try {
res = x ~/ y; // ~/ 整除
}
// ignore: deprecated_member_use_from_same_package
on IntegerDivisionByZeroException catch(e) {
print(e);
}
//output:IntegerDivisionByZeroException
```
### 三、finally
finally 通常放在catch後方,並且無論程式是否異常一定都會被呼叫
```dart=
var list0 = [0,1,2,3];
try{
for(var i=0;i<=list0.length;i++){
print(list0[i]);
}
}catch(error){
print(error);
}
finally{
print('finally');
}
/*
0
1
2
3
RangeError (index): Invalid value: Not in inclusive range 0..3: 4
finally
*/
```
> try-catch-finally 不一定需要三個都使用,我們可以只用其中兩個:try-catch 或是 try-finally,這完全取決於使用情境
## 捌、Dart Method/Function
### 一、自訂函式
```dart=
返回類型 方法名稱(參數1,參數2,...){
方法
return 返回值;
}
```
```dart=
void printInfo(){
print('我是一個自訂方法,沒有返回值');
}
int getNum(){
var myNum = 123;
return myNum;
}
String printUserInfo(){
return 'this is str';
}
List getList(){
return ['111',2222,'abcd'];
}
void main(){
print('系統內置方法');
printInfo();
print(getNum());
print(printUserInfo());
print(getList());
}
```
> 注意:方法名稱最前面不能為數字
### 二、function傳參考
帶默認參數的function
```dart=
String personInfo(String name ,[String sex='男',int? age]){
if(age!=null){
return "姓名:$name性別:$sex年齡:$age";
}return "姓名:$name性別:$sex年齡:保密";
}
void main(){
var name = 'toby';
int age = 18;
print(personInfo(name));
print(personInfo(name,'女'));
print(personInfo(name,'女',age));
}
/*
姓名:toby性別:男年齡:保密
姓名:toby性別:女年齡:保密
姓名:toby性別:女年齡:18
*/
```
可選參數的function
```dart=
String personInfo(String name ,[int? age]){
if(age!=null){
return "姓名:$name年齡:$age";
}return "姓名:$name年齡:保密";
}
void main(){
var name = 'toby';
int age = 18;
print(personInfo(name));
print(personInfo(name,age));
}
/*
姓名:toby年齡:保密
姓名:toby年齡:18
*/
```
命名參數的function
```dart=
String personInfo(String name ,{String sex='男',int? age}){
if(age!=null){
return "姓名:$name性別:$sex年齡:$age";
}return "姓名:$name性別:$sex年齡:保密";
}
void main(){
var name = 'toby';
int age = 18;
print(personInfo(name,sex: '女',age: age));
}
```
function當作參數
```dart=
void main(){
var fn=(){
print('我是匿名function');
};
fn();
fn1(){
print('fn1');
}
fn2(fn){
fn();
}
fn2(fn1);
}
```
### 三、箭頭函式
箭頭函式只能寫一句,並且後面沒有分號(;)
```dart=
List list1 = [1,2,3,4,5];
list1.forEach((element) {
print(element);
});
list1.forEach((element) => print(element));
list1.forEach((element) =>{ print(element)});
}
```
#### 練習:修改list[4,1,2,3,4],讓大於2的值乘2,使用兩種方法
### 四、匿名function
```dart=
(){
function
return 返回值;
};
```
```dart=
var printNum=(){
print(123);
};
printNum();
```
匿名function傳參考
```dart=
var printNum=(int n){
print(n+2);
};
printNum(12);
//output:14
```
匿名function自己執行
```dart=
((int n ){
print(n);
})(12);
//output:12
```
### 五、function遞迴
1~100的和
```dart=
var sum=0;
fn(int n){
sum+=n;
if(n==0){
return;
}
fn(n-1);
}
fn(100);
print(sum); //5050
```
### 詞法作用域 Lexical scoping
建議直接看這個詳細的解釋[參考資料-Dart進階|深入理解Function&Closure](https://codertw.com/程式語言/720650/#outline__1)
```dart=
void main() {
var a = 0;
var a = 1; // Error:The name 'a' is already defined
}
```
上述代碼中,我們在 main 函數的詞法作用域中定義了兩次 a
我們的變量都有它的 詞法作用域 ,在同一個詞法作用域中僅允許存在一個名稱為 a 的變量,且在編譯期就能夠提示語法錯誤。
```dart=
void main() {
var a = 1;
print(a); // => 1
}
var a = 0;
```
我們就能夠正常打印出 a 的值為 1
簡單的解釋,var a = 0; 是該 dart 文件的 Lexical scoping 中定義的變量,而 var a = 1; 是在 main 函數的 Lexical scoping 中定義的變量,二者不是一個空間,所以不會產生衝突
### 六、Dart Closure
建議直接看這個詳細的解釋[參考資料-Dart進階|深入理解Function&Closure](https://codertw.com/程式語言/720650/#outline__1)
下面只會簡易講解
> A closure is a function object that has access to variables in its lexical scope, even when the function is used outside of its original scope.
> 閉包 即一個函數物件,即使函數物件的調用在它原始作用域之外,依然能夠訪問在它詞法作用域內的變數。
簡單來說就是能夠讓函數物件外可以調用函數物件內的變數
```dart=
var a =1234;
void main(){
print(a); //1234
}
```
```dart=
void main(){
var a = 1234;
var b = (){
print(a);
};
b(); //output : 1234
}
```
由上述兩者可以看出內部Lexical scoping可以存取外部 Lexical scoping中定義的變數
要怎麼由外部存取內部呢? 這時就要使用Closure啦
```dart=
void main(){
var a = (int num){
int sum=0;
return (){
sum+=num;
print(sum);
};
};
var b = a(1);
b(); //1
b(); //2
}
```
由上面的例子可以看到外部可以存取到內部的變數sum並把它打印出來
用變數b保存sum的狀態,所以Closure也可以稱為**有狀態的函數**
[補充資料-深入理解 Function & Closure](https://flutter.cn/community/tutorials/deep-dive-into-dart-s-function-closure)
[補充資料-关于Dart闭包](https://www.itying.com/dart/dart22.html)
[補充資料-官方文檔-Lexical closures](https://dart.dev/guides/language/language-tour#lexical-closures)
[補充資料-Dart-4-函数与闭包(closure)](https://blog.csdn.net/itzyjr/article/details/120555898)
[補充資料-Dart進階|深入理解Function&Closure](https://codertw.com/程式語言/720650/#outline__1)
## 玖、Dart 物件導向(OOP)
### 一、定義類別
使用class關鍵字建立類別
一般在定義類別名稱時,都是以大駝峰的形式
* 大駝峰:第一個字的字首大寫,後面每個單字的字首也大寫,常用於命名類別,例:MyCar
* 小駝峰:第一個字的字首小寫,後面每個單字的字首大寫,常用於命名物件,例:myCar
```dart=
class MyCar{
String color = 'red';
int speed = 150;
void getInfo(){
print("$color ---- $speed");
}
void setInfo(String color, int speed){
this.color = color;
this.speed = speed;
}
}
void main(){
//實例化(instance)
var car1 = new MyCar(); //or you can use: MyCar car1 = new MyCar();
car1.getInfo(); //output: red ---- 150
car1.setInfo('green', 200);
car1.getInfo(); //output: green ---- 200
}
```
> 使用(.)來參考成員(函數或屬性),如果類別沒有實例化則不能參考裡面的函數及屬性
### 二、建構式
建構式是類別的特殊函式,負責初始化類別。建構式是一個與類別名稱相同的函式,它是一個函式,因此可以傳入參數。但是與函式不同的地方,在於建構式不能具有返回型別。如果未宣告建構式,則會預設無參數的建構式,且建構子無法被繼承。
```dart=
class MyCar{
final String color ; //屬性前方加上 final:代表屬性是由建構式傳入,在類別內無法被修改。如此可以確保我們的屬性不會因為程式而變動
final int speed ;
MyCar(this.color,this.speed); //建構子的寫法是由類別名稱加上小括弧,裡面填入需要傳入的引數
void getInfo(){
print("$color ---- $speed");
}
}
void main(){
//實例化(instance)
//在實例化時,小括弧裡面直接將數值按照建構式的順序填入
var car1 = new MyCar('green',200); //or you can use: MyCar car1 = new MyCar('green',200);
car1.getInfo(); //output: green ---- 200
}
```
> 注意:Dart不允許多載(overload),可使用可選參數(Optional positional parameters)或是具名建構式 (Named Constructors)替代
[補充資料-Dart 語言入門 5: 類別(Class)](https://ithelp.ithome.com.tw/articles/10233196)
**具名引數 (Named Arguments)**
什麼是具名引數呢?在呼叫建構式建立類別實例之時,不需依照順序、數量,也可以成功的建立
```dart=
class MyCar{
final String? color ; // 加一個問號可以讓靜態檢查通過,表示可空類型
final int? speed ;
MyCar({this.color,this.speed}); //在類別建構子後方的括弧,裡面用大括弧 { } 包起來
void getInfo(){
print("$color ---- $speed");
}
}
void main(){
//實例化(instance)
var car1 = new MyCar(speed:200,color: 'green'); //用具名建構式建立類別時,將類別的屬性用冒號帶入傳入的引數,再用逗號分隔不同的屬性
car1.getInfo(); //output: green ---- 200
}
```
[補充資料-dart ?符号 问号符 后置问号 问号后置 flutter](https://blog.csdn.net/qq_34529292/article/details/119594951)
**可選參數(Optional positional parameters)**
可選參數就是在建構子中宣告那些是可選擇的傳入參數
```dart=
class MyCar{
MyCar(var color,[var speed]){
if(speed!=null){
print('$color --- $speed');
}else{
print('$color --- 150');
}
}
}
void main(){
var car1 = new MyCar('green'); //green --- 150
var car2 = new MyCar('red',200); //red --- 200
}
```
[補充資料-Dart 的 Constructor 研究 => Named & Factory](https://www.pigo.idv.tw/archives/1939)
#### 具名/命名建構式 (Named Constructors)
固定引數
```dart=
class MyCar{
String? color; //移除 final 修飾,因為建立一個新的具名建構式,所以原本的建構式產生時就不會將屬性帶入,那麼該 final 的屬性就會為 null 。這是不被允許的。
int? speed;
MyCar(this.color,this.speed);
MyCar.justColor(String color){ //固定speed
this.color = color;
this.speed = 8;
}
void getInfo(){
print("$color --- $speed ");
}
}
void main(){
var car1 = new MyCar.justColor('green');
car1.getInfo(); //green --- 8
}
```
在上方的範例中,具名建構式設定屬性值的動作是在大括弧內進行,我們可以將大括弧改為直接呼叫預設建構式
```dart=
class MyCar{
String? color; //移除 final 修飾,因為建立一個新的具名建構式,所以原本的建構式產生時就不會將屬性帶入,那麼該 final 的屬性就會為 null 。這是不被允許的。
int? speed;
MyCar(this.color,this.speed);
MyCar.justColor(String color): this(color,8); //把大括弧改成直接呼叫預設建構式,也叫Redirecting constructors(重定向建構式)
void getInfo(){
print("$color --- $speed ");
}
}
```
#### 工廠建構式 (Factory constructors)
建構子前以關鍵字factory宣告一個工廠建構子,工廠建構子不一定會產生一個新物件,可能回傳一個既存物件。
要注意工廠建構子在return之前還未有實體,故不能使用this引用成員變數的值或呼叫函數
```dart=
class Example {
factory Example.again() {
// get and return a instance from somewhere
// Or create and return a new instance
}
}
```
```dart=
class Student{
String name;
int age;
Student(this.name, this.age);
factory Student.first_grade(String name){
return Student(name, 8);
}
void printInfo(){
print('$name --- $age');
}
}
void main(){
Student a = Student('Toby', 18);
Student b = Student.first_grade('Lucy is Toby\'s girl friend');
a.printInfo(); //Toby --- 18
b.printInfo(); //Lucy is Toby's girl friend --- 8
}
```
>factory 不支持異步(Async)
[補充資料-Dart教程-Dart教學3](https://medium.com/mp-mobile-application-lab/dart教程-dart教學3-223bac4fd868)
[補充資料-從建構式到工廠方法](https://openhome.cc/Gossip/Programmer/ConstructorToFactory.html)
[補充資料-`Dart` 中序列化与持久化](https://juejin.cn/post/7033601493143928839)
[補充資料-dart中factory关键词的使用](https://zhuanlan.zhihu.com/p/133924017)
[補充資料-Flutter 设计模式:Singleton 单例](https://sexywp.com/flutter-singleton.htm)
[補充資料-Flutter(五)之彻底搞懂Dart异步](https://juejin.cn/post/6844903942795558919)
#### 常量建構式 (Constant constructors)
有時候,我們希望帶入的引數相同,在建立類別實例時,這些類別實例會被指向在同一個記憶體位置上。
這時候我們可以用關鍵字 const 來定義建構式。
```dart=
class Person{
final String name;
final int age;
const Person(this.name,this.age);
}
main() {
var person1 = const Person('toby',18);
var person2 = const Person('toby', 18);
//利用 identical() 檢查兩個物件是否相同
print(identical(person1,person2)); //true
}
```
[補充資料-類別與建構式](https://ithelp.ithome.com.tw/articles/10236606)
### 參考資料
[參考資料-物件導向基礎:何謂類別(Class)?何謂物件(Object)?](https://blog.miniasp.com/post/2009/08/27/OOP-Basis-What-is-class-and-object)
[參考資料-淺談物件導向 (一):class與method](https://ithelp.ithome.com.tw/articles/10220019)
[參考資料-淺談物件導向(三):來聊聊封裝與繼承](https://ithelp.ithome.com.tw/articles/10221540)
[參考資料-Dart 語言入門 5: 類別(Class)](https://ithelp.ithome.com.tw/articles/10233196)
[參考資料-Flutter(五)之彻底搞懂Dart异步](https://juejin.cn/post/6844903942795558919)
### Dart Class 的抽離
在實際的開發中,我們可能會有很多的類。
這樣會檔案越來越大。導致維護麻煩
這個時候,我們就需要將類抽離出去
方法:
在專案跟目錄底下建立一個資料夾lib
將Class放在資料夾下,檔案名跟Class名相同
```dart=
class Person{
late String name;
late int age;
Person(this.name,this.age);
Person.now(){
print('我是命名函數');
}
Person.setInfo(String name,int age){
this.name=name;
this.age=age;
}
void printInfo(){
print("${this.name}----${this.age}");
}
}
```
```dart=
import 'lib/Person.dart';
void main(){
Person p1=new Person.setInfo('李四1',30);
p1.printInfo(); //李四1----30
}
```
### Dart Class getter/setter
針對類別的屬性,Dart 也提供了一組特別的方法來對屬性進行存取。
當實例化類別參考類別的屬性時,其實已經隱含了 getter/setter。
我們也可以利用關鍵字 get、set 設定屬性的 getter 、setter。
getter沒有參數並返回一個值,setter只有一個參數但不返回值
```dart=
class Student{
String first_name;
String last_name;
int age;
Student(this.first_name, this.last_name, this.age);
String get full_name => '$first_name $last_name';
void setFullName(String fullName){
first_name = fullName.split(' ')[0]; //split(Pattern pattern) pattern - 代表分隔符
last_name = fullName.split(' ')[1];
}
}
void main(){
var student = Student('Alex', 'Chen', 18);
print(student.full_name); // Alex Chen
student.setFullName('Michael Jordan');
print(student.full_name); // Michael Jordan
print(student.first_name);// Michael
print(student.last_name); // Jordan
}
```
[補充資料-Day 15:方法(Method)、getter 以及 setter](https://ithelp.ithome.com.tw/articles/10237210)
### Dart私有屬性與方法
Dart跟其他物件導向程式語言不一樣,沒有public、private、protected,但我們可以使用底線( _ )把一個屬性或是方法定義為私有。
所以如果某些屬性與函數僅在類別內部使用,那麼必須要將屬性或是方法名稱前方加上下底線。
```dart=
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final String _name;
// Not in the interface, since this is a constructor.
Person(this._name);
// In the interface.
String greet(String who) => 'Hello, $who. I am $_name.';
}
// An implementation of the Person interface.
class Impostor implements Person {
String get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy'))); //Hello, Bob. I am Kathy.
print(greetBob(Impostor())); //Hi Bob. Do you know who I am?
}
```
> 如果是使用DartPad的讀者,應該會發現即使加了_外部依然存取的到,為什麼?因為Dart private的scope是在package而不是class,簡單來說就是如果是在這隻檔案依然存取的到。
那我們要如何取得私有變數呢?以及要如何更改私有變數?
基本上都是利用一個function包裝起來例如:
```dart=
String getName() => _name;
// 上面是簡寫的arrow function形式,相等於
String getName(){
return _name;
}
```
那要如何更改私有變數呢?
```dart=
void setName(String x) {
_name = x;
}
```
操作起來就會像是這樣
```dart=
todd.getName() // todd
todd.setName('toddd')
todd.getName() // toddd
```
我們也可以使用剛剛教的getter/setter存取或修改
[補充資料-Day 06 | Dart基本介紹 - private & static](https://ithelp.ithome.com.tw/articles/10265940)
### Dart Class初始化列表(initializer list)
Dart中有以下四種方式初始化變數
1.在實例變數時初始化
```dart=
class Point {
var x = 0, y = 0;
}
```
2.使用構造函數初始化
```dart=
class Point {
var x, y;
Point(this.x, this.y);
}
```
3.通過初始化列表(initializer list)
```dart=
class Point {
var x, y;
Point(a, b) : x = a, y = b;
}
```
4.構造函數中初始化
```dart=
class Point {
var x, y;
Point(a, b) {
x = a;
y = b;
}
}
```
甚麼情況下適合使用初始化列表來進行實例化成員變數呢?
比如我們有以下一個類,它的兩個 final 的成員變數值 sum,factor 都是通過 x,y 計算得來的,所以我們不能使用 this.sum 這樣的初始化方式。同時因為 final 字段沒有 setter 方法(Dart 中的賦值語句就是 setter 方法的語法糖),所以也不能在構造函數中給這些字段賦值。此時我們就可以通過初始化列表解決如何在代碼運行前給實例變量進行初始化的問題。
```dart=
class Point {
final sum, factor;
Point(x, y) : sum = x + y, factor = x / y;
}
```
[參考資料-Dart 中该何时使用初始化列表](https://doslin.com/2020/01/27/when-to-use-initializer-list/)
[補充資料-final_not_initialized_constructor](https://dart.dev/tools/diagnostic-messages?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=final_not_initialized_constructor#final_not_initialized_constructor)
[補充資料-final_not_initialized](https://dart.dev/tools/diagnostic-messages?utm_source=dartdev&utm_medium=redir&utm_id=diagcode&utm_content=final_not_initialized_constructor#final_not_initialized_constructor)
### Dart 類別的繼承(extends)
* 子類別使用extends關鍵字來繼承父類別
* 子類別會繼承父類別裡面可見的屬性和方法,但不會繼承構造函數
* 子類別能複寫父類別的方法getter和setter
* 每個類別只能繼承一個父類別
```dart=
class Person {
String name='张三';
num age=20;
void printInfo() {
print("${this.name}---${this.age}");
}
}
class Web extends Person{
}
main(){
Web w=new Web();
print(w.name);
w.printInfo();
}
```
可以發現Web繼承了Person的屬性與方法
那如何在子類別呼叫父類別呢?
很簡單,使用super就能呼叫父類別
```dart=
class Person {
late String name;
late num age;
Person(this.name,this.age);
void printInfo() {
print("${this.name}---${this.age}");
}
}
class Web extends Person{
Web(String name, num age) : super(name, age);
}
main(){
Web w=new Web('張三', 12);
w.printInfo(); //張三---12
}
```
可以發現上面Web建構式後面使用前面教的重定向建構式並用super來呼叫父類別的建構式
```dart=
class Person {
String name;
num age;
Person(this.name,this.age);
void printInfo() {
print("${this.name}---${this.age}");
}
}
class Web extends Person{
late String sex;
Web(String name, num age,String sex) : super(name, age){
this.sex=sex;
}
run(){
print("${this.name}---${this.age}--${this.sex}");
}
}
main(){
Web w=new Web('張三', 12,"男");
w.printInfo(); //張三---12
w.run(); //張三---12--男
}
```
假如想要在子類裡面覆寫父類裡面的函數
可以使用@override來註明該函數是覆寫自父類的函數
```dart=
class Television {
// ···
set contrast(int value) {...}
}
class SmartTelevision extends Television {
@override
set contrast(num value) {...}
// ···
}
```
[參考資料-Overriding members](https://dart.dev/guides/language/language-tour#extending-a-class)
[補充資料-Day 30:Metadata 以及完賽心得](https://ithelp.ithome.com.tw/articles/10246838?sc=rss.iron)
[補充資料-Day 07 | Dart基本介紹 - extends、abstract、mixin](https://ithelp.ithome.com.tw/articles/10268368)
[補充資料-关于Dart中类的继承](https://www.itying.com/dart/dart32.html)
### Dart Extension methods
當我們使用他人的API 或一些被廣泛實作的Library 時,想要更改API 內容是不切實際、不可能的,但是我們可能仍想添加一些功能
比如要把一個String 轉成int 的類型,我們原本會直接用api 的方法
```dart=
int.parse('42')
```
但是覺得還要透過int類別來轉型實在太麻煩了,想要簡化成:
```dart=
'42'.parseInt() // 無法使用,String 類別沒有提供此api
```
此時就能透過定義Extension method 來擴充String 類別,添加新的方法
```dart=
//在String 類別定義一個extension 名為NumberParsing,並設計我們的方法
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
}
main() {
print('42'.parseInt()); // 印出 42
}
```
注意:不能對dynamic類型的變數調用擴展方法,例如
```dart=
dynamic d = '2' ;
print(d.parseInt()); // 會報錯
```
定義完 extension methods 後,可以依定義出來的extension 名,解決使用多個API 時,出現相同的方法名衝突
如果擴展成員與接口或另一個擴展成員衝突,那麼您可以使用show或hide來限制公開的API,從而更改導入衝突擴展名的方式:
hide:
```dart=
// 在專案裡,新建另一個Dart 檔string_apis.dart,並在其定義一個String 類別的extension 名為NumberParsing,並設計有方法parseInt().
import 'string_apis.dart' ; // 就像引用外部函式庫
// 再新建另一個Dart 檔string_apis.dart2,並在其也定義一個String 類別的extension 名為NumberParsing2,並設計有方法parseInt().
//此時兩個api衝突了,使用parseInt() 會有問題,我們選擇hides NumberParsing2 extension method.
import 'string_apis_2.dart' hide NumberParsing2 ;
// 此時的parseInt() 為在'string_apis.dart' 裡定義的 extension method
print ('42'.parseInt());
```
show:
```dart=
// 一樣有兩個有衝突的外部函式庫
import 'string_apis.dart' ;
import 'string_apis_2.dart' ;
print (NumberParsing('42').parseInt()); //明確的去使用哪個extension 的方法
print (NumberParsing2('42').parseInt());
```
如果兩個extension 名字都相同,則需要使用前綴導入
```dart=
// 一樣有兩個有衝突的外部函式庫,而且定義在String 類別的extension 名字都為NumberParsing
import 'string_apis.dart' ;
import 'string_apis_2.dart' as rad ; // 需要前綴,好讓後面如果要解決衝突時,能夠使用前綴名去調用它的extension 方法
print (NumberParsing('42').parseInt());
print (rad.NumberParsing('42').parseInt()); //透過前綴呼叫extension 方法
```
[參考資料-Extension methods](https://ithelp.ithome.com.tw/articles/10243264)
[參考資料-Extension methods](https://dart.dev/guides/language/extension-methods)
[補充資料-Dart extension methods](https://www.youtube.com/watch?v=D3j0OSfT9ZI&ab_channel=Flutter)
### Dart 抽象類別(Abstract class)
抽象概念:在現實生活中,有 Sheep、 Fish 等動物這個實體,但是卻沒有一種動物叫做 Animal,他就好像是一種概念,是抽象的,並不實際存在
在定義類別時,可以只宣告方法名稱而不實作(Implements),這樣的方法稱之為「抽象方法」(Abstract method),若類別中包括了抽象方法,則該類別稱之為「抽象類別」(Abstract class),抽象類別是擁有未實作方法的類別,所以不能被用來生成物件,只能被繼承擴充,並於繼承後實作未完成的抽象方法,所以可以把抽象類別理解成他就是用來被繼承的
* 與一般類別一樣,具有field,constructors,methods
* 關鍵在於一定會有抽象方法(只有方法名,無內容) → 型態 方法名( [引數] ) ;
* 只要有一個抽象方法,類別一定也要改為抽象 → abstract class
* 變成抽象類別後,本身無法實體化,即無法被 new,只剩提供繼承的功能,去當父類別的角色,或做為宣告的類別。但在Dart 語言中,並未定義介面interface這個關鍵字,而是讓類別有Implicit interfaces 隱式介面的特性,及類別可以當介面使用,故抽象類別常拿來定義介面interface使用 (介面等等會介紹)
* 繼承抽象類別的子類別
1. 一定要將父類別的抽象方法都實作出來 (即父類別的所有抽象方法都要被override),才可編譯
1. 子類別也為抽象類別 (不推薦,不能被實體化)
```dart=
abstract class Animal{
eat(); //抽象方法
run(); //抽象方法
printInfo(){
print('abstract class 的一般方法');
}
}
class Dog extends Animal{
@override
eat(){
print('小狗吃骨頭');
}
@override
run(){
print('小狗在跑');
}
}
class Cat extends Animal{
@override
eat(){
print('小貓在吃老鼠');
}
@override
run(){
print('小貓在跑');
}
}
void main(){
Dog d = Dog();
d.eat(); //小狗吃骨頭
d.printInfo(); //abstract class 的一般方法
Cat c = Cat();
c.eat(); //小貓在吃老鼠
c.printInfo(); //abstract class 的一般方法
}
```
[參考資料-[Day24] CH11:劉姥姥逛物件導向的世界——抽象、介面](https://ithelp.ithome.com.tw/articles/10270972?sc=iThelpR)
[參考資料-Day08 Dart 語言介紹(六) 抽象類別、介面、混合類別](https://ithelp.ithome.com.tw/articles/10242009)
[補充資料-一、关于Dart中类的抽象类](https://www.itying.com/dart/dart33.html)
[補充資料-Abstract classes](https://dart.dev/guides/language/language-tour#abstract-classes)
### Dart 介面(Interface)
當無關的類別需要共用共通的方法及常數時,通常會使用介面。舉例來說:
Animal 類別和植物(Plant)類別都具有生長(toGrow)這個方法,但 Animal 和 Plant 沒有什麼關聯,而且 Dart 只能繼承一個類別,所以 Sheep 繼承了 Animal,就不能再繼承 Grow 這個類別,這時就可以把 Grow 定義為介面,讓 Sheep 和 Tree 都可以實作這個介面
每個類別都會背地裡定義一個包含所有 instance 例項成員的介面 (Implicit interfaces),也就是說類別本身也可以是介面,只要用implements 類別就能將類別當作是interface 使用,如果有類別去實作這個介面,它就必須要去override掉介面裡的每個函式,包括Field 區的getter 和setter
```dart=
//設計一個Animal 抽象類別,並把之後要新建的動物系列類別的想設計的共同屬性、方法拉出來,以方便之後繼承使用
abstract class Animal {
String _name;
String _gender;
int _age;
String get _species; //繼承Animal 的類別,需要實作的getter 方法
List<String> get _dietList;
Animal(this._name, this._gender, this._age);
void introduce() {
print('My name is $_name, $_age years old, and $_gender.');
}
}
abstract class Mammals {
//哺乳類,要給哺乳類系列的動物當介面實作,其每個實作的類別皆有這些特性,但每個類別去實作時又有獨特性
String hairAndFur();
//...
}
abstract class Birds {
int flySpeed();
//...
}
class Bear extends Animal implements Mammals {
Bear(String name, String gender, int age) : super(name, gender, age);
@override
String get _species => 'Mammals';
@override
List<String> get _dietList => ['fruit', 'fish', 'grass'];
@override
String hairAndFur() {
return 'black and hairy';
}
@override
void introduce() {
super.introduce();
print(
'The Bear, species is $_species, main food are ${_dietList.join(", ")}, has ${hairAndFur()} hair and fur');
}
}
class Monkey extends Animal implements Mammals {
Monkey(String name, String gender, int age) : super(name, gender, age);
@override
String get _species => 'Mammals';
@override
List<String> get _dietList => ['fruit', 'bug'];
@override
String hairAndFur() {
return 'brown and hairy';
}
@override
void introduce() {
super.introduce();
print(
'The Monkey, species is $_species, main food are ${_dietList.join(", ")}, has ${hairAndFur()} hair and fur');
}
}
class Eagle extends Animal implements Birds {
Eagle(String name, String gender, int age) : super(name, gender, age);
@override
String get _species => 'Birds';
@override
List<String> get _dietList => ['fish', 'bug', 'rat'];
@override
int flySpeed() {
return 100;
}
@override
void introduce() {
super.introduce();
print(
'The Eagle, species is $_species, main food are ${_dietList.join(", ")}, has ${flySpeed()} km/h fly speed');
}
}
void main() {
List<Animal> animals = [ //多型
Bear('Winnie', 'male', 18),
Monkey('Goku', 'male', 7),
Eagle('FatFat', 'female', 12)
];
animals.forEach((animal) => animal.introduce());
/*
My name is Winnie, 18 years old, and male.
The Bear, species is Mammals, main food are fruit, fish, grass, has black and hairy hair and fur
My name is Goku, 7 years old, and male.
The Monkey, species is Mammals, main food are fruit, bug, has brown and hairy hair and fur
My name is FatFat, 12 years old, and female.
The Eagle, species is Birds, main food are fish, bug, rat, has 100 km/h fly speed
*/
}
```
* 重複使用=>用繼承
* 容易維護
使用率高=>用抽象繼承
要客製化=>用介面繼承
[參考資料-Day08 Dart 語言介紹(六) 抽象類別、介面、混合類別](https://thelp.ithome.com.tw/articles/10242009)
[補充資料-CH11:劉姥姥逛物件導向的世界——抽象、介面](https://ithelp.ithome.com.tw/articles/10270972?sc=iThelpR)
[補充資料-Dart的類別、抽象、介面](https://medium.com/@newpage0720/dart的類別-抽象-介面-9fdff07821e)
[補充資料-Implicit interfaces](https://dart.dev/guides/language/language-tour#implicit-interfaces)
### Dart 靜態用法(static)
Dart 提供了 static 關鍵字,讓類別的屬性、函數轉變為全域的變數以及方法。
```dart=
class People{
static String name = 'abcd';
}
```
靜態變數可以通過外部訪問,不需要將類實例化
```dart=
void main(){
print(People.name); //abcd
}
```
實例化後的類也可以利用函數內部訪問靜態變數
```dart=
class People{
static String name = 'abcd';
void show(){
print(name); //函數內部訪問
}
}
void main(){
People p = People();
p.show(); //abcd
}
```
靜態function也可以外部訪問
```dart=
class People{
static String name = 'abcd';
static void show(){ //靜態方法
print(name);
}
}
void main(){
People.show();//abcd 不須實例化即可調用show
}
```
因為靜態變數實際上存在於類中,而不是實例本身,所以**無法使用this**
```dart=
class People{
static String name = 'abcd';
void show(){
print(this.name); //error
}
}
```
* 靜態成員與實例成員是分開的,靜態成員屬於類的定義體中,實例成員屬於類的實例中
* 實例化後無法通過外部直接調用靜態成員
* 靜態變數只有在第一次使用的時候進行初始化,之後每次呼叫,都只是從記憶體取出該值而已。
* 覆寫靜態變數之後,每次取值都是新的值。
[參考資料-Dart学习(17):静态static](https://blog.csdn.net/hxl517116279/article/details/88561292)
[參考資料-Day19:靜態變數 (Static variable)、靜態方法 (Static method) 以及 頂層函數 (Top-level functions)](https://ithelp.ithome.com.tw/articles/10239932)
### Dart 頂層函數(top-level functions)
通常static只能使用在類裡面,那能否把static搬出類呢?
不行,但是 Dart 提供了頂層函數 (top-level functions) 可以達成一樣的目的。
```dart=
void show(){
print('abc');
}
void main(){
show(); //abc
}
```
* 其實,我們一開始看到的 main() 也是屬於頂層函數,因為他不屬於任何類別。
### 頂層變數 (top-level variables)、頂層常數 (top-level constants)
有頂層函數,當然就有頂層變數以及頂層常數。
定義頂層變數、頂層常數的方式也很簡單。
全部移到最外面就可以了
```dart=
String name = 'abc';
void main(){
print(name); //abc
}
```
Effective Dart 提到:
在 Java 和 C# 中,每個定義都必須在一個類中,因此通常會看到“類”僅作為填充靜態成員的地方而存在。其他類被用作命名空間——一種為一組成員提供共享前綴以將它們相互關聯或避免名稱衝突的方法。
Dart 有頂級函數、變量和常量,所以你不需要一個類來定義一些東西。如果您想要的是名稱空間,那麼庫更合適。庫支持導入前綴和顯示/隱藏組合器。這些是強大的工具,可以讓您的代碼使用者以最適合他們的方式處理名稱衝突。
如果函數或變量在邏輯上與類沒有關聯,請將其放在頂層。如果您擔心名稱衝突,請給它一個更精確的名稱或將其移動到可以使用前綴導入的單獨庫中。
[官方-AVOID defining a class that contains only static members.](https://dart.dev/guides/language/effective-dart/design#avoid-defining-a-class-that-contains-only-static-members)
### Dart 混合類別(mixin & with)
[官方-Adding features to a class: mixins](https://dart.dev/guides/language/language-tour#adding-features-to-a-class-mixins)
Mixins are a way of reusing a class’s code in multiple class hierarchies.
在官方文件來看,mixin最主要就是要拿來復用程式碼的。
mixins中文的意思就是混入,就是在類中混入其他功能
在Dart中可以使用mixins實現類似多繼承的功能,解決重複冗餘的代碼,相比約束性很強的接口來說顯得更加有彈性,是能夠為類別新增功能的方法,通常都是將通用 的特性拉出來,寫成一個 mixin,也方便覆用在多個地方。
* 作為mixins的類只能繼承自Object,不能繼承其他類
* 作為mixins的類不能有構造函數
* 一個類可以mixins多個mixins類
* misins不是繼承,也不是接口,而是全新的特性
簡單來說:我提供一些方法給你使用,但我不用成為你的父類別
雖然mixin跟介面一樣,在所有類別皆有隱式定義,但還是建議用明確的mixin去宣告我們的類別,這樣此類別只能被當作mixin使用,就不會被實體化或被繼承 →
* 使用mixin宣告
* 在extends後、implements前,使用with 混合類別來使用mixin
```dart=
abstract class Animal {}
//改為用 mixin
mixin Run {
//通用的方法就能定義在這,有需要使用此功能的都能再mixin Run,即能使用
void run() {
print('I can run.');
}
}
mixin Swim {
void swim() {
print('I can swim.');
}
}
mixin Fly {
void fly() {
print('I can fly.');
}
}
class Frog extends Animal with Run, Swim {}
class Fish extends Animal with Swim {}
class Swan extends Animal with Fly, Run, Swim {}
main() {
Frog froggy = Frog();
froggy.swim();
froggy.run();
/* 印出
I can swim.
I can run.
*/
Fish dollarFish = Fish();
dollarFish.swim();
//印出 I can swim.
Swan uglyDuck = Swan();
uglyDuck.fly();
uglyDuck.run();
uglyDuck.swim();
/* 印出
I can fly.
I can run.
I can swim.
*/
}
```
* 限定
我們可以限定拉出來當混合類別的功能行為只能限定在某些類別才能使用:
```dart=
abstract class Animal {}
mixin Breathe on Animal {
//限定只有Animal 類別(或子類別),可以使用此混合類別
void breathe() {
print('I can breathe.');
}
}
//class Robot with Breathe {} //會報錯,不是Animal 類別
class Person extends Animal with Breathe {} //成功,不會報
```
* 也可以對混合類別做限制:
```dart=
abstract class Animal {}
mixin Walk {
void walk() {
print('I can walk.');
}
}
mixin Run on Animal, Walk { //我們限定只有Animal 類別(或子類別),而且要有走路的混合類別,才可以使用此混合類別
void run() {
print("I can run.");
}
}
//class Person extends Animal with Run {} //會報錯
//class Person with Walk, Run {} //會報錯
class Person extends Animal with Walk, Run {} //成功,不會報錯
```
* 線性化
問題點:如果with後的多個混合類別中有相同的方法,那麼當調用該方法時,會調用哪個類裡的方法
```dart=
class A {
String getMessage() => 'A';
}
class B {
String getMessage() => 'B';
}
class P {
String getMessage() => 'P';
}
class AB extends P with A, B {}
class BA extends P with B, A {}
void main() {
String result = '';
AB ab = AB();
result += ab.getMessage();
BA ba = BA();
result += ba.getMessage();
print(result); //印出 BA
}
```
我們會發現,最後一個mixin的函數,被調用了,這說明最後一個混入的mixins會覆蓋前面一個mixins的特性
Dart 中的mixin通過創建一個類別來實現,該類將mixin的實現層疊在一個父類別之上以創建一個新類,它不是在父類別中,而是在父類別的
**頂部**
```dart=
class AB extends P with A, B {}
class BA extends P with B, A {}
```
在語義上等同於:
```dart=
class PA = P with A;
class PAB = PA with B;
class AB extends PAB {}
class PB = P with B;
class PBA = PB with A;
class BA extends PBA {}
```
最終的類別層次結構可以用下圖表示:

[來源-Dart: What are mixins?](https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3)
類別 AB,為父類別 P,與A 類別以及B 類別做混合完後的類別
* mixin可以實現類似多重繼承的功能,但是實際上和多重繼承又不一樣
* 它與單繼承兼容,因為它是線性的,可當作一條繼承鏈
* with 混合類別 的順序代表了繼承鏈的繼承順序,而最後面with 的混合類別,會最先執行
[參考資料-Day08 Dart 語言介紹(六) 抽象類別、介面、混合類別](https://ithelp.ithome.com.tw/articles/10242009)
[參考資料-关于Dart中的mixins](https://www.itying.com/dart/dart36.html)
[參考資料-Adding features to a class: mixins](https://dart.dev/guides/language/language-tour#adding-features-to-a-class-mixins)
[參考資料-Day 07 | Dart基本介紹 - extends、abstract、mixin](https://ithelp.ithome.com.tw/articles/10268368)
[補充資料-Dart: What are mixins?](https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3)
## Dart 泛型(Generics)
[參考-Day20:泛型 (Generic)](https://ithelp.ithome.com.tw/articles/10240453)
### 什麼是泛型?
前面介紹 List、Set、Map,它們可以使用不同的型別,
在 List 的 API 文件中,發現它的型別定義為 List;而 Set 是 Set;Map 則 Map<K, V>。
在 <...> 裡面定義的不是基本型別,而是英文字母,這就是泛型。
其中, E 代表的是集合類中的元素, K 與 V 分別代表的是 Key(鍵) 與 Value (值)。
### 為什麼使用泛型?
* 型別安全
* 減少重複的code
以 List 為範例,我們可以將任意型別的值存入 List 中,接者如果我們嘗試存入不同的型別,就會出現編譯錯誤,這就是型別安全。
如果我們不使用泛型的方式來設計 List,那麼我們就必須要為所有的型別設計一份 List,而利用泛型則可以設計出更為通用的類別、函數。
```dart=
var city = List<String>();
city.addAll(['Taipei', 'Tokyo', 'San Francisco']);
city.add(42); // Error
```
另一個理由是,可以減少重複的程式碼。
假設我們有一個類別如下:
```dart=
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
```
然後,你發現你希望想要一個 String 版本的,於是你新建一個類別如下:
```dart=
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
```
接者,你又想要建立一個 num 版本的,所以又建立了
```dart=
abstract class NumCache {
num getByKey(String key);
void setByKey(String key, num value);
}
```
等等... 如果針對每一個型別都需要新建立一個類別,那麼就會產生出很多重複的程式碼。
我們可以將這個類別改為用泛型的方式來設計:
* 將類型名稱後面加上角括弧,並加上泛型型別的識別字母 T。
```dart=
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
```
其中,T為替代類型,作為開發人員稍後定義的型別。
### 執行期的泛型型別
Dart 的泛型型別是整齊的,意思是它會將型別資訊帶到執行期。與之對比的是 Java ,在 Java 編譯時期將值存入之後,就會將型別刪除。所以無法測試型別。
```dart=
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); //true
```
```dart=
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
if(list instanceof ArrayList<String>){} //error
if(list instanceof ArrayList){} //OK
```
### 限制泛型型別
用泛型設計時,有些時候並不想設計給全部的型別使用,只想開放給某類別及其子類別使用,該怎麼做呢?
使用關鍵字 extends 將泛型型別繼承某類別,那麼該類別就只能使用某類別或其子類別。
```dart=
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
```
我們定義了泛型類別 Foo ,並且限制它的泛型型別為 SomeBaseClass ,所以 Foo 類別可以接受 SomeBaseClass 及其子類別 Extender 。
```dart=
var someBaseClassFoo = Foo<SomeBaseClass>(); //OK
var extenderFoo = Foo<Extender>(); //OK
```
沒有使用角括弧指定型別也可以:
```dart=
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
```
但是,使用其他型別就不被允許
```dart=
var foo = Foo<Object>(); //Error
```
### 泛型函數
泛型除了可以用在類別之外,還可以用在函數上。
我們將回傳型別 return_type 改為T,並且在函數名稱後面用角括弧定義泛型的型別。
```dart=
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
```
> 泛型的型別識別字母雖然可以任意使用,但是一般我們都會將 E 使用在 集合類的元素, K 用在 Map 類的鍵 (Key), V 用在 Map類的值 (Value),而 T 則是用在單一型別的參數
## Dart 異步
### 什麼是異步?
異步又稱為非同步,即每個任務各做各的,不用等其他任務完成,再進行任務
### 為什麼需要異步?
Dart不像java之類的可以使用多執行緒,Dart是一個單執行緒語言(single thread),也就是一次只做一件事,所有程式都是依序執行
這時假如有個任務跟伺服器回傳資料要花很久的時間,整個程式就會卡在那,等到資料回傳結束才會繼續下一個任務,這個時候我們就需要使用異步,讓我們可以繼續執行下一個任務
### 使用時機
* 需要花費時間執行
* 需要等待資料
也就是說我們真實需要的資料、結果沒有辦法在執行程式碼之後直接取得,此時我們就需要使用異步
在 Dart 中,異步工作分為兩種:**Future**、 **Stream** 。
**Future** 是用在結果會在未來完成的情況,而 **Stream** 是用在資料流的情況。
針對異步的工作 Dart 提供了關鍵字 **async** 以及 **await** 來處理它。
### Future
#### Future.value
```dart=
Future<int> futureWait(){
return Future.value(10);
}
void main(){
futureWait().then((value)=>print(value));
}
//10
```
* 將想回傳的值放在Future.value()裡面,該數值就會以Future的形式回傳
Future 類別包含了幾個函數,我們可以直接串接在 Future 之後。
像是上面的**then**:
* then(value) :在 Future 的數值回傳之後,就可以在 then 裡面使用這個數值,如 print(value) 。
* whenComplete() :當 Future 完成之後,最後就會呼叫到 whenComplete 。
```dart=
void main(){
futureWait().then((value) => print(value))
.whenComplete(()=> print('done'));
}
//10
//done
```
#### Future.delayed
想要測試Future時可以使用Future.delayed
在 Future.delayed 中帶入一個 Duration ,時間到了之後就會執行 Future 內的函數。
模擬資料回傳,延遲一秒:
```dart=
Future<String> futureDelayed(){
return Future.delayed(Duration(seconds: 1),
() => 'Server Data');
}
void main(){
futureDelayed().then((value) => print(value)); //Delay 1 second
print('hello world');
}
// hello world
// Server Data
```
* 如上面的範例,在執行 Future.delayed 函數之後,在一秒之後就回傳值了。我們同樣可以利用 then 將數值列印出來
#### Future.error
除了正確的資料外,當錯誤發生時,我們也可以用 Future 將錯誤捕捉起來。 Future.error 可以發出一個 Future 的 錯誤。
```dart=
Future<String> futureError(){
return Future.error('error message');
}
void main(){
futureError().catchError((error) => print(error));
}
//error message
```
* 這邊是利用 Future類別的 catchError 函數來捕捉
#### Async/Await關鍵字
呼叫異步函數的時候,每一個異步函數回傳的時間都不一樣,如果我們需要等待異步函數的回傳值才繼續,我們就可以使用 async 以及 await 關鍵字。
async :用來表示該函數是異步函數。
await :是一種讓我們在**非同步裡面實現同步**的語法,意思是加上await的會用同步的方式執行。
> await必須在async function 裡面才能使用
```dart=
void testFutureMethod()async{
await futureMethod1().then((value)=> print(value));
await futureMethod2().then((value) => print(value));
}
Future<int> futureMethod1(){
return Future.delayed(Duration(seconds: 1), ()=>10);
}
Future<bool> futureMethod2(){
return Future.value(true);
}
void main(){
testFutureMethod();
}
// 10
// true
```
* 可以發現原本應該是true 10 變成10 true,異步函數因為async和await關鍵字變得有順序了
await也可以用來實例化Future
```dart=
Future<String> futureValue(){
return Future.value('message');
}
void testFutureMethod() async{
print(await futureValue()); // message
}
void main(){
testFutureMethod();
}
```
* 可以發現可以用await實例化Future獲取回傳值,而不需要使用Future.value,不過還是建議使用Future.value
[參考資料-Dart23:異步處理](https://ithelp.ithome.com.tw/articles/10242688)
[補充資料-Day09 | Dart 非同步 - Future](https://ithelp.ithome.com.tw/articles/10269767)
[補充資料-Asynchrony support](https://dart.dev/guides/language/language-tour#asynchrony-support)
[補充資料-Flutter(五)之彻底搞懂Dart异步](https://juejin.cn/post/6844903942795558919)
[補充資料-异步编程:使用 Future 和 async-await](https://dart.cn/tutorials/language/futures?id=477fb799d21401f46f8c04462fd249c4&horizontalRatio=99&verticalRatio=73)
### Stream
#### Stream是什麼?
Stream是為異步事件而生的,有很多場景會遇到,例如請求網路,和用戶互動之類的都會用到,但也蠻抽象的,第一次接觸可能會不太好理解
Stream的特點:
* 有一個入口,可以放東西或指令(anything)
* 我們不知道入口什麼時候會有東西進來
* 能夠新增或是做一些處理
* 有一個出口
* 我們不知道出口什麼時候會有東西出來
可以整理出主要特點就是不知道什麼時候進去、不知道什麼時候出來、可以進行處理
基礎方式:
* 用StreamController創造Stream
* StreamController有個入口叫sink
* sink可以用add把東西丟進去
* StreamController有個出口叫stream
* 東西進入之後會進行處理,這可能需要一點時間,我們不知道什麼時候會處理完從出口出來,所以我們需要使用listen一直監聽這個出口
#### 獲得Stream的方法
* 使用 Stream 的建構函數
* 使用 StreamController
* 使用 async* 函數
#### Stream建構函數
* Stream.fromFuture(Future future)
* Stream.fromFutures(Iterable<Future> futures)
* Stream.fromIterable(Iterable element):從一個集合中獲取其資料的單訂閱流
範例:
Stream.fromFuture(Future future)
```dart=
final stream1 = Stream.fromFuture(Future.delayed(Duration(seconds:1), ()=>10));
```
Stream.fromFutures(Iterable<Future> futures)
```dart=
List<Future<int>> generateFutures() =>
List<Future<int>>.generate(10, (index) => Future.value(index));
//generate 建立帶有length位置的列表,並用為0..length - 1範圍內的每個索引呼叫generator建立的值填充它
final stream2 = Stream.fromFutures(generateFutures());
```
Stream.fromIterable(Iterable element)
```dart=
final stream3 = Stream.fromIterable(List<int>.generate(5, (index) => 5*index));
```
#### 監聽Stream的方法
監聽Stream最常見的方法就是使用listen。當有事件Stream將會通知listener。Listen提供了幾種觸發事件:
```dart=
StreamSubscription<T> listen(void onData(T event)?,
{Function? onError, void onDone()?, bool? cancelOnError});
```
onData(必填):收到數據時觸發
onError:收到Error時觸發
onDone:結束時觸發
unsubscribeOnError:遇到第一個Error時是否取消訂閱,默認為false
例如:
```dart=
stream.listen((event) => print(event));
```
* 將會在每一個事件到來的時候,執行 print(event) 這個動作。
#### 使用await for 來處理 Stream
除了通過listen我們還可以使用await for 來處理
範例如下:
```dart=
Future<int> sumStream(Stream<int> stream) async {
var sum = 0;
await for (var value in stream) {
sum += value;
}
return sum;
}
```
這段程式會接收一個Stream然後統計所有事件的和,然後返回結果。
await for 能夠在每個事件到來時處理它。
一個Stream接收事件的時機是不確定的,那什麼時候應該要退出await for循環呢?
答案是,當這個Stream完成或關閉時。
#### StreamController
如果你想要建立一個新的Stream,可以使用StreamController,可以在StreamController上發送資料、處理錯誤,並獲得結果
在 StreamController 中,利用 sink 將資料加進 (add) Stream裡:
```dart=
StreamController<int> numController = StreamController();
numController.sink.add(777);
```
接者用 stream 將 controller 轉為 stream,然後用listen監聽資料並輸出:
```dart=
numController.stream.listen((value) => print(value));
//777
```
範例:
```dart=
StreamController controller = StreamController();
controller.sink.add(4);
controller.sink.add('xyz');
controller.stream.listen((data) => print(data));
// 4
// xyz
```
泛型定義了我們能夠向Stream上傳什麼類型的資料。他可以是任何類型!
#### 通過async*獲得Stream
假如我們一系列事件需要處理,我們也許需要把它轉為Stream。
這個時候可以使用async* - yield來生成一個Stream
```dart=
Stream<int> increaseStream() async*{
for(int i = 1 ; i<=10; i++){
yield i;
}
}
increaseStream().listen((event) =>print(event));
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10
```
* 可以發現每次循環yield都會回傳一個stream
當循環退出時,這個Stream也就done了,可以結合之前說的await for更加深刻體驗一下
範例:
```dart=
/// 餐點金額加總
Future<int> sushiStream(Stream<int> sushi) async {
var price = 0;
await for (var value in sushi) {
price = value * 30;
print('數量: $value');
}
return price;
}
/// 點餐數量
Stream<int> getSushi(Duration time, [int? quantity]) async* {
int i = 0;
while (true) {
await Future.delayed(time);
yield i++;
// 使用break停止循環
if (i > quantity!) break;
}
}
main() async {
var sushi = getSushi(Duration(seconds: 1), 5);
var checkout = await sushiStream(sushi);
print('總金額: $checkout');
/*
數量: 0
數量: 1
數量: 2
數量: 3
數量: 4
數量: 5
總金額: 150
*/
}
```
#### Transforming an existing stream
假如你已經有一個Stream,你可以通過轉換為一個新的Stream。
##### where
可以使用where篩選
```dart=
StreamController a = StreamController();
a.sink.add(1233);
a.sink.add('abc');
a.stream.where((event) => event==1233).listen((data)=>print(data)); //1233
```
##### take
可以使用take限制這個Stream最多能傳多少東西
```dart=
StreamController a = StreamController();
a.sink.add(1233);
a.sink.add('abc');
a.sink.add(5555666);
a.stream.take(1).listen((data) => print(data)); //1233
```
take接收一個int,代表最多能經過take函數的次數。
當傳輸次數達到這個數字時,Stream就會關閉,無法再傳輸
##### transform
假如你想要更多的控制轉換,那麼可以使用transform,他需要配合StreamTransformer進行使用
範例:
```dart=
StreamController<int> controller = StreamController<int>();
final transform = StreamTransformer<int,String>.fromHandlers(
handleData: (value,sink) {
if(value==100){
sink.add('恭喜你猜對了');
sink.close();
}else{
sink.add('還沒猜中,再試一次吧');
}
},
);
controller.stream
.transform(transform)
.listen((data) =>print(data),
onError: (e) => print(e));
controller.sink.add(23); //還沒猜中,再試一次吧
```
StreamTransformer<S,T>是Stream的檢查員,負責接收Stream通過的訊息,進行處理後返回新的Stream
* S代表之前輸入Stream的類型,這邊輸入的是一個數字,所以使用int
* T為轉化後Stream的類型,我們這裡add的是一串字串,所以使用String
* handleData可以在這邊對Stream進行轉化,可以參考[StreamTransformer<S, T>.fromHandlers constructor](https://api.dart.dev/stable/2.17.6/dart-async/StreamTransformer/StreamTransformer.fromHandlers.html)
#### Stream的種類
* 單一訂閱 stream
在Stream的生命週期裡,只允許一個監聽器監聽其變化。需要注意的是,Stream 會在有人註冊之後才開始發送事件,當監聽器取消訂閱時,將不再發送事件,縱使 Stream 的事件還沒有完全發送完畢。
另外,同一個 Stream 不允許多次註冊監聽器,就算是第一個監聽器已經被取消也是一樣。
通常用在傳送傳輸量較大的連續數據塊,例如檔案的 I/O。
```dart=
StreamController controller = StreamController();
controller.stream.listen((event) => print(event));
controller.stream.listen((event) => print(event));
controller.sink.add(123);
//Bad state: Stream has already been listened to. 單一訂閱不能有多個監聽者
```
* 廣播 stream
允許多個監聽器註冊,當 Stream 有事件進來的時候,會立刻發送事件,縱使沒有監聽器註冊也會發。
一般的Stream都單一訂閱。從Stream繼承的廣播Stream必須重寫isBroadcast才能返回true
```dart=
//將單一訂閱Stream轉換為廣播Stream
Stream stream = controller.stream.asBroadcastStream();
stream.listen((event) => print(event));
stream.listen((event) => print(event));
controller.sink.add(123);
//123
//123
```
[補充資料-Day 24:異步處理 Part2-Stream](https://ithelp.ithome.com.tw/articles/10243320)
[補充資料-進入Dart的世界-Stream](https://medium.com/learn-and-live/進入dart的世界-stream-ed2a20fc7be2)
[補充資料-Day 11 | Dart 非同步 - Stream](https://ithelp.ithome.com.tw/articles/10270434)
[補充資料-What are streams in Dart?](https://www.educative.io/answers/what-are-streams-in-dart)
[補充資料-异步编程:使用 stream](https://dart.cn/tutorials/language/streams)
[參考資料-Dart | 什么是Stream](https://juejin.cn/post/6844903686737494023)
## Generator
當你想要延遲產生一系列值時,可以使用Generator函數
Dart內置了兩種Generator函數的支持
* Synchronous generator: Returns an Iterable object.
* Asynchronous generator: Returns a Stream object.
### 同步產生器(Synchronous generator)
介紹同步產生器之前,先介紹 Iterator<E> 以及 Iterable<E>
#### Iterator是什麼?
迭代器 (Iterator) 是一個簡單的介面,可以迭代一系列的值
```dart=
abstract class Iterator<E>{
bool moveNext();
E get current; }
```
* curret:取得目前元素
* moveNext:移動到下一個,如果沒有則回傳false
Implementation Iterator
```dart=
Iterator<E> get iterator;
```
#### Iterable是什麼?
一個集合或是元素,可以照順序訪問的
Iterable也可以讓類別擁有Iterable的特性
如何使用呢?
類別繼承Iterable,並用Iterator getter獲取一個iterator讓它遍訪值
例如: MyStrings 類別擴展了 Iterable<String> ,所以它需要一個迭代器屬性(Iterator get iterator => string.iterator;)
```dart=
class MyStrings extends Iterable<String>{
MyStrings(this.strings);
final List<String> strings;
Iterator<String> get iterator => strings.iterator;
}
```
接者,我們就可以使用 for-in 來迭代走訪迭代器裡的每一個元素。
* 眾所皆知,for in 只接受Iterable,所以讓類別擁有Iterable特性就能使用
```dart=
void main(){
final myStrings = MyStrings([ 'How', 'Are', 'You' ]);
for (final str in myStrings){
print(str);
}
} //How //Are //You
```
* 使用 for-in 迴圈,Dart 會自動使用Iterator裡的 moveNext 以及 current。
> 只要是繼承Iterable的集合或是元素,在進行迭代的時候都會使用Iterator
除了可以使用 for-in 迴圈之外,也可以使用 forEach 來迭代走訪每一個元素。
```dart=
myStrings.forEach((value) => print(str);)
```
#### sync* 以及 yield 關鍵字
有了 Iterable、 Iterator 的概念之後,我們來看看怎麼使用同步產生器。
利用關鍵字 sync* 將函式定義為同步產生器,再利用關鍵字 yield 返回一個值
範例:
```dart=
Iterable<int> getRange(int start, int finish) sync*{
for(int i = start; i <= finish; i++ ){
yield i;
}
}
```
* 呼叫這個函數之後會產生一個Iterable的集合。
```dart=
void main(List<String> args) {
var numbers = getRange(1, 5);
print(numbers); //(1, 2, 3, 4, 5) 輸出一個集合
for(var val in numbers){
print(val); //迭代輸出
/*
1
2
3
4
5
*/
}
}
```
#### 產生器含有遞迴
```dart=
Iterable<int> getRange(int start, int finish) sync*{
if(start <= finish){
yield start;
for(final val in getRange(start+1, finish)){
yield val;
}
}
}
```
可以將遞迴改為
```dart=
Iterable<int> getRange(int start, int finish) sync*{
if(start <= finish){
yield start;
yield* getRange(start + 1, finish);
}
}
```
* 用關鍵字 yield* 可以在每一次的遞迴取得一個值,而不需要 for 迴圈
### 異步產生器(Asynchronous generator)
在 Stream 章節有提到,可以使用 async* 來建立 Stream<T> 。這就是異步產生器。
我們知道,Stream 就是一連串的 Future。
例如:
有一個函數 fetchString() 會回傳:
```dart=
Future<String> fetchString(String str) async{
return Future.value(str);
}
```
* 那麼,如果呼叫此函數就可以取得一個 Future<String> 的值
並且可以使用 then 當 Future 結束之後取到真實的值。
```dart=
void main(){
fetchString("test").then((value) => print(value));
}
//test
```
如果需要得到一連串的 Future 呢?
可以使用 async* 並使用yield* 遞迴的方式來取得Stream的每一個值。
```dart=
Stream<String> fetchStrings(List<String> strings) async* {
yield* fetchStrings(strings);
}
```
* 在遞迴中,使用關鍵字 yield* 來取得遞迴時每一個 Future 的值
[參考資料-Day 27:讓產生器 (Generator) 來產生一連串的同步或異步資料吧。](https://ithelp.ithome.com.tw/articles/10245339)
[補充資料-Generators](https://dart.dev/guides/language/language-tour#generators)
## Callable classes
Dart 提供了一個特別的方法
如果類別裡面有一函數的名稱為 call ,那麼該函數則不需要名稱就可以呼叫。
所以就可以像呼叫函數一樣,呼叫類別裡面的函數
```dart=
class TestCallable{
String message;
TestCallable(this.message);
void call()=>print(message);
}
var out = TestCallable('this is call');
void main() =>out(); //this is call
```
[參考資料-Callable classes](https://dart.dev/guides/language/language-tour#callable-classes)
[補充資料-Day 21:像呼叫函式一樣的呼叫類別吧。(Callable class)](https://ithelp.ithome.com.tw/articles/10241328)
## Isolates
大多數計算機,甚至在移動平台上,都具有多核 CPU。
為了利用所有這些內核,開發人員傳統上使用並發運行的共享內存線程。
然而,共享狀態並發很容易出錯並且可能導致複雜的代碼。
所有 Dart 代碼都在隔離內部運行,而不是線程。
每個 Dart 隔離器都有一個執行線程,並且不與其他隔離器共享可變對象。
[參考資料-Isolates](https://dart.dev/guides/language/language-tour#isolates)
## Typedefs
Typedefs也可以稱為類型別名(type alias)
關鍵字 typedef 定義一個類型回傳以及宣告使用
讓程式更加簡潔
範例:
```dart=
typedef IntList = List<int>;
IntList il = [1, 2, 3];
```
```dart=
typedef ListMapper<X> = Map<X, List<X>>;
Map<String, List<String>> m1 = {}; // Verbose.
ListMapper<String> m2 = {}; // Same thing but shorter and clearer.
```
函式也可以用
```dart=
typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
assert(sort is Compare<int>); // True!
}
```
也可以放到類別裡面
```dart=
typedef MyFunction = int Function(int, int);
class MyClass{
MyFunction function;
MyClass(this.function);
int call(int x, int y){
return function(x, y);
}
}
```
搭配callable class
```dart=
void main(){
var myClass = MyClass(add);
myClass(10, 20); //30
}
int add(int x, int y){
return x + y;
}
```
[參考資料-Typedefs](https://dart.dev/guides/language/language-tour#typedefs)
[補充資料-Dart 22:將函數定義成型別吧。 (Typedef)](https://ithelp.ithome.com.tw/articles/10241928)
## MetatData
//努力編輯中...
## Dart Libraries
//努力編輯中...
## Dart 其他資源
//努力編輯中...
## Flutter
//努力編輯中...
[參考資料-Flutter Course for Beginners – 37-hour Cross Platform App Development Tutorial](https://vwww.youtube.com/watch?v=VPvVD8t02U8&list=LL&index=14&t=45502s)