创建一个Dart项目
# 创建一个dart 项目
dart create dart_learn
# 使用VSCode打开
code dart_learn

变量
弱类型
弱类型:变量的类型会在需要时自动转换,编译器/解释器会尽量帮你“猜”类型,即使类型不匹配也尝试运行(有时容易出错)。
var
含义:编译时由编译器自动推断类型(type inference)。
特点:
- 一旦赋值,类型就固定了,不能再改成别的类型。
- 编译阶段就会做类型检查。
适用场景:当你希望省略类型声明,但又要保持类型安全时。
void main(List<String> args) {
// var a;
var a = ""; // 一旦赋值,就确定类型,不能随意改动
a = 'ducafecat';
print(a is String); // true
a = 123;
print(a is int); // true
a = true;
print(a is bool); // true
a = {'key': 'val123'};
print(a is Map); // true
a = ['abc'];
print(a is List); // true
}

dynamic
含义:关闭编译时的类型检查,类型在运行时才确定。
特点:
- 可以随时赋任何类型的值。
- 编译阶段不会报错,但运行时可能出错。
适用场景:当类型不确定,或者需要兼容多种类型(例如 JSON 解析、与弱类型 API 交互)。
void main() {
dynamic b = 123;
print(b.runtimeType); // int
b = "abc"; // ✅ 可以改成 String
print(b.runtimeType); // String
// 运行时才会发现错误
print(b + 1); // ❌ 运行时报错:String 不能做加法
}
Object
含义:Dart 所有类的基类(类似 Java 的 Object
)。
特点:
- 所有类型都可以赋给
Object
变量。 - 但是调用子类特有方法前必须显式转换。
适用场景: 当你要存储任意对象,但仍希望保留编译时类型检查。
void main() {
Object c = "hello"; // ✅ String 可以赋给 Object
print(c.runtimeType); // String
// c.toUpperCase(); // ❌ 编译报错:Object 没有 toUpperCase 方法
print((c as String).toUpperCase()); // ✅ 转换后可调用
}
强类型
强类型:变量的类型一旦确定,不能隐式转换成其他类型,类型错误会在编译阶段或运行时直接报错。
String a;
a = 'ducafecat';
a = 123; // ❌ 运行时报错:a 的类型是 String,不能赋值为 int
常见的强类型有:
名称 | 说明 |
---|---|
num |
数值父类,既能存整数也能存浮点数 |
int |
整型 |
double |
浮点 |
bool |
布尔 |
String |
字符串 |
StringBuffer |
字符串 buffer |
DateTime |
时间日期 |
Duration |
时间区间 |
List |
列表 |
Sets |
无重复队列 |
Maps |
kv 容器 |
enum |
枚举 |
String a = 'doucafecat';
int i = 123;
double d = 0.12;
bool b = true;
DateTime dt = new DateTime.now();
List l = [ a, i, d, b, dt];
默认值
变量声明后默认都是 null

