Dart:基础类型(包括String和Datetime)

创建一个Dart项目

# 创建一个dart 项目
dart create dart_learn

# 使用VSCode打开
code dart_learn 

变量

弱类型

弱类型变量的类型会在需要时自动转换,编译器/解释器会尽量帮你“猜”类型,即使类型不匹配也尝试运行(有时容易出错)。

var

含义:编译时由编译器自动推断类型(type inference)。

特点

  1. 一旦赋值,类型就固定了,不能再改成别的类型。
  2. 编译阶段就会做类型检查。

适用场景:当你希望省略类型声明,但又要保持类型安全时。

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

含义:关闭编译时的类型检查,类型在运行时才确定。

特点

  1. 可以随时赋任何类型的值。
  2. 编译阶段不会报错,但运行时可能出错。

适用场景:当类型不确定,或者需要兼容多种类型(例如 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)。

特点

  1. 所有类型都可以赋给 Object 变量。
  2. 但是调用子类特有方法前必须显式转换。

适用场景: 当你要存储任意对象,但仍希望保留编译时类型检查

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 查询参数可能是 Stringintbool

  • 取值时 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:既可以表示整数,也可以表示浮点数,是 intdouble 的父类。
  • 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 里是 关键字truefalse

与 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 → 只能是 truefalse
  • 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

×

喜欢就点赞,疼爱就打赏