Flutter 空安全精讲

前言

看完本章节你将知道:

  • 什么是空安全
  • 空安全的原则
  • 如何启用空安全
  • 空安全的类型
  • 空断言运算符
  • late修饰符

视频教程

空安全视频教程地址

空安全介绍

空安全null-safe type system是在Dart 2.12中引入的,如果开启空安全,默认情况下代码中的类型不能为空,也就是说除非声明该类型是可以为空的,否则值不能为空。

空安全是官方极力推荐的,现在很多流行的第三方库全部都是支持了空安全,所以空安全是我们必须要掌握的知识。

空安全的原则

  • 默认不可空:除非你将变量显式声明为可空,否则它一定是非空的类型。
  • 渐进迁移:可以自由地选择何时进行迁移,多少代码会进行迁移,还可以使用混合模式的空安全,在一个项目中同时使用空安全和非空安全的代码。
  • 完全可靠:对代码的健全性带来的所有优势——更少的 BUG、更小的二进制文件以及更快的执行速度。

启用空安全

空安全在Dart 2.12 和 Flutter 2.0中可用,可通过指定Dart SDK版本为2.12那么就会开启空安全

environment:
  sdk: ">=2.12.0 <3.0.0"

空安全类型

空安全分可为空和不可为空,可为空就是变量、形参都可以传null值,不可为空变量、形参一定不能为空,我们在使用空安全的时候会碰到下面三种情况,接下来的代码演示我们都是dart 2.12开启空安全为准

  • 变量为空编译时报错
  • 传递参数时为空编译时报错
  • 方法需要返回参数时必须返回,否则编译时报错

变量可为空和不可为空的使用对比

声明一个空变量

这里我声明了一个变量为name的字符串属性,但并没有赋值,所以name的内存地址存的是一个空的字符串。

String name;

错误提示

它提示说不可为空的变量一定要进行初始化

[图片上传失败...(image-30e8ab-1632465574247)]

标明变量可为空

我们可以在Stirng后面加一个?号,该符号标明name这个变量可以为空,这个时候我们发现定义时不会出现报错,但是我们在使用name属性的时候会发现有一个报错,它报错的信息是String?不能分配给一个String,如下:

[图片上传失败...(image-da9c52-1632465574247)]

空断言运算符

在上面我们使用name这个属性的时候会出现一个报错,我们可以使用空断言运算符!来标明该值不会为空,所以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)
====================================================================================================

参数可为空和不可为空的使用对比

声明一个需要传参的方法

下面这段代码我们定义了一个可选参数为name的字符串,在空安全的机制下,我们必须保证传入的参数不能为空,那么将会收到如下报错:

_upperCase({String name}) {
  setState(() {
    value.toUpperCase();
  });
}

floatingActionButton: FloatingActionButton(
  onPressed: _upperCase(),
  child: Icon(Icons.add),
), 

错误提示

它提示参数name的类型不能为null值,但是被隐式转换成了null,我们碰到这种情况有两种解决方案,我们接下来看看如果解决

[图片上传失败...(image-d2dd46-1632465574247)]

解决方法

第一种:给可选参数添加默认值

_upperCase({String name = "Jimi"}) {
  setState(() {
    name.toUpperCase();
  });
}

第二种:给可选参数增加required

_upperCase({required String name}) {
  setState(() {
    name.toUpperCase();
  });
}

当给可选参数增加required后,说明可选参数name必传,这样会导致调用处会报错,我们来在调用时把name传进去,如下:

floatingActionButton: FloatingActionButton(
  onPressed: _upperCase(name: name!),
  child: Icon(Icons.add),
), 

这里使用name!来进行传入,在上面我们说过这个是空断言运算符,只有你明确知道该变量会有值时才使用,那么我们这里这样使用还是会报错,所以我们来继续优化,把name声明一个有值的变量,如下:

String name = "Jimi";

