前言
看完本章節(jié)你將知道:
- 什么是空安全
- 空安全的原則
- 如何啟用空安全
- 空安全的類型
- 空斷言運算符
- late修飾符
視頻教程
空安全介紹
空安全null-safe type system是在Dart 2.12中引入的次伶,如果開啟空安全硬猫,默認(rèn)情況下代碼中的類型不能為空责蝠,也就是說除非聲明該類型是可以為空的,否則值不能為空。
空安全是官方極力推薦的,現(xiàn)在很多流行的第三方庫全部都是支持了空安全庞钢,所以空安全是我們必須要掌握的知識。
空安全的原則
- 默認(rèn)不可空:除非你將變量顯式聲明為可空因谎,否則它一定是非空的類型基括。
- 漸進遷移:可以自由地選擇何時進行遷移,多少代碼會進行遷移财岔,還可以使用混合模式的空安全风皿,在一個項目中同時使用空安全和非空安全的代碼。
- 完全可靠:對代碼的健全性帶來的所有優(yōu)勢——更少的 BUG匠璧、更小的二進制文件以及更快的執(zhí)行速度揪阶。
啟用空安全
空安全在Dart 2.12 和 Flutter 2.0中可用,可通過指定Dart SDK版本為2.12那么就會開啟空安全
environment:
sdk: ">=2.12.0 <3.0.0"
空安全類型
空安全分可為空和不可為空患朱,可為空就是變量鲁僚、形參都可以傳null值,不可為空變量裁厅、形參一定不能為空冰沙,我們在使用空安全的時候會碰到下面三種情況,接下來的代碼演示我們都是dart 2.12開啟空安全為準(zhǔn)
- 變量為空編譯時報錯
- 傳遞參數(shù)時為空編譯時報錯
- 方法需要返回參數(shù)時必須返回执虹,否則編譯時報錯
變量可為空和不可為空的使用對比
聲明一個空變量
這里我聲明了一個變量為name
的字符串屬性拓挥,但并沒有賦值,所以name
的內(nèi)存地址存的是一個空的字符串袋励。
String name;
錯誤提示
它提示說不可為空的變量一定要進行初始化
[圖片上傳失敗...(image-30e8ab-1632465574247)]
標(biāo)明變量可為空
我們可以在Stirng
后面加一個?
號侥啤,該符號標(biāo)明name
這個變量可以為空当叭,這個時候我們發(fā)現(xiàn)定義時不會出現(xiàn)報錯,但是我們在使用name
屬性的時候會發(fā)現(xiàn)有一個報錯盖灸,它報錯的信息是String?
不能分配給一個String
蚁鳖,如下:
[圖片上傳失敗...(image-da9c52-1632465574247)]
空斷言運算符
在上面我們使用name
這個屬性的時候會出現(xiàn)一個報錯,我們可以使用空斷言運算符!
來標(biāo)明該值不會為空赁炎,所以Dart
在編譯時不會報錯醉箕,該符號在項目中盡量不要使用,除非你明確知道它是不為空的徙垫,因為我們name
屬性還是空的讥裤,所以在運行時將會收到如下報錯:
======== Exception caught by widgets library =======================================================
The following _CastError was thrown building MyHomePage(dirty, state: _MyHomePageState#5824d):
Null check operator used on a null value
The relevant error-causing widget was:
MyHomePage MyHomePage:file:///Users/jm/Desktop/Work/Git/my_project/flutter_null_safety/lib/main.dart:29:13
When the exception was thrown, this was the stack:
..........
(package:flutter/src/rendering/binding.dart:319:5)
#123 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1143:15)
#124 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1080:9)
#125 SchedulerBinding.scheduleWarmUpFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:863:7)
(elided 4 frames from class _RawReceivePortImpl, class _Timer, and dart:async-patch)
====================================================================================================
參數(shù)可為空和不可為空的使用對比
聲明一個需要傳參的方法
下面這段代碼我們定義了一個可選參數(shù)為name
的字符串,在空安全的機制下姻报,我們必須保證傳入的參數(shù)不能為空己英,那么將會收到如下報錯:
_upperCase({String name}) {
setState(() {
value.toUpperCase();
});
}
floatingActionButton: FloatingActionButton(
onPressed: _upperCase(),
child: Icon(Icons.add),
),
錯誤提示
它提示參數(shù)name
的類型不能為null
值,但是被隱式轉(zhuǎn)換成了null
吴旋,我們碰到這種情況有兩種解決方案损肛,我們接下來看看如果解決
[圖片上傳失敗...(image-d2dd46-1632465574247)]
解決方法
第一種:給可選參數(shù)添加默認(rèn)值
_upperCase({String name = "Jimi"}) {
setState(() {
name.toUpperCase();
});
}
第二種:給可選參數(shù)增加required
_upperCase({required String name}) {
setState(() {
name.toUpperCase();
});
}
當(dāng)給可選參數(shù)增加required
后,說明可選參數(shù)name
必傳邮府,這樣會導(dǎo)致調(diào)用處會報錯,我們來在調(diào)用時把name
傳進去溉奕,如下:
floatingActionButton: FloatingActionButton(
onPressed: _upperCase(name: name!),
child: Icon(Icons.add),
),
這里使用name!
來進行傳入褂傀,在上面我們說過這個是空斷言運算符,只有你明確知道該變量會有值時才使用加勤,那么我們這里這樣使用還是會報錯仙辟,所以我們來繼續(xù)優(yōu)化,把name
聲明一個有值的變量鳄梅,如下:
String name = "Jimi";
floatingActionButton: FloatingActionButton(
onPressed: _upperCase(name: name),
child: Icon(Icons.add),
),
方法的返回值可為空和不可為空的使用對比
聲明一個需要有返回值的方法
下面我聲明了一個方法用來將name`的轉(zhuǎn)換為大寫叠国,在我們沒有引入空安全之前下來代碼是不會報錯的,但是引入空安全機制后你會收到如下報錯:
String name = "Jimi";
String _upperCaseName(String name) {
}
錯誤提示
它提示返回類型可能是不能為null
的類型戴尸,可以嘗試在最后添加 return
或 throw
語句粟焊。
[圖片上傳失敗...(image-1eb4c4-1632465574247)]
解決方法
第一種:添加return語句
String _upperCaseName(String name) {
return name.toUpperCase();
}
第二種:添加throw語句
String _upperCaseName(String name) {
throw "no return";
}
自定義類字段可為空和不可為空使用對比
聲明一個User類
我們在平時的開發(fā)過程中常常會自定義類蜜自,而自定義類中的屬性一開始都不會賦值热凹,那么我們引入空安全的情況下將會報錯,如下所示:
class User {
String name;
int age;
setName(String name) {
this.name = name;
}
getName() {
return this.name;
}
setAge(int age) {
this.age = age;
}
getAge() {
return this.age;
}
}
報錯提示
它這里提示name
以及age
必須要有初始化值蚀浆,或者標(biāo)記為late
挎峦。
[圖片上傳失敗...(image-17a779-1632465574247)]
解決方法
第一種:聲明變量可為空
我們采用聲明變量可為空的方式香追,在使用的時候我們使用空斷言運算符!
來進行寫入和讀取操作,但是這也暗示著null
對于字段來說是有用的值坦胶,這樣就背道而馳了透典,所以建議不要采用這種方式晴楔,接下來我們來看看第二種解決方案late
修飾符。
String? name;
int? age;
第二種:late修飾符
因為late
涉及內(nèi)容較多峭咒,我們拿一個章節(jié)來講解税弃。
late修飾符
late
修飾符是在運行時而非編譯時對變量進行約束,這也就是說late
相當(dāng)于何時執(zhí)行對變量的強制約束讹语。
比如本示例中name
字段用late
修飾后并不一定已經(jīng)被初始化钙皮,每次它被讀取時,都會插入一個運行時的檢查顽决,以確保它已經(jīng)被賦值短条。如果并未賦值,就會拋出一個異常才菠,給變量加上String
類型就是說:“我的值絕對是字符串”茸时,而加上late
修飾符意味著:"每次運行我都要檢查是不是真的"。
當(dāng)使用late
修飾符總結(jié)如下:
- 先不給變量賦值
- 稍后再給變量賦值
- 在使用前會給變量賦值
- 在使用前不賦值赋访,將會報錯
late String name;
late int age;
late修飾符懶加載
懶加載也有一種說法是初始化延遲執(zhí)行可都,當(dāng)你用late
修飾變量后,那么它將會被延遲到字段首次被訪問時才會執(zhí)行蚓耽,而不是在實例化構(gòu)造器時就初始化了渠牲。而且實例字段的初始化內(nèi)容是無法訪問this
的,因為在所有的初始化方法完成前步悠,是無法訪問到新的實例對象签杈。但是,使用了late
的話就可以訪問到this
鼎兽、調(diào)用方法以及訪問實例的字段答姥。
不使用late修飾符
我們這里就是創(chuàng)建一個User
類,name
屬性直接調(diào)用getUserName()
方法谚咬,最后當(dāng)我們實例化一個User
對象并獲取name
的值鹦付,我們來看下控制臺輸出:
void main() {
print("調(diào)用構(gòu)造函數(shù)");
var user = User();
print("獲取值");
print("獲取的值為: ${user.getName()}");
}
class User {
String name = getUserName();
setName(String name) {
this.name = name;
}
getName() {
return this.name;
}
}
String getUserName() {
print("返回用戶的名稱");
return "Jimi";
}
控制臺輸出
flutter: 調(diào)用構(gòu)造函數(shù)
flutter: 返回用戶的名稱
flutter: 獲取值
flutter: 獲取的值為: Jimi
使用late修飾符
代碼和上面一樣,只是在定義字段處加了一個late
修飾符择卦,我們來看一下控制臺輸出:
void main() {
print("調(diào)用構(gòu)造函數(shù)");
var user = User();
print("獲取值");
print("獲取的值為: ${user.getName()}");
}
class User {
late String name = getUserName();
setName(String name) {
this.name = name;
}
getName() {
return this.name;
}
}
String getUserName() {
print("返回用戶的名稱");
return "Jimi";
}
控制臺輸出
flutter: 調(diào)用構(gòu)造函數(shù)
flutter: 獲取值
flutter: 返回用戶的名稱
flutter: 獲取的值為: Jimi
我們明顯可以看到第二行和第三行反過來了敲长,而在不使用late
修飾符也就是沒有懶加載的情況下,當(dāng)我們實例化構(gòu)造器時就直接調(diào)用了獲取值的方法秉继。而加了late
修飾符后是在使用該字段的時候才會去進行獲取潘明。
總結(jié)
空安全的引入讓我的代碼變得更加可靠
- 在類型上都必須是非空的,當(dāng)然你也可以添加
?
變成可空的秕噪,用空斷言運算符!
進行使用钳降。 - 可選參數(shù)都必須是非空的,可以使用
required
來構(gòu)建一個非可選命名參數(shù)腌巾。 -
List
類現(xiàn)在不再允許包含未初始化的元素遂填。 -
late
修飾符在運行時檢查铲觉,能夠使用非空類型和final
,它同時提供了對字段延遲初始化的支持吓坚。