使用场景
var 简化定义变量
不用明确变量类型:这里不用 var
,就要写成 Map<String, dynamic>
var map = <String, dynamic>{};
map["image"] = image;
map["title"] = title;
map["desc"] = desc;
Java 对应写法(Java 10+)
var map = new HashMap<String, Object>(); // Java 10 开始支持 var(类型推断)
map.put("image", image);
map.put("title", title);
map.put("desc", desc);
Java 10+ 的
var
也是编译时类型推断。推断结果是
HashMap<String, Object>
,以后这个变量就固定这个类型。如果是 Java 8/9 或更早版本,就得写全:
Map<String, Object> map = new HashMap<>();
区别
- Dart 的
<String, dynamic>
里dynamic
代表运行时可以是任何类型; - Java 没有
dynamic
,只能用Object
做“万能类型”,但取值时需要强转。
查询参数定义
api 查询通用接口封装的时候,我们一般用动态类型
如一个 api 请求:Map<String, dynamic>? queryParameters
, 查询参数值是动态
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic> queryParameters,
...
});
queryParameters
的值可以是任意类型(dynamic
),因为 API 查询参数可能是String
、int
、bool
。取值时 Dart 会直接允许使用对应类型的方法(运行时报错)。
Java 对应写法
public <T> Response<T> get(
String path,
Map<String, Object> queryParameters
// ... 其他参数
) {
// 使用 queryParameters.get("xxx") 时需要类型转换
}
Java 用
Map<String, Object>
代替Map<String, dynamic>
。或者干脆用
Map<String, String>
(HTTP 查询参数通常最终都是字符串),在封装时统一toString()
:Map<String, String> params = new HashMap<>(); params.put("page", String.valueOf(1)); params.put("search", "dart");
返回的实例对象
class Category {
int id; // 数字 id
String name; // 字符串 分类名称
String slug;
Category({this.id, this.name, this.slug});
...
}
常量
final
- 含义:只能赋值一次(运行时确定值),但值可以是运行时计算的结果。
- 特点:
- 变量在 第一次赋值后不可改变。
- 可以用运行时表达式初始化。
final now = DateTime.now(); // 运行时才确定
// now = DateTime.now(); // ❌ 再赋值会报错
const
- 含义:编译时常量(compile-time constant),值在编译时就必须能确定。
- 特点:
- 值必须是编译器可以直接计算的。
const
修饰的对象是不可变的(immutable)。- 相同的
const
对象会复用同一份内存(canonicalized)。
const pi = 3.14159; // 编译时就确定
const list = [1, 2, 3]; // 不可变列表
Dart const
vs final
对比表
特性 | final |
const |
---|---|---|
初始化时间 | 运行时 | 编译时 |
赋值次数 | 只能一次 | 只能一次 |
值是否必须编译期已知 | ❌ | ✅ |
对象是否不可变 | ❌(内容可改) | ✅(完全不可变) |
内存复用 | ❌ | ✅(常量池) |
运行时确定的值:用 final
final now = DateTime.now(); // ✅ 运行时确定
// const now2 = DateTime.now(); // ❌ 编译期不能确定
print(now);
final
:允许值在运行时计算,比如当前时间、用户输入、网络请求结果。const
:必须编译期就确定,所以不能用DateTime.now()
。
编译期常量:用 const
const pi = 3.14159; // ✅ 编译时就知道
const list = [1, 2, 3]; // ✅ 不可变
// final list = [1, 2, 3]; // ✅ 列表引用不可改,但内容可改
区别:
final list1 = [1, 2, 3];
list1[0] = 99; // ✅ 内容可改:由于我们希望使用的是常量,所有如果内容可以改变,那么就不是常量了
const list2 = [1, 2, 3];
// list2[0] = 99; // ❌ 完全不可改
内存共享(Canonicalization)
const a = [1, 2, 3];
const b = [1, 2, 3];
print(identical(a, b)); // ✅ true,同一份对象
final c = [1, 2, 3];
final d = [1, 2, 3];
print(identical(c, d)); // ❌ false,不同对象
const
:相同内容的常量会复用同一份内存。final
:即使内容一样,也会新建对象。
Java 中的常量写法
Java 没有 const
关键字(保留字但没启用),常量主要用 final
+ static
实现。
运行时不可变变量(Dart final
对应)
final LocalDateTime now = LocalDateTime.now();
// now = LocalDateTime.now(); // ❌ 再赋值会报错
编译时常量(Dart const
对应)
public static final double PI = 3.14159;
static final
:
static
表示类级别(全局共享)。final
表示只能赋值一次。- 如果值是编译期常量(字面量或编译时能算出),Java 编译器会把它内联到字节码中。
Java 没有专门的 const
关键字,static final
常量兼顾了“编译时固定”和“全局唯一”的作用。
Java中的static final
和Dart中的const
最大的区别就是:对象不可变性
Dart
const
对象完全不可改const list = [1, 2, 3]; // list[0] = 99; // ❌ 报错,完全不可变
Java
static final
只保证引用不可改public static final List<Integer> list = new ArrayList<>(List.of(1, 2, 3)); public static void main(String[] args) { list.set(0, 99); // ✅ 可以改内容 }
数值
数值类型
Object
└─ num // 抽象父类(数值通用操作)
├─ int // 整数
└─ double // 浮点数(64 位 IEEE 754)
num
:既可以表示整数,也可以表示浮点数,是int
和double
的父类。int
:只能表示整数,精度由平台决定(在现代 Dart 中,通常是 64 位有符号整数)。double
:表示双精度浮点数,符合 IEEE 754 标准。
数值的表示方法
int a = 1001; // 十进制
int b = 0xF; // 16进制
num c = 2e3; // 科学计数法
print([a, b, c]);
[1001, 15, 2000]
数值转换
// string -> int
// string -> double
int a = int.parse('123');
double b = double.parse('1.223');
// int -> string
// double -> string
String a = 123.toString();
String b = 1.223.toString();
print([a, b]);
// double -> int
double a = 1.8;
int b = a.toInt();
print(b); // 1, double 转 int 会丢失小数部分
位运算
&
与运算
同时 1
才行
1 0 1 0 10
0 0 1 0 2
--------
0 0 1 0 2
var a = 10;
var b = 2;
print(a & b); // 2
|
或运算
有一个 1
就行
1 0 1 0 10
0 0 1 0 2
--------
1 0 1 0 10
var a = 10;
var b = 2;
print(a | b); // 10
~
非运算
二进制数逐位进行逻辑非运算
0 1 0 0 1 +9 二进制 最高位 0 整数 1 负数
0 0 1 1 0 补码
1 1 0 0 1 取反
1 1 0 1 0 加1
--------
1 1 0 1 0 -10
var a = 9;
print(~a); //4294967286
^
异或
不相同的才出 1
。计算机中可以用来取反色
1 0 1 0 10
0 0 1 0 2
--------
1 0 0 0 8
var a = 10;
var b = 2;
print(a ^ b); // 8
移位运算符
<<
左移
0 0 0 1 1 二进制
0 0 1 0 左移一位 2
0 1 0 0 左移一位 4
1 0 0 0 左移一位 8
var a = 1 << 1;
print(a); //2
>>
右移
1 0 0 0 8 二进制
0 1 0 0 右移一位 4
0 0 1 0 右移一位 2
0 0 0 1 右移一位 1
var a = 8 >> 1;
print(a); //4
位运算的应用:按位标志(bit flags) 来表示状态
按位标志(bit flags)
const LEFT = 0x1; // 0001
const TOP = 0x2; // 0010
const RIGHT = 0x4; // 0100
const BOTTOM= 0x8; // 1000
// 同时启用 LEFT + TOP + RIGHT
var state = LEFT | TOP | RIGHT; // 0111
// 检查是否包含某个标志
print(state & LEFT != 0); // true : 0111 & 0001 = 0001
print(state & BOTTOM != 0); // false : 0111 & 1000 = 0000
- 每个标志对应一个二进制位,互不冲突。
- 可以随意组合(用
|
),不会产生重复值。 - 可以快速判断某个标志是否启用(用
&
检查某一位)。 - 内存占用小(一个整数就能存 N 个状态)。
直接用数字表示的缺点
组合状态需要人为分配,不可扩展
如果状态组合不多还好,但一旦状态种类多、组合情况复杂,你就得手动给每一种组合一个唯一数字。
举个例子:
LEFT = 1
TOP = 2
RIGHT = 3
BOTTOM = 4
LEFT+TOP = 5
LEFT+RIGHT = 6
TOP+RIGHT = 7
...
- 组合一多,编号表会越来越乱。
- 如果后面加新状态,还得重新分配,容易出错。
无法快速拆解组合状态
假设你用 5
表示 LEFT + TOP
:
var state = 5;
if (state == LEFT) { ... } // ❌ 无法判断,因为 5 != 1
你没法像 bit flags 那样:
if (state & LEFT != 0) { ... } // ✅ 一步判断是否包含 LEFT
用数字标记组合状态时,判断就要写成:
// state == 1 == LEFT
// state == 5 == LEFT + TOP
// state == 7 == LEFT + TOP + BOTTOM
if (state == 5 || state == 1 || state == 7 || ...) { ... }
组合越多,判断就越复杂。
布尔
基本概念
Dart 中的布尔类型就是 bool
,只有两个取值:
bool isVisible = true;
bool isDone = false;
这两个值在 Dart 里是 关键字:true
和 false
。
与 JavaScript / Python 的区别
在 JavaScript / Python,有“真值/假值”概念(例如 0
、空字符串、空数组等会被当作 false
)。
Dart 不这样,布尔类型必须是 bool
类型,其他类型不能直接当作条件使用。
var num = 0;
if (num) { ... } // ❌ 编译错误:num 不是 bool
assert 断言
assert
是什么
assert
用来在 调试模式(debug mode)下检测条件是否为真。- 如果条件是
false
,程序会立刻抛出AssertionError
,并可选输出提示信息。 - 在 release 模式(生产版本)下,
assert
会被完全移除,不会影响性能。
语法
assert(condition);
assert(condition, "错误提示");
condition
必须是一个bool
表达式。- 第二个参数(可选)是当断言失败时的错误信息。
int age = 15;
assert(age >= 18, "年龄必须 >= 18 才能注册");
// 如果 age < 18,在调试模式下会抛出 AssertionError
逻辑运算符
bool a = true;
bool b = false;
print(a && b); // false AND
print(a || b); // true OR
print(!a); // false NOT
&&
:短路与(如果左边是false
,右边不会计算)||
:短路或(如果左边是true
,右边不会计算)!
:逻辑非
比较运算
int x = 5;
int y = 10;
print(x > y); // false
print(x == y); // false
print(x != y); // true
空安全与 bool?
如果变量可能为 null
,类型必须写成 bool?
:
bool? flag; // 防止 null 出错
print(flag == true); // false
print(flag == false); // false
print(flag == null); // true
不能直接把 null
当作布尔值:
bool? flag;
if (flag) { ... } // ❌ 编译错误
在 Dart 里,布尔类型(
bool
)本身只有两个取值:
true
false
什么时候会出现第三种情况:
null
如果你显式声明 可空布尔:
bool? flag; // 默认是 null
。那么这个变量的可能值就变成了true
/false
/null
三种。这里的null
不是布尔的第三个逻辑值,而是“这个变量还没赋值”或“没有值”的意思。
Dart 是强类型 + 空安全的语言,
bool?
和bool
是不同的类型。bool? flag; if (flag) { // ❌ 编译报错 print("flag 为 true"); }
会报错:
bool?
不能直接作为条件,因为它可能是null
。//正确做法是: if (flag == true) { // ✅ 只有 flag 为 true 才会执行 print("flag 为 true"); } // 或者提供默认值: if (flag ?? false) { // ✅ null 当作 false 处理 print("flag 为 true"); }
总结
bool
→ 只能是true
或false
。bool?
→ 可以是true
/false
/null
。null
不是布尔的第三个逻辑值,而是“没有值”。
字符串
使用''
或者""
定义字符串
String a = 'ducafecat';
String b = "ducafecat";
字符串转义
final myString = 'Bob\'s dog'; // Bob's dog
final myString = "a \"quoted\" word"; // a "quoted" word
final myString = "Bob's dog"; // Bob's dog
final myString = 'a "quoted" word'; // a "quoted" word
final value = '"quoted"'; // "quoted"
final myString = "a $value word"; // a "quoted" word
字符串模板
可以直接在字符串中嵌入变量或表达式,用 $变量
或 ${表达式}
:
var name = "Dart";
var version = 3;
print("Language: $name, Version: ${version + 1}");
// Language: Dart, Version: 4
多行字符串
使用 '''
或 """
定义多行字符串:
var multiLine = '''
Hello,
This is multi-line text.
''';
print(multiLine);
转义符号
var a = 'hello word \n this is multi line';
print(a);
/*
hello word
this is multi line
*/
原始字符串(不转义)
在字符串前加 r
,表示里面的反斜杠不会转义:
var raw = r'C:\Program Files\Dart';
print(raw); // C:\Program Files\Dart
字符串连接
var a = 'hello' + ' ' + 'ducafecat';
var a = 'hello'' ''ducafecat';
var a = 'hello' ' ' 'ducafecat';
var a = 'hello'
' '
'ducafecat';
print(a);
String的常用方法
检查与判断
var str = "Dart Language";
str.isEmpty // 是否为空字符串
str.isNotEmpty // 是否非空
str.contains("art") // 是否包含子串
str.startsWith("Da") // 是否以指定字符串开头
str.endsWith("ge") // 是否以指定字符串结尾
str.indexOf("a") // 第一次出现的位置(找不到返回 -1)
str.lastIndexOf("a") // 最后一次出现的位置
大小写转换
str.toUpperCase() // 全部转大写
str.toLowerCase() // 全部转小写
提取与替换
str.substring(5) // 从索引 5 开始到结尾
str.substring(0, 4) // 截取 [0,4) 部分
str.replaceAll("a", "o") // 全部替换
str.replaceFirst("a", "o") // 替换第一次出现
str.replaceRange(0, 4, "Flutter") // 替换指定范围
去除空白
" hello ".trim() // 去除两端空格
" hello ".trimLeft() // 去除左侧空格
" hello ".trimRight() // 去除右侧空格
字符串分割 & 拼接
var csv = "apple,banana,orange";
var list = csv.split(",");
print(list); // [apple, banana, orange]
var joined = list.join(" | ");
print(joined); // apple | banana | orange
字符串比较
Dart 的 String
重载了 ==
运算符,比较的是 内容:
print("abc" == "abc"); // true
如果想比较大小(字典序):
"abc".compareTo("abd") // -1(小于)
"abc".compareTo("abc") // 0(等于)
"abd".compareTo("abc") // 1(大于)
其他
str.codeUnitAt(0) // 获取 UTF-16 码单元
str.runes // 获取 Unicode 码点迭代器
str.padLeft(10, '*') // 左侧补足
str.padRight(10, '-') // 右侧补足
String 为 null 怎么办
Dart的String一定是非null的

在Java中,对于字符串我们一般要进行非空判断,否则会报空指针
String str = null;
str.isEmpty(); // ❌ NullPointerException
必须先判空:
if (str != null && !str.isEmpty()) { ... }
Dart 从 2.12 开始引入了 空安全(null-safety),
- 如果变量可能是
null
,类型必须显式标注为可空类型:String?
- 如果是 非空类型
String
,编译器会在你赋null
时直接报错,避免运行时空指针。
String str = "hello";
print(str.isEmpty); // ✅ 安全
str = null; // ❌ 编译错误
如果变量可能为空(String?)
如果你声明了:
String? str; // 可空类型
那直接调用:
print(str.isEmpty); // ❌ 编译错误(提示可能为空)
必须用:
print(str?.isEmpty); // ✅ null-safe 调用,返回 null 而不是报错
print(str?.isNotEmpty); // 返回 null。
print(str?.isEmpty ?? true); // ✅ null 时默认 true
print(str == null); // true,str 可能为 null,所以使用 ?== 来比较
所以,如果定义的是String? str
这个类型,还是需要进行非空判断:
String? str; // 从数据库里面取出来
if (str?.isEmpty ?? true) { // str?.isEmpty 得到是是null。 ?? true:如果为null,就取默认值true
print("str 是 null 或空字符串"); // 如果是空,就抛异常或者其他处理
} else {
print("str 有内容"); // 如果不为空,就作业务处理
}
日期时间
创建 DateTime
对象
当前时间
var now = DateTime.now();
print(now); // 2025-08-09 15:32:45.123
指定日期时间
var specific = DateTime(2025, 8, 9, 14, 30, 0);
// 年, 月, 日, 时, 分, 秒
print(specific); // 2025-08-09 14:30:00.000
从 UTC 时间创建
var utcTime = DateTime.utc(2025, 8, 9, 14, 30);
print(utcTime); // 2025-08-09 14:30:00.000Z
什么是UTC

UTC(Coordinated Universal Time,协调世界时)是全球统一的标准时间,不受时区影响。
世界上所有时区都是在 UTC 基础上加减小时 得到的,比如:
- 北京时间 = UTC + 8
- 纽约时间 = UTC - 5(冬令时)
用 UTC,可以让全球系统在传递时间时没有时区歧义。
2025-08-09 14:30:00.000Z
14:30:00 → 小时:分钟:秒
.000 → 毫秒部分(这里是 0 毫秒)
Z → Zero offset(零时区)
- 表示这是 UTC 时间
Z
在 ISO 8601 时间格式里是 “Zulu time” 的缩写,等于 UTC±00:00,也就是零时区
Dart 是怎么知道的所属时区的
当你运行 Dart 程序时,Dart VM 会向你的操作系统请求当前的本地时间和时区信息
操作系统会返回:
- 本地时间
- 时区偏移量(例如 +08:00)
- 夏令时信息(如果有)
Dart 的 DateTime.toLocal()
方法,就是用这个偏移量去把 UTC 转换成本地时间
操作系统的时区信息从哪里来
- Windows:系统控制面板 → 时间和语言 → 时区
- macOS / Linux:一般存储在
/etc/localtime
,由系统时钟守护进程读取 - 手机(Android/iOS):用设备的“日期与时间”设置
从时间戳创建
获取秒级时间戳:
int timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
print(timestamp); // 例如:1723209834
millisecondsSinceEpoch
→ 从 1970-01-01 00:00:00 UTC 到现在的毫秒数~/ 1000
→ 转换成秒(整除运算)
获取毫秒级时间戳:
int timestamp = DateTime.now().millisecondsSinceEpoch;
print(timestamp); // 例如:1723209834123
获取微秒级时间戳(更精确):
int timestamp = DateTime.now().microsecondsSinceEpoch;
print(timestamp); // 例如:1723209834123456
常用属性
var now = DateTime.now();
print(now.year); // 年
print(now.month); // 月
print(now.day); // 日
print(now.hour); // 小时
print(now.minute); // 分钟
print(now.second); // 秒
print(now.weekday); // 星期(1=周一,7=周日)
常用方法
时间加减
var now = DateTime.now();
var twoDaysLater = now.add(Duration(days: 2));
var threeHoursAgo = now.subtract(Duration(hours: 3));
比较时间
var now = DateTime.now();
var future = now.add(Duration(days: 1));
print(now.isBefore(future)); // true
print(future.isAfter(now)); // true
print(now.isAtSameMomentAs(now)); // true
时间差
var d1 = new DateTime(2018, 10, 1);
var d2 = new DateTime(2018, 10, 10);
var difference = d1.difference(d2); // 计算两个日期之间的差值
print(difference); // -9 days, 0:00:00.000000
print(difference.inDays); // -9
print(difference.inHours); // -216
print(difference.inMinutes); // -12960
print(difference.inSeconds); // -777600
print(difference.inMilliseconds); // -777600000
格式化时间(常用 intl
包)
Dart 原生 DateTime
没有复杂的格式化方法,一般配合 intl
:
import 'package:intl/intl.dart';
var now = DateTime.now();
var formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
print(formatter.format(now)); // 2025-08-09 15:40:00
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com