floatingActionButton: FloatingActionButton(
  onPressed: _upperCase(name: name),
  child: Icon(Icons.add),
), 

方法的返回值可为空和不可为空的使用对比

声明一个需要有返回值的方法

下面我声明了一个方法用来将name`的转换为大写,在我们没有引入空安全之前下来代码是不会报错的,但是引入空安全机制后你会收到如下报错:

String name = "Jimi";

String _upperCaseName(String name) {
}

错误提示

它提示返回类型可能是不能为null的类型,可以尝试在最后添加 returnthrow语句。

[图片上传失败...(image-1eb4c4-1632465574247)]

解决方法

第一种:添加return语句

String _upperCaseName(String name) {
  return name.toUpperCase();
}

第二种:添加throw语句

String _upperCaseName(String name) {
  throw "no return";
}

自定义类字段可为空和不可为空使用对比

声明一个User类

我们在平时的开发过程中常常会自定义类,而自定义类中的属性一开始都不会赋值,那么我们引入空安全的情况下将会报错,如下所示:

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必须要有初始化值,或者标记为late

[图片上传失败...(image-17a779-1632465574247)]

解决方法

第一种:声明变量可为空

我们采用声明变量可为空的方式,在使用的时候我们使用空断言运算符!来进行写入和读取操作,但是这也暗示着null对于字段来说是有用的值,这样就背道而驰了,所以建议不要采用这种方式,接下来我们来看看第二种解决方案late修饰符。

String? name;
int? age;

第二种:late修饰符

因为late涉及内容较多,我们拿一个章节来讲解。

late修饰符

late修饰符是在运行时而非编译时对变量进行约束,这也就是说late相当于何时执行对变量的强制约束。

比如本示例中name字段用late修饰后并不一定已经被初始化,每次它被读取时,都会插入一个运行时的检查,以确保它已经被赋值。如果并未赋值,就会抛出一个异常,给变量加上String类型就是说:“我的值绝对是字符串”,而加上late修饰符意味着:"每次运行我都要检查是不是真的"。

当使用late修饰符总结如下:

  • 先不给变量赋值
  • 稍后再给变量赋值
  • 在使用前会给变量赋值
  • 在使用前不赋值,将会报错
late String name;
late int age;

late修饰符懒加载

懒加载也有一种说法是初始化延迟执行,当你用late修饰变量后,那么它将会被延迟到字段首次被访问时才会执行,而不是在实例化构造器时就初始化了。而且实例字段的初始化内容是无法访问this的,因为在所有的初始化方法完成前,是无法访问到新的实例对象。但是,使用了late的话就可以访问到this、调用方法以及访问实例的字段。

不使用late修饰符

我们这里就是创建一个User类,name属性直接调用getUserName()方法,最后当我们实例化一个User对象并获取name的值,我们来看下控制台输出:

void main() {
  print("调用构造函数");
  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: 调用构造函数
flutter: 返回用户的名称
flutter: 获取值
flutter: 获取的值为: Jimi

使用late修饰符

代码和上面一样,只是在定义字段处加了一个late修饰符,我们来看一下控制台输出:

void main() {
  print("调用构造函数");
  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: 调用构造函数
flutter: 获取值
flutter: 返回用户的名称
flutter: 获取的值为: Jimi

我们明显可以看到第二行和第三行反过来了,而在不使用late修饰符也就是没有懒加载的情况下,当我们实例化构造器时就直接调用了获取值的方法。而加了late修饰符后是在使用该字段的时候才会去进行获取。

总结

空安全的引入让我的代码变得更加可靠

  • 在类型上都必须是非空的,当然你也可以添加?变成可空的,用空断言运算符!进行使用。
  • 可选参数都必须是非空的,可以使用required来构建一个非可选命名参数。
  • List 类现在不再允许包含未初始化的元素。
  • late修饰符在运行时检查,能够使用非空类型和 final,它同时提供了对字段延迟初始化的支持。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容