第3章 Dart语言快速入门(上):Dart语言特性概述、变量与数据类型、函数与匿名函数、运算符与流程控制、我在项目中遇到的Dart类型转换坑
各位同学,欢迎来到Dart语言的世界。说实话,很多从Java或Kotlin转过来的朋友,第一次看到Dart代码会觉得有点“怪”。但别急,我当年也是这么过来的。用上一段时间你就会发现,Dart其实是一门设计得非常“顺手”的语言。
这一章我们不讲太多理论,直接上干货。我会把Dart最核心的几个知识点拆开揉碎了讲,顺便分享一些我在实际项目中踩过的坑。嗯,尤其是那个类型转换的坑,当年可是让我排查了一整个下午。
3.1 Dart语言特性概述
Dart是Google亲儿子,专门为UI开发而生。它有几个特性,我个人觉得特别重要:
- 强类型但可选类型:你可以显式声明类型,也可以直接用
var让编译器去推断。我习惯在公共API里写清楚类型,内部逻辑用var省事。 - 一切皆对象:包括数字、布尔值、甚至函数。没有基本类型这一说,所有东西都继承自
Object。 - 空安全(Null Safety):这是Dart 2.12引入的杀手锏。简单说,就是编译器帮你盯着,不让空指针偷偷溜进你的代码里。
- 支持AOT和JIT编译:开发时用JIT热重载爽到飞起,发布时用AOT编译成原生机器码,性能杠杠的。
核心观点:Dart的设计哲学就是“为UI而生”。它的异步机制、集合操作、以及类型系统,都在为构建流畅的用户界面服务。
3.2 变量与数据类型
先说说变量声明。Dart里声明变量有几种方式,你想想看,是不是跟其他语言有点像?
// 显式类型声明
String name = '张三';
int age = 25;
// 类型推断
var city = '北京'; // 编译器自动推断为String
dynamic anything = '可以是任何类型'; // 动态类型,慎用
// 常量
final String country = '中国'; // 运行时常量
const double pi = 3.14159; // 编译时常量
这里有个细节我要强调一下:final 和 const 的区别。说白了,const 是编译时就确定的值,而 final 是运行时第一次赋值后就不能再改了。我在项目中遇到过一个问题:用 const 声明了一个 DateTime.now(),结果编译报错。为什么?因为 DateTime.now() 是运行时才能确定的,只能用 final。
Dart的数据类型,我列个表大家看看:
| 类型 | 说明 | 示例 |
|---|---|---|
int |
整型,64位 | int a = 42; |
double |
双精度浮点型 | double b = 3.14; |
String |
字符串,UTF-16编码 | String s = 'Hello'; |
bool |
布尔值,只有 true 和 false |
bool flag = true; |
List |
有序集合,类似数组 | List<int> nums = [1, 2, 3]; |
Map |
键值对集合 | Map<String, int> map = {'a': 1}; |
Set |
无序不重复集合 | Set<String> set = {'a', 'b'}; |
Runes |
Unicode字符 | Runes runes = Runes('\u{1f600}'); |
小技巧:字符串插值用 ${expression},如果只是一个变量名,可以省略花括号。比如 '我的名字是$name'。这个语法糖我每天都在用,写起来特别顺手。
3.3 函数与匿名函数
Dart里的函数是一等公民。什么意思?就是函数可以像变量一样被赋值、被传递、被当作参数。这一点跟JavaScript很像。
// 普通函数
int add(int a, int b) {
return a + b;
}
// 箭头函数(语法糖)
int multiply(int a, int b) => a * b;
// 可选参数
void greet(String name, {String? title, int age = 18}) {
print('$title $name, 年龄$age');
}
// 调用
greet('李四', title: '先生'); // 输出:先生 李四, 年龄18
匿名函数,说白了就是没有名字的函数。在Flutter里,你几乎天天跟它打交道——比如按钮的点击回调:
// 匿名函数赋值给变量
var myFunction = (int x) {
return x * 2;
};
// 作为参数传递
List<int> numbers = [1, 2, 3, 4];
var doubled = numbers.map((e) => e * 2).toList();
print(doubled); // [2, 4, 6, 8]
我个人习惯在回调逻辑简单时用箭头函数,逻辑复杂时用花括号包裹的完整函数体。这样代码可读性更好。
3.4 运算符与流程控制
Dart的运算符大部分跟C系语言一样,但有几个特殊的我得提一下:
- 级联运算符
..:可以连续对同一个对象进行操作。这在配置UI时特别爽。 - 空安全运算符
??和??=:前者是“如果为空则使用默认值”,后者是“如果为空则赋值”。 - 类型测试运算符
is和as:is判断类型,as强制转换。
// 级联运算符
var sb = StringBuffer()
..write('Hello ')
..write('World')
..write('!');
// 空安全运算符
String? nullableName;
var name = nullableName ?? '默认名字'; // 如果nullableName为null,使用默认值
// 类型测试
if (obj is String) {
print('这是一个字符串');
}
流程控制方面,if-else、for、while 这些跟Java几乎一样。但有个 for-in 循环我特别喜欢用:
var fruits = ['苹果', '香蕉', '橘子'];
for (var fruit in fruits) {
print('我喜欢吃$fruit');
}
还有 switch-case,Dart的 switch 支持字符串和枚举,比Java的灵活多了。不过要注意,每个 case 后面必须跟 break,否则会报错——这是Dart故意设计的,防止你忘记写 break 导致穿透。
3.5 我在项目中遇到的Dart类型转换坑
好了,终于到了我最想分享的部分。这个坑,我曾经踩过,而且踩得特别惨。
事情是这样的:有一次我从后端API拿到一个JSON数据,里面有个字段叫 score,文档里写的是 int 类型。我用 json['score'] as int 去转换,结果运行时直接崩了。为什么?因为后端实际返回的是 double 类型——比如 95.0。
你想想看,95.0 as int 在Dart里是不允许的。Dart的类型转换非常严格,它不会帮你做隐式类型转换。这一点跟Java不一样,Java里 double 转 int 会截断小数部分,但Dart直接抛异常。
警告:Dart的 as 关键字是强制转换,如果类型不匹配,会直接抛出 TypeError。千万不要在生产代码里随意使用 as 做类型转换,除非你100%确定类型是对的。
那正确的做法是什么?我后来总结了一套“安全转换”方案:
// 安全转换方案
dynamic value = json['score'];
// 方案1:使用 is 判断
if (value is int) {
int score = value;
} else if (value is double) {
int score = value.toInt();
}
// 方案2:使用 try-catch 包裹
try {
int score = value as int;
} catch (e) {
// 处理转换失败的情况
int score = (value as double).toInt();
}
// 方案3:使用 toString() 中转(我最推荐的方式)
int score = int.parse(value.toString());
方案3为什么最推荐?因为 toString() 几乎不会失败,而 int.parse() 对字符串的容错性比 as 好得多。当然,如果字符串里包含非数字字符,int.parse() 也会抛异常,所以最好再包一层 try-catch。
还有一个坑:List 的类型转换。比如你从JSON解析出来一个 List<dynamic>,想转成 List<String>,直接 as List<String> 是不行的。Dart的泛型在运行时会被擦除,所以这种转换会静默成功,但当你从列表里取元素时,类型不对就会崩。
// 错误的做法
List<dynamic> dynamicList = ['a', 'b', 123];
List<String> stringList = dynamicList as List<String>; // 编译不报错,运行也不报错
String first = stringList[0]; // 正常
String third = stringList[2]; // 运行时崩溃!因为第三个元素是int
// 正确的做法
List<String> safeList = dynamicList.map((e) => e.toString()).toList();
嗯,这个坑我当年排查了整整一个下午。最后发现是接口返回的数据里混了一个数字,而我的 as 转换没有做任何校验。从那以后,我给自己定了个规矩:从外部来源(网络、文件、用户输入)获取的数据,一律做显式类型检查和转换,绝不用 as 偷懒。
总结一下:Dart的类型系统很严格,这是好事——它帮你提前发现很多潜在问题。但严格也意味着你要多写几行转换代码。别嫌麻烦,这些代码将来会帮你省下无数调试时间。
好了,这一章的内容就到这里。下一章我们会继续深入Dart的面向对象特性,包括类、继承、混入(Mixin)这些在Flutter开发中天天用的东西。到时候见!