Dart 的空安全機制是其重要特性之一,旨在幫助開發者避免 Null Pointer Exception 的錯誤。透過區分可空與不可空的型別,Dart 編譯器能有效檢查程式碼中潛在的空值問題,提升程式碼的可靠性。本文將介紹 Dart 空安全的核心概念,包括空感知運運算元、延遲初始化、列表操作等,並輔以程式碼範例和圖表說明,幫助讀者掌握 Dart 空安全的使用技巧。
If-Null Operator
??
運運算元可以用來提供一個預設值,如果左邊的值為 null。例如:
String? message;
final text = message ?? 'Error';
在這個例子中,如果 message
為 null,則 text
會被指定為 'Error'
。
Null-Aware Assignment Operator
??=
運運算元可以用來更新一個變數,如果它的值為 null。例如:
double? fontSize;
fontSize = fontSize ?? 20.0;
這個運運算元可以簡化為 fontSize ??= 20.0;
。
Dart 中的 Null 安全性
Dart是一種強型別的語言,為了避免 Null Pointer Exception,Dart 引入了 Null 安全性。Null 安全性是一種機制,確保變數不會為 Null,除非明確宣告為可 Null。
Null 感知指定運運算元(??=)
Null 感知指定運運算元(??=)是一種簡單的方式,來初始化變數時保證非 Null 值。例如:
double? fontSize;
fontSize ??= 20.0;
如果 fontSize
是 Null,則指定為 20.0,否則保持原值。
Null 感知存取運運算元(?.)
Null 感知存取運運算元(?.)是一種安全的方式,來存取物件的屬性或方法。如果左側的物件是 Null,則傳回 Null,否則傳回右側的屬性或方法。例如:
int? age;
print(age?.isNegative);
如果 age
是 Null,則不會丟擲 NoSuchMethodError,反而傳回 Null。
Null 斷言運運算元(!)
Null 斷言運運算元(!)是一種方式,來告訴 Dart 某個變數不會為 Null。例如:
String nonNullableString = myNullableString!;
注意,使用 Null 斷言運運算元可能會導致 Runtime Error,如果變數實際上是 Null。
Null 感知級聯運運算元(?..)
Null 感知級聯運運算元(?..)是一種方式,來安全地呼叫多個方法或設定多個屬性。例如:
class MyClass {
void method1() {}
void method2() {}
}
MyClass? myObject;
myObject?.method1();
myObject?.method2();
如果 myObject
是 Null,則不會丟擲 NoSuchMethodError,反而傳回 Null。
內容解密
- Null 感知指定運運算元(??=)用於初始化變數時保證非 Null 值。
- Null 感知存取運運算元(?.)用於安全地存取物件的屬性或方法。
- Null 斷言運運算元(!)用於告訴 Dart 某個變數不會為 Null。
- Null 感知級聯運運算元(?..)用於安全地呼叫多個方法或設定多個屬性。
圖表翻譯
flowchart TD A[Null 安全性] --> B[Null 感知指定運運算元] B --> C[Null 感知存取運運算元] C --> D[Null 斷言運運算元] D --> E[Null 感知級聯運運算元]
這個流程圖描述了 Dart 中的 Null 安全性機制,包括 Null 感知指定運運算元、Null 感知存取運運算元、Null 斷言運運算元和 Null 感知級聯運運算元。
Dart 中的 Nullability
在 Dart 中,nullability 是一個重要的概念,指的是變數或屬性是否可以為 null。瞭解 nullability 有助於我們避免 null pointer exceptions 和寫出更安全的程式碼。
非空屬性
在 Dart 中,屬性可以是非空的,也可以是可空的。非空屬性必須在初始化時指定,否則會導致編譯錯誤。
class User {
String name;
}
在上面的例子中,name
屬性是非空的,必須在初始化時指定。
可空屬性
可空屬性可以為 null,需要在宣告時加上 ?
符號。
class User {
String? name;
}
在上面的例子中,name
屬性是可空的,可以為 null。
初始化非空屬性
有兩種方式可以初始化非空屬性:使用初始值和使用初始化形式。
使用初始值
class User {
String name = 'anonymous';
}
在上面的例子中,name
屬性被初始化為 ‘anonymous’。
使用初始化形式
class User {
User(this.name);
String name;
}
在上面的例子中,name
屬性被初始化為建構函式的引數。
Null-Aware 運算子
Dart 提供了兩個 null-aware 運算子:?.
和 ?..
。
?.
運算子
?.
運算子用於存取可空物件的屬性或方法。如果物件為 null,則不會呼叫屬性或方法,直接傳回 null。
String? lengthString = user?.name?.length.toString();
在上面的例子中,如果 user
或 name
為 null,則 lengthString
會被指定為 null。
?..
運算子
?..
運算子用於可空物件的 cascade 運算。如果物件為 null,則不會執行 cascade 運算。
user?..name = 'Ray'..id = 42;
在上面的例子中,如果 user
為 null,則不會執行 name
和 id
的指定。
使用初始化列表
您也可以使用初始化列表來設定欄位變數:
class User {
User(String name) : _name = name;
String _name;
}
私有的 _name
欄位在建構函式被呼叫時將被保證獲得一個值。
使用預設引數值
可選引數如果沒有被設定,則預設為 null
,因此對於非可為 null
的型別,您必須提供一個預設值。
您可以為位置引數設定預設值,如下所示:
class User {
User([this.name = 'anonymous']);
String name;
}
或對於具名引數:
class User {
User({this.name = 'anonymous'});
String name;
}
現在,即使在沒有任何引數的情況下建立物件,name
仍將至少具有預設值。
必需的具名引數
如第 7 章「函式」中所學,如果您想使具名引數成為必需的,請使用 required
關鍵字。
class User {
User({required this.name});
String name;
}
由於 name
是必需的,因此不需要提供預設值。
可為 Null 的例項變數
上述所有方法都保證類別欄位將被初始化,並且不僅初始化,而且初始化為非 null
值。由於欄位是非可為 null
的,因此甚至不可能犯以下錯誤:
final user = User(name: null);
Dart 不會允許您這樣做,您將會得到以下編譯時錯誤:
The argument type 'Null' can't be assigned to the parameter type 'String'
內容解密
在這個例子中,我們使用初始化列表和預設引數值來保證類別欄位被初始化。同時,我們也使用 required
關鍵字來使具名引數成為必需的。這些方法可以幫助我們避免 null
值的錯誤,並使程式碼更加安全。
圖表翻譯
flowchart TD A[建立 User 物件] --> B[初始化欄位] B --> C[設定預設值] C --> D[檢查是否為 null] D --> E[如果為 null,丟擲錯誤] E --> F[如果不是 null,繼續執行]
這個流程圖展示了建立 User
物件的過程,包括初始化欄位、設定預設值、檢查是否為 null
等步驟。
Dart 中的 Nullability
在 Dart 中,nullability 是一個重要的概念,用於處理可能為 null 的變數。在本章中,我們將探討 Dart 中的 nullability,包括 nullable 型別、非本地變數的型別提升以及 late 關鍵字。
Nullable 型別
在 Dart 中,你可以使用 nullable 型別來表示一個變數可能為 null。例如:
class User {
User({this.name});
String? name;
}
在這個例子中,name
屬性是 nullable 的,這意味著它可以為 null。
非本地變數的型別提升
Dart 提供了型別提升的功能,可以自動將 nullable 變數提升為非 nullable 型別。但是,這個功能只適用於本地變數,而不適用於非本地變數。
例如:
bool isLong(String? text) {
if (text == null) {
return false;
}
return text.length > 100;
}
在這個例子中,text
變數是本地的,Dart 可以保證它在訪問 length
屬性之前不會為 null,因此可以將其提升為非 nullable 型別。
但是,如果我們將這個例子修改為非本地變數,如下所示:
class TextWidget {
String? text;
bool isLong() {
if (text == null) {
return false;
}
return text.length > 100; // error
}
}
Dart 編譯器會報錯,因為它不能保證 text
變數在訪問 length
屬性之前不會為 null。
解決方案
有兩個解決方案可以解決這個問題:
- 使用
!
運算子:
bool isLong() {
if (text == null) {
return false;
}
return text!.length > 100;
}
- 使用區域性變數 shadowing:
class TextWidget {
String? text;
bool isLong() {
final text = this.text; // shadowing
if (text == null) {
return false;
}
return text.length > 100;
}
}
Late 關鍵字
在某些情況下,你可能想要使用非 nullable 型別,但不能在建構函式中初始化它。在這種情況下,你可以使用 late
關鍵字來延遲初始化變數。
例如:
class User {
User(this.name);
final String name;
final int _secretNumber = _calculateSecret();
}
在這個例子中,_secretNumber
變數是使用 late
關鍵字延遲初始化的。
在本章中,我們探討了 Dart 中的 nullability,包括 nullable 型別、非本地變數的型別提升以及 late 關鍵字。我們還學習瞭如何使用 !
運算子和區域性變數 shadowing 來解決非本地變數的型別提升問題。
Dart 中的 Nullability 和 Lazy Initialization
在 Dart 中,nullability 是一個重要的概念,指的是變數是否可以為 null。Dart 提供了多種方式來處理 nullability,包括使用 ?
來表示可為 null 的變數、使用 !
來表示不可為 null 的變數,以及使用 late
來延遲初始化變數。
使用 late
來延遲初始化變數
late
是 Dart 中的一個關鍵字,用於延遲初始化變數。當你使用 late
來初始化一個變數時,Dart 不會立即初始化它,而是等到你第一次訪問它時才會初始化。
class User {
late final int _secretNumber;
int _calculateSecret() {
return 42;
}
User() {
_secretNumber = _calculateSecret();
}
}
在上面的例子中, _secretNumber
是一個 late
初始化的變數,它的值是在 User
類的建構函式中計算出來的。
使用 late
的風險
使用 late
來延遲初始化變數有一定的風險。如果你沒有在使用變數之前初始化它,Dart 會丟擲一個 LateInitializationError
。
class User {
late String name;
}
void main() {
final user = User();
print(user.name); // 會丟擲 LateInitializationError
}
在上面的例子中, name
是一個 late
初始化的變數,但它沒有被初始化,所以當你試圖訪問它時,Dart 會丟擲一個 LateInitializationError
。
使用 late
的好處
使用 late
來延遲初始化變數也有好處。如果你有一個變數需要進行昂貴的計算來初始化,但你不確定是否需要使用它,使用 late
可以避免不必要的計算。
class SomeClass {
late String? value = doHeavyCalculation();
String? doHeavyCalculation() {
// 進行昂貴的計算
}
}
在上面的例子中, value
是一個 late
初始化的變數,它的值是在 doHeavyCalculation
函式中計算出來的。但是,如果你從來沒有訪問過 value
, doHeavyCalculation
函式就不會被呼叫,從而避免了不必要的計算。
練習題
- 建立一個
Name
類,包含givenName
和surname
屬性。 - 新增一個
surnameIsFirst
屬性來表示姓氏是否放在名字之前。 - 新增一個
toString
方法來傳回全名。
class Name {
final String givenName;
final String? surname;
final bool surnameIsFirst;
Name({required this.givenName, this.surname, this.surnameIsFirst = false});
@override
String toString() {
if (surname == null) {
return givenName;
} else if (surnameIsFirst) {
return '$surname $givenName';
} else {
return '$givenName $surname';
}
}
}
Dart 中的 Null 安全性和列表
Dart 2.12 引入了 null 安全性,這是一個重要的特性,可以幫助開發者避免 null 相關的錯誤。null 安全性可以區分可空和不可空的型別,確保不可空的型別永遠不會是 null。
Null 安全性運算子
Dart 提供了多個 null 安全性運算子,包括:
??
:如果-null 運算子??=
:null-aware 指派運算子?.
:null-aware 存取運算子?.
:null-aware 方法呼叫運算子!
:null 斷言運算子?..
:null-aware 連鎖運算子?[]
:null-aware 索引運算子...?
:null-aware 展開運算子
延遲初始化
Dart 的 late
關鍵字可以用於延遲初始化一個欄位。使用 late
關鍵字可以使初始化變得懶惰,這意味著變數的值只有在第一次存取時才會被計算。
列表
列表是 Dart 中的一種重要的集合型別。列表可以用於儲存多個相同型別的物件,並且可以進行排序。Dart 中的列表可以透過列表字面量建立,例如:
var desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
列表可以進行修改和存取其元素。例如,可以使用 add
方法新增元素,使用 remove
方法刪除元素。
列表的基本操作
列表提供了多種基本操作,包括:
- 建立列表
- 修改列表
- 存取列表元素
列表的建立
列表可以透過列表字面量建立,也可以使用 List
類別建立。例如:
var desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
var snacks = <String>[];
列表的修改
列表可以進行修改,例如新增或刪除元素。例如:
desserts.add('cake');
desserts.remove('cupcakes');
列表的存取
列表可以存取其元素,例如:
print(desserts[0]); // 輸出:cookies
內容解密
上述程式碼示範瞭如何建立和修改列表,包括新增和刪除元素。列表可以透過列表字面量建立,也可以使用 List
類別建立。列表可以進行修改和存取其元素。
圖表翻譯
graph LR A[建立列表] --> B[修改列表] B --> C[存取列表元素] C --> D[新增元素] D --> E[刪除元素] E --> F[存取元素]
上述圖表示範了列表的基本操作,包括建立、修改和存取列表元素。
列表的基本操作
在 Dart 中,列表(List)是一種常用的資料結構,允許您儲存多個元素。下面我們將探討如何存取列表中的元素、修改列表元素的值以及列印列表。
存取列表元素
要存取列表中的元素,您可以使用索引(Index)來參照它。索引是從 0 開始的,這意味著第一個元素的索引是 0,第二個元素的索引是 1,依此類推。
void main() {
List<String> desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
String secondElement = desserts[1];
print(secondElement); // 輸出: cupcakes
}
在上面的例子中,desserts[1]
傳回列表中的第二個元素,即 'cupcakes'
。
使用 indexOf
方法
如果您知道元素的值,但不知道其索引,您可以使用 indexOf
方法來查詢其索引。
void main() {
List<String> desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
int index = desserts.indexOf('pie');
String value = desserts[index];
print('The value at index $index is $value.'); // 輸出: The value at index 3 is pie.
}
在上面的例子中,desserts.indexOf('pie')
傳回 'pie'
的索引,即 3
。
修改列表元素的值
您可以使用索引來修改列表元素的值。
void main() {
List<String> desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
desserts[1] = 'cake';
print(desserts); // 輸出: [cookies, cake, donuts, pie]
}
在上面的例子中,desserts[1] = 'cake'
修改了列表中的第二個元素的值為 'cake'
。
列印列表
您可以使用 print
函式來列印列表。
void main() {
List<String> desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
print(desserts); // 輸出: [cookies, cupcakes, donuts, pie]
}
在上面的例子中,print(desserts)
列印了列表中的所有元素。
內容解密
- 列表的索引是從 0 開始的。
- 您可以使用
indexOf
方法來查詢元素的索引。 - 您可以使用索引來修改列表元素的值。
- 您可以使用
print
函式來列印列表。
圖表翻譯
flowchart TD A[列表] --> B[索引] B --> C[存取元素] C --> D[修改元素值] D --> E[列印列表]
在上面的圖表中,我們展示了列表的基本操作,包括存取元素、修改元素值和列印列表。
列表元素的新增和刪除
在 Dart 中,列表是可擴充的,因此您可以使用 add
方法新增元素到列表的末端。例如:
void main() {
List<String> desserts = ['cookies', 'cake', 'donuts', 'pie'];
desserts.add('brownies');
print(desserts);
}
這會輸出:[cookies, cake, donuts, pie, brownies]
如果您想在列表中間新增元素,可以使用 insert
方法。例如:
void main() {
List<String> desserts = ['cookies', 'cake', 'donuts', 'pie', 'brownies'];
desserts.insert(1, 'ice cream');
print(desserts);
}
這會輸出:[cookies, ice cream, cake, donuts, pie, brownies]
如果您想刪除列表中的元素,可以使用 remove
方法。例如:
void main() {
List<String> desserts = ['cookies', 'ice cream', 'cake', 'donuts', 'pie', 'brownies'];
desserts.remove('cake');
print(desserts);
}
這會輸出:[cookies, ice cream, donuts, pie, brownies]
如果您知道要刪除的元素的索引,可以使用 removeAt
方法。例如:
void main() {
List<String> desserts = ['cookies', 'ice cream', 'donuts', 'pie', 'brownies'];
desserts.removeAt(0);
print(desserts);
}
這會輸出:[ice cream, donuts, pie, brownies]
內容解密
在上述範例中,我們使用 add
方法新增元素到列表的末端,使用 insert
方法新增元素到列表中間,使用 remove
方法刪除列表中的元素,使用 removeAt
方法刪除列表中指定索引的元素。
圖表翻譯
flowchart TD A[新增元素] --> B[add 方法] B --> C[列表末端新增元素] A --> D[insert 方法] D --> E[列表中間新增元素] A --> F[刪除元素] F --> G[remove 方法] G --> H[刪除列表中的元素] F --> I[removeAt 方法] I --> J[刪除列表中指定索引的元素]
在這個圖表中,我們展示了新增和刪除列表元素的流程。新增元素可以使用 add
方法新增到列表末端,也可以使用 insert
方法新增到列表中間。刪除元素可以使用 remove
方法刪除列表中的元素,也可以使用 removeAt
方法刪除列表中指定索引的元素。
Dart 中的列表排序和操作
在 Dart 中,列表(List)是一種常用的資料結構,用於儲存多個元素。當您需要對列表中的元素進行排序或插入、刪除操作時,Dart 提供了多種方法。
從技術架構視角來看,Dart 的 Null 安全性機制,包括可空型別、Null 感知運算子和 late 關鍵字,有效地解決了 null pointer exceptions 的問題,提升了程式碼的健壯性。分析 Dart 的 Null 安全特性可以發現,它允許開發者更精確地控制變數的可空性,並提供工具來安全地處理可能為 null 的值。然而,對於非本地變數的型別提升的限制,仍需額外處理,例如使用 !
運算子或區域性變數 shadowing。展望未來,隨著 Dart 的持續發展,預計 Null 安全機制將更加完善,並與其他語言特性更好地整合,進一步提升開發效率和程式碼品質。對於追求程式碼品質的開發者而言,深入理解和應用 Dart 的 Null 安全機制至關重要。