空包 Flutter面试最常问以及Flutter中最核心的几块知识
2023-09-21
941浏览
空包 Flutter面试最常问以及Flutter中最核心的几块知识空安全。空安全究竟是什么?空安全。要想弄明白空安全是什么,我们先要知道空安全帮我们解决了什么?模式下会抛出空异常,屏幕爆红提示。二、如何使用空安全?那么空安全包含哪些内容,我们在日常开发的时候该如何使用?在空安全中,所有类型在默认情况下都是非空的。四、空安全并不意味没有空异常这几个练习,也更加的反应了安全的作用:空安全在代码编辑阶段帮助我们提前发现可能出现的空异常问题。

学习最忌讳的是盲目、没有计划、碎片化的知识点很难串成一个体系。 学过的都忘记了,笔试什么也想不起来。 这里我整理了一些Flutter笔试中最常见的问题以及Flutter框架中的一些核心知识。 欢迎关注,共同进步。

欢迎搜索公众号:Attack Flutter 或 runflutter。 这里收集了最详细的Flutter进阶和优化手册。 关注我,解答你的疑问,获取我的最新文章~

编者注

在 Flutter 2.0 中,一个重要的升级是 Dart 支持 null 安全。 Alex用心为我们翻译了很多关于null safety的文章:迁移手册、深入理解null safety等。通过迁移手册空包,我也将fps_monitor迁移到了null safety。 但是适应了项目之后,我们在日常开发中应该如何使用呢? 到底什么是航空安全? 下面我们将通过几个练习来快速入门Flutter空中安全。

1. 航空安全解决什么问题?

要了解什么是空中安全,我们首先要知道空中安全帮助我们解决什么问题?

我们先看一个例子

void main() {
  String stringNullException;
  print(stringNullException.length);
}

在适配空安全之前,这段代码在编译阶段不会有任何提示。 但实际上这是一段有问题的代码。 在调试模式下,将抛出空异常并且屏幕将爆红。

I/flutter (31305): When the exception was thrown, this was the stack:
I/flutter (31305): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)

在发布模式下,此异常会使整个屏幕变黑。

这是一个典型案例。 stringNullException为空,没有形参,我们调用了.length方法,导致程序异常。

相同代码适配空安全后,编译时会给出错误信息,开发者可以及时修复。

所以简单来说,空安全可以帮助我们在代码编辑阶段尽早发现可能的空异常,但这并不意味着程序中不会出现空异常。

2.如何使用空安全?

这样空洞的安全性都包含哪些内容,我们在日常开发中又该如何使用呢? 下面我们就通过几个练习来学习。

1. 非空类型和可为空类型

在空安全中,默认情况下所有类型都是非空的。 例如,如果您有一个 String 类型的变量,它应该始终包含一个字符串。

如果希望 String 类型的变量接受任意字符串或 null,请在类型名称后添加问号(?)来指示该变量可以为 null。 比如String类型? 可以包含任何字符串,也可以为空。

练习 A:不可为空和可为空类型

void main() {
  int a;
  a = null; // 提示错误,因为 int a 表示 a 不能为空
  print('a is $a.');
}

这段代码通过int声明变量a为非空变量,执行a=null时报错。 可以改成int吗? 输入,允许 a 为空:

void main() {
  int? a; // 表示允许 a 为空
  a = null; 
  print('a is $a.');
}

练习 B:类库的可为空类型

void main() {
  List<String> aListOfStrings = ['one', 'two', 'three'];
  List<String> aNullableListOfStrings = [];
  // 报错提示,因为泛型 String 表示非 null
  List<String> aListOfNullableStrings = ['one', null, 'three']; 
  print('aListOfStrings is $aListOfStrings.');
  print('aNullableListOfStrings is $aNullableListOfStrings.');
  print('aListOfNullableStrings is $aListOfNullableStrings.');
}

在本练习中,由于aListOfNullableStrings变量的类型是List,它代表一个非空的String链表,因此在创建过程中提供了一个null元素,从而导致错误。 因此,null可以改为其他字符串,或者在子类中表示为可为空的字符串。

