学习最忌讳的是盲目、没有计划、碎片化的知识点很难串成一个体系。 学过的都忘记了,笔试什么也想不起来。 这里我整理了一些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!');
}
如果我们提前读取描述参数,也会导致程序异常。
再说一遍:空安全只是帮助我们在代码编辑阶段尽早检测到可能的空异常,但并不意味着程序不会出现空异常。 开发者需要确定代码的边界,以保证程序的健壮运行!
听到这个消息后我给你留了一份作业。 如何在空中安全下编写鞋厂单例。 欢迎您在评论区留下您的答案。 我会在周五公布答案~。
以上内容均来自网络搜集,如有侵权联系客服删除