簡(jiǎn)單理解Dart空安全

一、前言

dart 2.12版本之前,任何一種類型都可以為null,所有的類型默認(rèn)都是null,這樣也就意味著我們每次使用一個(gè)變量的時(shí)候,可能都需要判斷一個(gè)變量是否為空,否則就可能拋出NoSuchMethodError異常,這樣在我們的開(kāi)發(fā)中就很頭痛,一不小心就會(huì)出現(xiàn)NoSuchMethodError異常,導(dǎo)致程序崩潰,為了減少這種情況的發(fā)生,我們就不得不在代碼中添加很多的冗余判斷代碼。
后來(lái)官方可能也是實(shí)在容忍不了這種現(xiàn)象,就像其他語(yǔ)言比如Swift、Kotlin一樣,引入了健全的空安全檢查機(jī)制。

下面這個(gè)例子,就是目前我們?cè)跊](méi)有引入空安全時(shí),要面臨的問(wèn)題。我們聲明一個(gè)變量,并且調(diào)用它的一些API,在使用之前必須要判斷是否為空:

int nullableInt; //null
if (nullableInt != null) {
    print('nullableInt ==== ${nullableInt.toDouble()}');
} else {
    print('nullableInt ==== $nullableInt');//打印結(jié)果:nullableInt ==== null
}


//使用前沒(méi)有判空
print('nullableInt ==== ${nullableInt.toDouble()}');

//拋出異常
Unhandled exception:
NoSuchMethodError: The method 'toDouble' was called on null.
Receiver: null
Tried calling: toDouble()

二、什么是空安全?

空安全是一種對(duì)可能為空的變量在編譯期就進(jìn)行校驗(yàn)的一種審查機(jī)制。這種檢查機(jī)制對(duì)所有變量的默認(rèn)值也進(jìn)行了改變,以前默認(rèn)為null,現(xiàn)在除非時(shí)候顯式聲明為可為空,所有都按照非空處理,在可能為null的情況下,如果不進(jìn)行非空判斷,dart的分析器就會(huì)報(bào)錯(cuò),從而減少我們?cè)陂_(kāi)發(fā)中的NoSuchMethodError情況的出現(xiàn)。

三、使用

要使用空安全機(jī)制,需要把dart版本設(shè)置為2.12版本及以上,具體環(huán)境配置在pubspec.yaml文件中,如果想聲明一個(gè)可空變量,需要顯示在類型后面添加?

name: dart_study
description: A simple command-line application.
# version: 1.0.0
# homepage: https://www.example.com

environment:
  sdk: '>=2.12.0 <3.0.0' #修改dart環(huán)境版本,支持空安全

#dependencies:
#  path: ^1.7.0

dev_dependencies:
  pedantic: ^1.9.0

3.1 基本類型

創(chuàng)建變量時(shí),如果想要一個(gè)變量可為null,需要顯示聲明為nullable類型,具體就是使用?進(jìn)行聲明。比如String?int?、double?等等。看下面的實(shí)例:
例子1:顯式創(chuàng)建一個(gè)可為nullint類型變量

int? nullableInt;
print(nullableInt.toDouble()); //The method 'toDouble' can't be unconditionally invoked because the receiver can be 'null'.

這種變量在使用時(shí),必須要判斷是否為空,直接使用話,編譯不通過(guò),會(huì)拋出異常,告知使用了可空的變量

bin/dart_study.dart:4:21: Error: Method 'toDouble' cannot be called on 'int?' because it is potentially null.
Try calling using ?. instead.
  print(nullableInt.toDouble());
                    ^^^^^^^^

正確的寫法應(yīng)該是

print(nullableInt?.toDouble() ?? '為空了');

例子2:創(chuàng)建一個(gè)非空的int類型變量,如果判斷是否為null時(shí),會(huì)有個(gè)警告,這時(shí)我們就可以減少不必要的判斷,而直接使用變量。

int nonNullInt = 7;
print(nonNullInt);
if (nonNullInt != null) {//The operand can't be null, so the condition is always true. 
    print(nonNullInt);
}

3.2 集合類型

對(duì)于List、Map、Set類型來(lái)說(shuō),在使用時(shí),不僅要看集合變量是否可空,還要看里面的數(shù)據(jù)類型是否可空。對(duì)于ListSet我們可以大致總結(jié)為下面的表格:

類型 列表是否可為空 元素是否可為空 說(shuō)明
List<String> 一個(gè)包含非空字符串的非空列表
List<String>? 一個(gè)包含非空字符串的可空列表
List<String?> 一個(gè)包含可空字符串的非空列表
List<String?>? 一個(gè)包含可空字符串的可空列表
var nullableList = [1, null, 3];
print(nullableList[0]?.toDouble()); //校驗(yàn)空

var nonNullList = [1, 2, 3];
print(nonNullList[0].toDouble());//無(wú)需校驗(yàn)空

同樣,對(duì)于Map類型也可以總結(jié)為一個(gè)表格:

類型 Map是否可為空 元素是否可為空 說(shuō)明
Map<String: int> 所有元素不可空的非空Map,但取值結(jié)果表面不可為空,如果使用的是無(wú)效Key,取出的是空
Map<String: int>? 所有元素不可空的可空Map,但取值結(jié)果表面不可為空,如果使用的是無(wú)效Key,取出的是空
Map<String: int?> 元素可空的非空Map,key是否有效取值都可為空
Map<String: int?>? 元素可空的可空Map,key是否有效取值都可為空

MapList、Set不同的一點(diǎn)是,盡管Map規(guī)定了元素非空,但是如果使用的是無(wú)效Key取值的話,仍然返回的是一個(gè)可空值,所以默認(rèn)情況下,我們?cè)谑褂米兞拷邮杖〕龅闹禃r(shí),一般都需要使用可空類型接收。

var nullableMap = <String, int?>{'one': 1};
int? result = nullableMap['two'];
print('取出的值為${result?.toDouble()}'); //必須校驗(yàn)空

var nonNullMap = <String, int>{'one': 1};
print('取出的值為${nonNullMap['two']?.toDouble()}'); //必須校驗(yàn)空

四、空斷言運(yùn)算符

當(dāng)然,如果我們確定某個(gè)Key是有值的,但是編輯器認(rèn)為它可能為null,這個(gè)時(shí)候我們不想判斷一次,我們可以使用!進(jìn)行一次強(qiáng)轉(zhuǎn),

但是如果不能確定有值的情況,萬(wàn)萬(wàn)不可使用這種方式。

var nullableMap = <String, int?>{'one': 1};
int resultOne = nullableMap['one']!; //我們能夠確?!痮ne‘一定有值
print('取出的值為${resultOne.toDouble()}');

五、關(guān)鍵字 late

顧名思義,晚點(diǎn)。。。我們可以使用這個(gè)關(guān)鍵字對(duì)變量進(jìn)行修飾,使用late修飾的變量,可以認(rèn)為是一種懶加載,分析器也不會(huì)要求我們立刻對(duì)一個(gè)非空類型進(jìn)行賦值,只要我們能夠保證在第一次使用的時(shí)候進(jìn)行賦值,就可以了。

比如我們定義一個(gè)類Person:

class Person {
  int age;
  String name;

  ///
  /// Non-nullable instance field 'age' must be initialized. (Documentation)  
  /// Try adding an initializer expression, or add a field initializer in this constructor, 
  /// or mark it 'late'.
  ///
  Person();
}

我們可以通過(guò)添加late關(guān)鍵字,延遲加載:

class Person {
  late int age;
  late String name;
  Person(); //編譯器不會(huì)報(bào)錯(cuò)
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容