前面幾天的學習,我們瞭解了Dart語言的特性(基礎語法概覽、迭代集合、非同步編程和Mixin高級特性)。今天我們深入學習Dart的變數,包括:空安全(Null safety)、變數預設值、延遲變數(late)、final變數和const常量…… ...
Dart官網文檔:https://dart.dev/language/variables
重要說明:本博客基於Dart官網文檔,但並不是簡單的對官網進行翻譯,在覆蓋核心功能情況下,我會根據個人研發經驗,加入自己的一些擴展問題和場景驗證。
Dart中的變數
變數是一個對象的引用,引用名就是變數的名稱;就算引用是null
的變數也一樣。
變數有3種定義方式:var
關鍵字,顯示類型
和Object
/dynamic
類型。
var varName = 'Tom';
String strName = 'Tom';
Object objName = 'Tom';
dynamic dynName = 'Tom';
最佳實戰:對於局部變數,優先使用var
關鍵字,其次為顯示類型
,再次為Object
,不推薦使用dynamic
(原因:容易引發運行時錯誤,後續的學習會講到)。
當使用var
關鍵字定義的變數,Dart會根據值內容,推導出變數的實際類型,如上訴代碼varName
變數最終為String
類型。
Null safety空安全
Dart語言強制健壯空安全。空安全可以防止意外使用null
而導致的錯誤(還記得在Java編程中,在很多對象使用的地方,我們都需要進行null
判斷,以防止NPE的發生)。Dart在編譯期間,就會進行null潛在錯誤檢測分析,從而防止這些錯誤的發生(註意:並不是所有場景都能檢測分析到,後面章節會講到)。
代碼樣例:如下代碼,變數strName
的預設值為null
,在其他編程語言如Java語言中,下麵的代碼是合法的,但是在運行時,當執行strName.length
時會引發NPE異常;但是在Dart語言中,以下代碼是非法的(無法編譯),因此阻止發生NPE等這些潛在的錯誤。
String strName;
print(strName.length)
Dart為了強制執行空安全,有3個關鍵改變:
- 如果明確希望某個變數、參數或者其他組件是可以為
null
的,那麼需要在類型後面增加一個?
標識:
String? name // `name`的值可能為`null`, 或者為某個字元串
String name // `name`的值只能是某個字元串,不能為`null`
- Dart的變數在使用之前,必須被初始化。可空變數(含有
?
標識)的預設值為null
,即預設初始化為null,因此無需顯示的賦值初始化;而非空變數因沒有預設值,因此必須顯示賦值初始化。 - 禁止直接訪問可空類型的屬性或者表達式方法,包括訪問
null
對象的hashCode
和toString()
方法(記住:Dart中一起皆對象,null也是),也會引發錯誤。
Dart空安全通過以上3個關鍵改變,保證null
潛在錯誤在代碼編寫階段就能被前置發現,而不是等到代碼運行時。
變數預設值
第2章節中,其實已經提到一點:任何變數在使用之前,必須被初始化;可空變數因為預設值為null,因此可無需顯示初始化;非空變數必須顯示初始化。
特別註意:非空變數只需要確保它在被使用時已經初始化即可,而不是必須在申明的時候。如下代碼,變數lineCount
在申明時並未初始化,但是在print
被使用時,Dart語言檢測到它已經被初始化了,因此如下代碼是空安全有效代碼。
// 申明:未被初始化
int lineCount;
if (weLikeToCount) {
lineCount = countLines();
} else {
lineCount = 0;
}
// 使用:Dart檢測到已經被初始化,因此是可以使用
print(lineCount);
late延遲變數
頂級變數和類變數會延遲初始化,當第一次使用到這些變數時,初始化代碼的邏輯才會被執行(即:延遲初始化)。
在大多數情況下,Dart可以檢測並分析一個非空變數在使用時已經初始化,但是在有些場景下,Dart無法檢測分析或者檢測分析會失效。最常見的2種場景:頂級變數和實例變數,Dart無法確定它們在被使用時是否已經被初始化了。
如果我們明確一個非空變數在被使用之前能完成初始化(但Dart卻無法檢測到),可通過增加late
關鍵字,告訴Dart該變數為延遲變數,在被使用之前確保能被初始化。當然,對於late
延遲變數,在被使用時卻並沒有初始化,那麼使用它同樣會導致運行時錯誤。
late String description;
void main() {
description = 'Feijoada!';
print(description);
}
late
延遲變數主要處理以下2種場景:
- 這些變數並不是必須的,同時初始化它們非常耗時或者浪費資源(如網路交互等)。
- 在初始化實例變數時,需要用到實例本身。
代碼樣例:如下代碼,當變數temperature
在第1次被使用時,才會被調用readThermometer()
方法,即該方法僅僅被調用1次。
late String temperature = readThermometer();
final變數和const常量
最佳實踐:如果我們明確一個變數在被初始化之後,它的引用值再也不會變化,那麼使用final
或者const
修飾,而不是使用var
者顯示類型
。
const
變數隱式為final
變數const
變數是編譯期的常量變數- 實例變數可以是
final
變數但不能是const
變數(原因:實例在運行時才能確定,因此其變數無法作為編譯期常量)
final name = 'Bob';
final String nickname = 'Bobby';
const bar = 1000000;
const double atm = 1.01325 * bar;
特別註意:const
不僅可以申明如上代碼的常量變數,它也可以用於申明創建常量值,也可以用於申明創建常量值的構造器(還記得第2天學習內容:const構造函數?);同時,任何變數,都可以有常量值。
// 1. 常量值
var foo = const [];
final bar = const [];
const baz = []; // 等同:`const []`
// 2. 非final/const變數,它的常量值可以更新
foo = const [1, 2, 3];
特別註意:常量的定義,可以包含類型檢測、類型轉換、集合過濾和集合展開操作符。
// `i`是一個`Object`類型常量,它的值是int數字值
const Object i = 3;
// 1. 類型轉換
const list = [i as int];
// 2. 類型檢測和集合過濾
const map = {if (i is int) i: 'int'};
// 3. 類型檢測、集合過濾、集合展開
const set = {if (list is List<int>) ...list};
final變數和const常量總結:
final
變數不可修改,但是它的值是可以更新。const
常量不可修改,同時它的值也不可以更新,即它的值是不可變的(immutable)。
void main() {
// 1. final變數
final finalList = [1, 2, 3];
print('1.1 final變數: $finalList');
// 輸出:1.1 final變數: [1, 2, 3]
finalList
..add(4)
..add(5);
print('1.2 final變數: $finalList');
// 輸出:1.2 final變數: [1, 2, 3, 4, 5]
// 2. const常量
const constList = ['a', 'b', 'c'];
print('2.1 const常量: $constList');
// 輸出:2.1 const常量: [a, b, c]
constList
..add('c')
..add('d');
print('2.2 const常量: $constList');
// 錯誤:
// Unhandled exception:
// Unsupported operation: Cannot add to an unmodifiable list
// #0 UnmodifiableListMixin.add (dart:_internal/list.dart:114:5)
}
我的本博客原地址:https://ntopic.cn/p/2023100101
本文作者:奔跑的蝸牛,轉載請註明原文鏈接:https://ntopic.cn