void main() {
  List<String> aListOfStrings = ['one', 'two', 'three'];
  List<String> aNullableListOfStrings = [];
  // 数组元素允许为空,所以不再报错
  List<String?> aListOfNullableStrings = ['one', null, 'three'];
  print('aListOfStrings is $aListOfStrings.');
  print('aNullableListOfStrings is $aNullableListOfStrings.');
  print('aListOfNullableStrings is $aListOfNullableStrings.');
}

2. 空断言运算符 (!)

如果您确定可空表达式不为空,则可以使用空断言运算符! 让 Dart 将其视为非空。 通过增加 ! 在表达式之后,您可以将其形式参数赋予非空变量。

练习 A:空断言

/// 这个方法的返回值可能为空
int? couldReturnNullButDoesnt() => -3;
void main() {
  int? couldBeNullButIsnt = 1;
  List<int?> listThatCouldHoldNulls = [2, null, 4];
  // couldBeNullButIsnt 变量虽然可为空,但是已经赋予初始值,因此不会报错
  int a = couldBeNullButIsnt;
  // 列表泛型中声明元素可为空,与 int b 类型不匹配报错
  int b = listThatCouldHoldNulls.first; // first item in the list
  // 上面声明这个方法可能返回空,而 int c 表示非空,所以报错
  int c = couldReturnNullButDoesnt().abs(); // absolute value
  print('a is $a.');
  print('b is $b.');
  print('c is $c.');
}

在本练习中,技术couldReturnNullButDoesnt和字段listThatCouldHoldNulls都被声明为可空类型,并且前面的变量b和c被声明为不可空类型,因此报告错误。 你可以加 ! 表达式末尾,表示操作不为空(必须确认此表达式不会为空,否则仍可能导致空指针异常)。 更改如下:

int? couldReturnNullButDoesnt() => -3;
void main() {
  int? couldBeNullButIsnt = 1;
  List<int?> listThatCouldHoldNulls = [2, null, 4];
  int a = couldBeNullButIsnt;
  // 添加 ! 断言 表示非空,赋值成功
  int b = listThatCouldHoldNulls.first!; // first item in the list
  int c = couldReturnNullButDoesnt()!.abs(); // absolute value
  print('a is $a.');
  print('b is $b.');
  print('c is $c.');
}

3. 类型改进

Dart 的流程分析已扩展到考虑归零。 不能为 null 的可空变量将被视为非 null 变量。 这种行为称为类型提升。

bool isEmptyList(Object object) {
  if (object is! List) return false;
  // 在空安全之前会报错,因为 Object 对象并不包含 isEmpty 方法
  // 在空安全后不报错,因为流程分析会根据上面的判断语句将 object 变量提升为 List 类型。
  return object.isEmpty; 
}

这段代码在成为空安全之前会报错,因为对象变量是Object类型并且不包含isEmpty技巧。

null安全后就不会报错了,因为流程分析会根据前面的判断语句,将对象变量增加为List类型。

练习 A:明确地形参数

void main() {
  String? text;
  //if (DateTime.now().hour < 12) {
  //  text = "It's morning! Let's make aloo paratha!";
  //} else {
  //  text = "It's afternoon! Let's make biryani!";
  //}
  print(text);
  // 报错提示,text 变量可能为空
  print(text.length);
}

在此代码中,我们使用 String? 声明一个可为空的变量text,前面直接使用text.length。 Dart 会认为这是不安全的并报告错误消息。

但是当我们把之前注释掉的代码去掉后,就不再报错了。 由于Dart确定了text参数的位置,感觉文本不会为空,因此将文本提升为非空类型(String),不再报错。

练习 B:空值检测

int getLength(String? str) {
  // 此处报错,因为 str 可能为空
  return str.length;
}
void main() {
  print(getLength('This is a string!'));
}

这种情况下,由于str可能为空,使用str.length会提示错误。 通过类型改进空包,我们可以这样改:

int getLength(String? str) {
  // 判断 str 为空的场景 str 提升为非空类型
  if (str == null) return 0;
  return str.length;
}
void main() {
  print(getLength('This is a string!'));
}

提前判断str为空的场景,以便后续str的类型从String增加? (nullable) 转 String (非空),不报错。

3.迟到的关键词

有时变量(例如数组或类中的顶级变量)应该是非空的,但您不能立即给它们提供形式参数。 对于这些情况,请使用 Late 关键字。

当您放在变量声明后面时,它会告诉 Dart 以下信息:

练习A:晚用

