
一、前言
学习 Dart 数据类型时,可以先想象一个超市。
超市里有饮料、蔬菜、水果、零食、日用品。不同商品不能全部随便塞进同一个容器里:鸡蛋需要防摔的盒子,饮料需要瓶子,散装糖果需要袋子,冷冻食品需要冷柜。
代码里的数据也是一样。
数字、文字、真假、列表、集合、键值对、函数,它们都有各自更适合的“容器”。这个容器在编程里就叫数据类型。
数据类型不是为了增加记忆负担,而是为了告诉 Dart:这份数据是什么、能做什么操作、应该如何存储和检查。
这一篇我们系统梳理 Dart 中常见的数据类型。
二、一句话理解
Dart 数据类型就是数据的分类规则,它决定一个值能表示什么内容、能使用哪些方法、能参与哪些操作。
简单说:
int适合整数。double适合小数。String适合文字。bool适合真假。List适合有顺序的一组数据。Set适合去重的一组数据。Map适合键值对数据。
先知道每种类型解决什么问题,再去记它的方法,学习会轻很多。
三、Dart 数据类型总览
Dart 中的数据类型可以先分成四类:
| 分类 | 常见类型 | 主要作用 |
|---|---|---|
| 基本数据类型 | num、int、double、bool、String、dynamic |
表示数字、真假、文本和动态值 |
| 集合类型 | List、Set、Map |
存放一组数据 |
| 特殊类型 | Function、enum、Symbol、typedef |
表示函数、枚举、符号和类型别名 |
| 类型转换 | toString()、toInt()、toDouble() |
在不同类型之间转换 |
这张表是本文的地图。接下来我们一类一类展开。
四、基本数据类型
基本数据类型是最常见、最基础的一组类型。写 Dart 时几乎每天都会用到。
4.1 数字类型 num
num 是 Dart 数字类型的父类型,它下面有两个常见子类型:int 和 double。
void main() {num a = 10;num b = 12.5;print(a); // 10print(a.runtimeType); // intprint(b); // 12.5print(b.runtimeType); // double
}
虽然变量声明的是 num,但 Dart 会根据具体值判断它实际是 int 还是 double。
可以这样理解:
num是数字的大箱子。int是整数小盒子。double是小数小盒子。
num 适合你只关心“这是数字”,但暂时不关心它是整数还是小数的场景。
4.2 整数类型 int
int 用于表示整数。
void main() {int age = 18;int count = 100;print(age);print(count);
}
整数没有小数部分,适合表示年龄、数量、页码、索引、次数等。
Dart 里的 int 支持平台相关的整数范围。通常开发中不需要一开始就背范围,但要知道:如果数字特别大,可以使用 BigInt。
void main() {BigInt bigNumber = BigInt.parse('1200000213231241431212133322222223');print(bigNumber);print(bigNumber.runtimeType);
}
如果需要随机整数,可以使用 dart:math 里的 Random。
import 'dart:math';void main() {Random random = Random();print(random.nextInt(100)); // 生成 0 到 99 的随机整数
}
4.3 浮点数类型 double
double 用于表示小数。
void main() {double price = 19.9;double score = 98.5;print(price);print(score);
}
即使写成 4,如果变量类型是 double,输出时也会表现为小数形式。
void main() {double value = 4;print(value); // 4.0print(value.runtimeType); // double
}
double 还能表示一些特殊值:
void main() {print(double.infinity); // Infinityprint(double.negativeInfinity); // -Infinityprint(double.nan); // NaN
}
使用 double 时要知道一个重要点:浮点数可能出现精度问题。
void main() {double value = 0.1 + 0.2;print(value); // 0.30000000000000004
}
这不是 Dart 算错了,而是浮点数在计算机中的表示方式决定的。涉及金额、精确小数时,要格外谨慎。
4.4 数字类型基本操作
数字类型可以做加、减、乘、除、整除、取余。
void main() {num a = 4;int b = 5;print(a + b); // 9print(a - b); // -1print(a * b); // 20print(a / b); // 0.8print(a ~/ b); // 0print(a % b); // 4
}
几个容易忽略的点:
/的结果是double。~/是整除,结果是整数。%是取余,常用于判断是否整除。
数字也可以做类型转换:
void main() {int a = 4;double b = 5.1;print(a.toDouble()); // 4.0print(b.toInt()); // 5
}
数字可以比较和检查:
void main() {int a = 4;double b = 7.8;print(a.compareTo(b)); // -1,表示 a 小于 bprint(a.isNegative); // falseprint(a.isEven); // trueprint(a.isOdd); // false
}
常用数字方法还有:
void main() {int a = -10;num b = 5.834;print(a.abs()); // 10print(a.clamp(1, 5)); // 1print(b.ceil()); // 6print(b.floor()); // 5print(b.round()); // 6print(b.truncate()); // 5
}
这些方法可以这样记:
abs():求绝对值。clamp():把数限制在一个范围内。ceil():向上取整。floor():向下取整。round():四舍五入。truncate():直接截断小数部分。
4.5 布尔类型 bool
bool 只有两个值:true 和 false。
void main() {bool isLogin = true;bool isVip = false;print(isLogin);print(isVip);
}
它常用于条件判断。
void main() {bool hasTicket = true;if (hasTicket) {print('可以入场');} else {print('不能入场');}
}
可以把 bool 理解成代码里的开关:开是 true,关是 false。
4.6 字符串类型 String
String 用于表示文本。
void main() {String name = 'Dart';String language = "Flutter";print(name);print(language);
}
Dart 字符串可以用单引号,也可以用双引号。
字符串支持插值,也就是把变量嵌入字符串中。
void main() {String name = 'Dart';print('Hello, $name');
}
如果表达式比较复杂,可以使用 ${}。
void main() {int a = 3;int b = 5;print('结果是:${a + b}');
}
多行字符串可以使用三个引号。
void main() {String slogan = '''
I love Dart
I love Flutter
''';print(slogan);
}
要注意:Dart 中的字符串是不可变的。所谓修改字符串,本质上通常是创建一个新的字符串。
4.7 动态类型 dynamic
dynamic 表示这个变量可以存放任意类型的值。
void main() {dynamic value = 5;print(value);value = 'hello, Dart';print(value);
}
看起来很灵活,但也有代价:编译器不会帮你做严格类型检查,错误可能留到运行时才暴露。
void main() {dynamic value = 10;value = 'hello';print(value.toUpperCase());
}
如果你很确定数据结构,优先使用明确类型。dynamic 更适合处理临时不确定的数据,比如外部接口返回、动态 JSON、迁移旧代码等。
五、集合类型
集合类型用于存放一组数据。
如果基本类型像一个个商品,那么集合类型就像购物袋、收纳盒和标签柜。
5.1 列表 List
List 是有顺序的集合,允许重复元素。
void main() {List<int> numbers = [1, 2, 2, 5, 9];List<String> skills = ['Dart', 'Flutter', 'Android'];print(numbers);print(skills);
}
List 的特点:
- 有顺序。
- 可以通过下标访问。
- 允许重复。
- 适合表示一串有序数据。
void main() {List<String> names = ['Tom', 'Jerry', 'Lucy'];print(names[0]); // Tomnames.add('Lily');print(names);
}
如果你关心“第几个元素是什么”,通常应该先想到 List。
5.2 集合 Set
Set 是不重复的集合。
void main() {Set<int> numbers = {1, 2, 2, 2, 4, 5};print(numbers); // {1, 2, 4, 5}
}
Set 的特点:
- 不允许重复元素。
- 适合去重。
- 适合判断某个元素是否存在。
void main() {Set<String> tags = {'Dart', 'Flutter', 'Dart'};print(tags); // {Dart, Flutter}print(tags.contains('Dart')); // true
}
如果你关心“有没有重复”“是否包含某个值”,通常可以考虑 Set。
5.3 映射 Map
Map 是键值对集合。
void main() {Map<String, int> scores = {'小明': 5,'小红': 2,'小敏': 7,};print(scores);
}
Map 的特点:
- 由 key 和 value 组成。
- key 必须唯一。
- 可以通过 key 快速找到 value。
void main() {Map<String, int> scores = {'Tom': 90,'Jerry': 85,};print(scores['Tom']); // 90scores['Lucy'] = 100;print(scores);
}
如果你关心“通过一个名字找到对应数据”,通常应该想到 Map。
5.4 List、Set、Map 怎么选
三种集合很容易混在一起,可以这样判断:
| 类型 | 适合场景 | 关键特点 |
|---|---|---|
List |
商品列表、消息列表、步骤列表 | 有顺序、可重复 |
Set |
标签去重、唯一 ID 集合 | 不重复 |
Map |
用户信息、配置项、成绩表 | key-value |
简单记法:
- 要顺序,用
List。 - 要去重,用
Set。 - 要通过 key 查值,用
Map。
六、特殊类型
特殊类型不是每天都像 String、int 那样频繁出现,但它们在 Dart 里也很重要。
6.1 函数类型 Function
在 Dart 中,函数也可以作为值传递。
void hello() {print('hello, Dart!');
}void main() {Function fn = hello;fn();
}
这意味着我们可以把函数存到变量里,也可以把函数作为参数传递。
更推荐的写法是尽量写清楚函数类型。
void main() {int Function(int, int) add = (int a, int b) {return a + b;};print(add(3, 5));
}
明确函数类型之后,参数和返回值都会更清楚。
6.2 枚举类型 enum
enum 用于定义一组固定的命名常量。
enum Color {red,green,yellow,blue,
}void main() {Color myColor = Color.yellow;print(myColor);
}
枚举适合表示有限选项,比如颜色、状态、方向、订单状态。
enum OrderStatus {pending,paid,shipped,finished,
}
相比直接使用字符串,枚举更安全。
String status = 'paidd'; // 拼错了也可能不容易发现
OrderStatus orderStatus = OrderStatus.paid;
6.3 符号类型 Symbol
Symbol 用于表示 Dart 代码中的标识符。
void main() {Symbol symbol = #hello;print(symbol); // Symbol("hello")
}
普通业务代码里 Symbol 不算高频,它更多出现在反射、元编程等场景中。
初学阶段不需要深挖它,只要知道:#hello 表示一个符号,不是字符串。
6.4 类型别名 typedef
typedef 用于给类型起一个别名。
最简单的例子:
typedef MyInt = int;void main() {MyInt value = 10;print(value);print(value.runtimeType);
}
不过实际开发中,typedef 更常用于函数类型别名。
typedef Calculate = int Function(int a, int b);int add(int a, int b) {return a + b;
}void main() {Calculate fn = add;print(fn(3, 5));
}
这样写可以让复杂函数类型更容易阅读。
七、类型转换
类型转换就是把一种类型的数据变成另一种类型。
比如把整数变成小数,把数字变成字符串,把字符串解析成数字。
7.1 隐式转换
某些情况下,Dart 会自动完成类型转换。
最常见的是把 int 赋值给 double。
void main() {double value = 4;print(value); // 4.0print(value.runtimeType); // double
}
这里 4 原本是整数,但赋值给 double 变量时,会转换成 4.0。
不过不要以为 Dart 会随便帮你转。Dart 仍然是强类型语言,很多转换需要你明确写出来。
7.2 显式转换
显式转换就是开发者主动调用方法转换类型。
void main() {int a = 4;double b = 4.5;print(a.toDouble()); // 4.0String text = a.toString();print(text); // 4print(text.runtimeType); // Stringprint(b.toInt()); // 4
}
常见转换方法:
| 方法 | 作用 |
|---|---|
toString() |
转成字符串 |
toInt() |
转成整数 |
toDouble() |
转成小数 |
int.parse() |
把字符串解析成整数 |
double.parse() |
把字符串解析成小数 |
字符串转数字示例:
void main() {String ageText = '18';String priceText = '19.9';int age = int.parse(ageText);double price = double.parse(priceText);print(age);print(price);
}
如果字符串格式不正确,解析会报错。更稳的方式是使用 tryParse()。
void main() {String text = 'abc';int? value = int.tryParse(text);print(value); // null
}
tryParse() 解析失败时返回 null,比直接抛异常更适合处理用户输入。
八、常见误区
8.1 以为 dynamic 更方便就到处用
dynamic 的确灵活,但它会减少类型检查。
void main() {dynamic value = 10;value = true;value = 'hello';
}
这段代码能运行,但如果后续调用了不存在的方法,错误可能到运行时才出现。
建议是:能明确类型就明确类型,不确定时再使用 dynamic。
8.2 混淆 List、Set、Map
这三个集合类型不是谁替代谁,而是解决不同问题。
List解决顺序问题。Set解决去重问题。Map解决映射查找问题。
写代码前先问自己:我需要顺序、唯一性,还是通过 key 找 value?
8.3 忽略 double 精度问题
浮点数不适合直接用于所有精确计算。
void main() {print(0.1 + 0.2); // 0.30000000000000004
}
如果涉及金额、计费、精确小数,要考虑更稳妥的处理方式,比如按分存整数,或使用专门的高精度方案。
8.4 把类型转换想得太随意
不是所有值都能安全转换。
void main() {int value = int.parse('abc'); // 会报错
}
处理外部输入时,优先使用 tryParse()。
void main() {int? value = int.tryParse('abc');print(value); // null
}
九、总结
这一篇我们系统梳理了 Dart 中的数据类型。
可以把数据类型理解成数据的容器:不同数据需要放进不同容器里,容器决定了数据能做什么操作。
最常见的基础类型包括:
num:数字类型的父类型。int:整数。double:小数。bool:真假。String:文本。dynamic:动态类型。
集合类型包括:
List:有顺序、可重复。Set:不重复。Map:键值对。
特殊类型包括:
Function:函数也是值。enum:固定选项集合。Symbol:符号。typedef:类型别名。
最后,类型转换分为隐式转换和显式转换。写 Dart 时,不要只看值本身,还要关注它的类型。类型选对了,后面的代码才会更清晰、更安全、更容易维护。