class Meal {
  // description 变量没有直接或者在构造函数中赋予初始值,报错
  String description;
  void setDescription(String str) {
    description = str;
  }
}
void main() {
  final myMeal = Meal();
  myMeal.setDescription('Feijoada!');
  print(myMeal.description);
}

本例中,Meal类包含非空的变量描述,但该变量没有直接或在构造函数中分配初始值,因此会报错。 在这些情况下,我们可以使用 Late 关键字来指示变量被延迟:

class Meal {
  // late 声明不在报错
  late String description;
  void setDescription(String str) {
    description = str;
  }
}
void main() {
  final myMeal = Meal();
  myMeal.setDescription('Feijoada!');
  print(myMeal.description);
}

练习 B:将 Late 与循环引用一起使用

class Team {
  // 非空变量没有初始值,报错
  final Coach coach;
}
class Coach {
  // 非空变量没有初始值,报错
  final Team team;
}
void main() {
  final myTeam = Team();
  final myCoach = Coach();
  myTeam.coach = myCoach;
  myCoach.team = myTeam;
  print('All done!');
}

通过添加late关键字解决该错误。 请注意,我们不需要删除 Final。 在latefinal中声明的变量意味着你只需要设置它们的值一次,然后它们就变成只读变量。

class Team {
  late final Coach coach;
}
class Coach {
  late final Team team;
}
void main() {
  final myTeam = Team();
  final myCoach = Coach();
  myTeam.coach = myCoach;
  myCoach.team = myTeam;
  print('All done!');
}

练习 C:延迟关键字和延迟加载

int _computeValue() {
  print('In _computeValue...');
  return 3;
}
class CachedValueProvider {
  final _cache = _computeValue();
  int get value => _cache;
}
void main() {
  print('Calling constructor...');
  var provider = CachedValueProvider();
  print('Getting value...');
  print('The value is ${provider.value}!');
}

此练习没有错误,但您可以查看运行此代码的输出:

Calling constructor...
In _computeValue...
Getting value...
The value is 3!

复制第一句Callingconstructor...后,生成了CachedValueProvider()对象。 生成过程会初始化它的变量final_cache=_computeValue(),所以复制第二句In_computeValue...,然后复制后面的单词和句子。

当我们将late关键字添加到_cache变量时,结果是什么?

int _computeValue() {
  print('In _computeValue...');
  return 3;
}
class CachedValueProvider {
  // late 关键字,该变量不会在构造的时候初始化
  late final _cache = _computeValue();
  int get value => _cache;
}
void main() {
  print('Calling constructor...');
  var provider = CachedValueProvider();
  print('Getting value...');
  print('The value is ${provider.value}!');
}

日志如下:

Calling constructor...
Getting value...
In _computeValue...
The value is 3!

日志中In_computeValue...的执行有延迟。 当然,_cache变量在构造期间并没有初始化,而是延迟到使用时才初始化。

4. 空安全并不意味着没有空异常

这些练习也越来越多地体现了安全性的作用:空安全性帮助我们在代码编辑阶段的早期检测可能的空异常。 但请注意,这并不意味着空异常不存在。例如下面的例子

void main() {
  String? text;
  print(text);
  // 不会报错,因为使用 ! 断言 表示 text 变量不可能为空
  print(text!.length);
}

因为text!.length意味着变量text不能为空。 但实际上,text可能会因为各种诱因(例如json被解析为null)而为空,导致程序异常。

使用late关键字的场景也存在:

class Meal {
  // late 声明编辑阶段将不会报错
  late String description;
  void setDescription(String str) {
    description = str;
  }
}
void main() {
  final myMeal = Meal();
  // 先去读取这个未初始化变量,导致异常
  print(myMeal.description);
  myMeal.setDescription('Feijoada!');
}

如果我们提前读取描述参数,也会导致程序异常。

再说一遍:空安全只是帮助我们在代码编辑阶段尽早检测到可能的空异常,但并不意味着程序不会出现空异常。 开发者需要确定代码的边界,以保证程序的健壮运行!

听到这个消息后我给你留了一份作业。 如何在空中安全下编写鞋厂单例。 欢迎您在评论区留下您的答案。 我会在周五公布答案~。

以上内容均来自网络搜集,如有侵权联系客服删除

图文阅读
 
QQ在线咨询
客服热线
客服微信号
STU006