Dart的基本语法
以下是Dart与Kotlin的详细对比及流程图总结:
一、语言背景与定位
特性 | Dart | Kotlin |
---|---|---|
开发者 | JetBrains | |
主要应用 | Flutter跨平台UI框架 | Android/JVM开发,多平台(KMM) |
编译方式 | JIT/AOT(移动端优先AOT) | JVM/JS/Native(多编译目标) |
设计目标 | 客户端优化、高生产力、跨平台 | 现代化Java替代、简洁与互操作性 |
二、核心语法对比
1. 变量声明
dart
// Dart
var name = 'Dart'; // 类型推断
String name = 'Dart'; // 显式类型
final int age = 30; // 不可变
const pi = 3.14; // 编译时常量
late String description; // 延迟初始化
kotlin
// Kotlin
var name = "Kotlin" // 类型推断
val age: Int = 30 // 不可变(只读变量)
const val PI = 3.14 // 编译时常量(需在顶层或object内)
lateinit var desc: String // 延迟初始化(不可val)
关键差异:
- Dart的
final
对应Kotlin的val
,但Dart允许const
用于编译时常量 - Kotlin的
lateinit
仅用于可变变量,Dart的late
更灵活
2. 函数定义
dart
// Dart
int add(int a, int b) => a + b; // 箭头函数
void printMsg(String msg) { /* ... */ }
// 命名参数(默认非必填)
void greet({String name = 'Guest', int age}) { ... }
greet(age: 25);
kotlin
// Kotlin
fun add(a: Int, b: Int): Int = a + b
fun printMsg(msg: String): Unit { /* ... */ }
// 命名参数(需注解)
fun greet(name: String = "Guest", age: Int?) { ... }
greet(age = 25)
关键差异:
- Dart支持直接命名参数,Kotlin需
@JvmOverloads
处理 - Dart的
=>
对应Kotlin的=
单表达式函数
3. 空安全(Null Safety)
dart
// Dart
String? nullableString; // 可空类型
String nonNullable = ''; // 非空
print(nullableString?.length ?? 0); // 空合并
late String lateInit; // 延迟初始化保证非空
kotlin
// Kotlin
var nullable: String? = null
val nonNull: String = ""
nullable?.length ?: 0 // Elvis操作符
lateinit var lateInit: String // 延迟初始化(非基本类型)
共同点:
- 默认变量不可空,需显式声明
?
- 提供安全调用操作符
?.
和空合并??
(Dart)/?:
(Kotlin)
在 Dart 中,??
是 空合并运算符(null coalescing operator) ,它的作用是:当左侧的值为 null
时,返回右侧的默认值;否则直接返回左侧的值。
dart
print(nullableString?.length ?? 0);
-
nullableString?.length
?.
是 安全调用操作符(conditional member access) :- 如果
nullableString
为null
,整个表达式会直接返回null
,而不会抛出空指针异常。 - 如果
nullableString
不为null
,则返回其length
属性的值。
- 如果
-
?? 0
- 如果左侧的
nullableString?.length
结果为null
,则整个表达式会返回0
; - 否则直接返回
nullableString.length
的值。
- 如果左侧的
等价于传统写法:
dart
if (nullableString != null) {
print(nullableString.length);
} else {
print(0);
}
其他常见用法示例:
dart
// 1. 变量默认值
String? name;
String displayName = name ?? "Guest"; // 如果 name 是 null,显示 "Guest"
// 2. 链式安全调用 + 空合并
int? length = user?.profile?.bio?.length ?? 0;
// 3. 简化空判断逻辑
int value = nullableInt ?? calculateDefault(); // 仅当 nullableInt 为 null 时调用函数
总结:
??
是 Dart 空安全(null safety)中非常实用的语法糖,用于优雅处理可能为null
的情况。- 它的优先级较低,通常可以与其他操作符(如
?.
)无缝结合使用。
三、面向对象与高级特性
1. 类与继承
dart
// Dart
class Animal {
String name;
Animal(this.name); // 构造方法简写
void speak() => print('...');
}
class Dog extends Animal {
Dog(String name) : super(name);
@override void speak() => print('Woof!');
}
kotlin
// Kotlin
open class Animal(val name: String) { // 默认final类需open
open fun speak() = println("...")
}
class Dog(name: String) : Animal(name) {
override fun speak() = println("Woof!")
}
关键差异:
- Kotlin类默认
final
,需open
允许继承 - Dart使用
@override
注解,Kotlin直接override
关键字
2. 数据类(Data Class)
dart
// Dart
class Point {
final int x, y;
Point(this.x, this.y);
@override
bool operator ==(Object other) => ... // 需手动实现equals/hashCode
}
kotlin
// Kotlin
data class Point(val x: Int, val y: Int) // 自动生成equals/hashCode/toString等
对比 :Kotlin的data class
自动生成样板代码,Dart需手动实现或使用三方库(如equatable
)
3. 扩展方法(Extension)
dart
// Dart
extension NumberParsing on String {
int parseInt() => int.parse(this);
}
'42'.parseInt();
kotlin
// Kotlin
fun String.parseInt(): Int = this.toInt()
"42".parseInt()
相似性 :两者均支持扩展已有类功能,语法略有不同。 这段代码是 Dart 中扩展方法(extension method) 的典型应用,它的作用是为 String
类型添加一个自定义方法 parseInt()
,用于将字符串转换为整数。下面逐步解释:
代码逐行解读
- 定义扩展方法
dart
extension NumberParsing on String {
int parseInt() => int.parse(this);
}
extension NumberParsing on String
- 创建一个名为
NumberParsing
的扩展,专门为String
类添加新方法。 - 扩展方法的作用是让所有
String
实例都能调用新增的方法。
- 创建一个名为
int parseInt() => int.parse(this);
- 定义一个名为
parseInt
的方法,返回int
类型。 =>
是 Dart 的箭头语法,相当于{ return ...; }
。int.parse(this)
:this
表示当前调用该方法的String
实例(例如'42'
)。int.parse()
是 Dart 内置方法,用于将字符串解析为整数。
- 定义一个名为
- 使用扩展方法
dart
'42'.parseInt(); // 返回整数 42
- 字符串
'42'
调用parseInt()
方法(通过扩展添加的方法)。 - 等价于直接调用
int.parse('42')
,但通过扩展方法更符合面向对象风格。
类比传统写法
如果不使用扩展方法,传统写法需要直接调用静态方法:
dart
int.parse('42'); // 直接调用内置方法
而扩展方法让代码看起来像是字符串"自己拥有"这个方法:
dart
'42'.parseInt(); // 更直观,类似其他面向对象语言(如 Java 的 "42".toInt())
扩展方法的核心优势
-
增强代码可读性
将工具方法(如类型转换)附加到目标类上,代码更符合直觉。
-
避免重复代码
如果项目中频繁需要将字符串转整数,可以统一通过扩展方法实现。
-
不修改原始类
扩展方法是语法糖,不会实际修改
String
类的内部结构。
注意事项
-
作用域 :
扩展方法仅在导入该扩展的文件中可用。如果要在其他文件中使用,需导入定义扩展的文件。
-
命名冲突 :
如果两个扩展为同一个类添加了同名方法,需通过显式指定扩展名解决:
dart'42'.NumberParsing.parseInt(); // 明确使用 NumberParsing 扩展中的方法
-
不可访问私有成员 :
扩展方法无法访问类的私有(以
_
开头的)属性和方法。
更多示例为 String
添加一个 toDouble()
扩展:
dart
extension NumberParsing on String {
int parseInt() => int.parse(this);
double toDouble() => double.parse(this);
}
// 使用
print('3.14'.toDouble()); // 3.14
总结
这段代码通过 扩展方法 ,让 String
类型"拥有"了一个 parseInt()
方法,本质上是一种语法糖,目的是让代码更简洁、易读。它的底层实现仍然是调用 int.parse(this)
,但通过扩展方法,代码的组织方式更加面向对象。
四、异步编程模型
1. Dart(async/await + Future/Stream)
dart
Future<String> fetchData() async {
var data = await http.get('url');
return process(data);
}
Stream<int> countStream() async* {
for (int i=0; i<5; i++) {
await Future.delayed(Duration(seconds:1));
yield i;
}
}
在 Dart 中,async/await
和 Future/Stream
是处理异步编程的核心机制,它们的区别主要体现在 抽象层级 和 适用场景 上:
一、核心概念差异
Future | Stream | async/await | |
---|---|---|---|
本质 | 单个异步操作的结果封装 | 多个异步事件的连续数据流 | 语法糖,简化异步代码编写 |
数据量 | 单一值(或错误) | 多个值(0个、1个、或多个) | 主要配合 Future 使用 |
执行次数 | 一次性完成(类似 Promise) | 持续监听事件(类似 Observable) | 用于控制异步代码流程 |
二、使用场景对比
1. Future + async/await
-
适用场景 :单次异步操作(如 HTTP 请求、文件读写)。
-
代码示例 :
dartFuture<String> fetchData() async { var data = await http.get('https://api.example.com/data'); return processData(data); }
-
特点 :
await
会暂停函数执行,直到Future
完成。- 通过
try/catch
捕获异常,代码更直观。
2. Stream
-
适用场景 :连续异步事件(如 WebSocket 消息、用户输入事件、实时数据流)。
-
代码示例 :
dartStream<int> countNumbers(int max) async* { for (int i = 1; i <= max; i++) { await Future.delayed(Duration(seconds: 1)); yield i; // 逐步生成值 } } // 使用 listen 监听 countNumbers(5).listen((num) => print(num)); // 输出 1,2,3,4,5
-
特点 :
-
通过
listen
订阅数据,或使用await for
逐条处理:dartvoid processStream() async { await for (var num in countNumbers(5)) { print(num); } }
-
支持丰富的操作符(如
map
、where
、debounce
)进行流转换。
-
三、关键区别总结
特性 | Future + async/await | Stream |
---|---|---|
数据频率 | 单次结果 | 多次事件(可连续或间断) |
控制流 | 线性执行(等待完成) | 事件驱动(持续监听) |
错误处理 | try/catch |
onError 回调或 Stream.transform |
组合操作 | Future.wait([future1, future2]) |
StreamZip , StreamGroup 等操作符 |
资源管理 | 自动释放 | 需手动调用 cancel() 或关闭流 |
四、如何选择?
-
用
Future + async/await
如果:- 只需等待单次异步操作的结果(如 API 响应)。
- 希望代码保持同步风格,避免回调嵌套。
-
用
Stream
如果:- 处理持续不断的事件(如聊天消息、传感器数据)。
- 需要对数据流进行复杂转换或合并(如搜索框防抖)。
五、进阶技巧
- 互操作性 :
- 将
Stream
转为Future
:await stream.first
(获取第一个值)或await stream.toList()
。 - 将
Future
转为Stream
:Stream.fromFuture(future)
。
- 将
- 并发控制 :
- 使用
Stream.asyncMap
限制异步任务的并发数。
- 使用
- 冷热流 :
- 冷流(Cold Stream):订阅后才开始产生数据(如文件读取)。
- 热流(Hot Stream):无论是否有订阅,数据持续产生(如时钟事件)。
通过理解这些差异,你可以更精准地选择异步模型,写出高效且易维护的 Dart 代码! 🚀
2. Kotlin(Coroutines + Flow)
kotlin
suspend fun fetchData(): String {
val data = withContext(Dispatchers.IO) { http.get("url") }
return process(data)
}
fun countFlow(): Flow<Int> = flow {
for (i in 0..4) {
delay(1000)
emit(i)
}
}
核心差异:
- Dart的
Future
≈Kotlin的Deferred
,Stream
≈Flow
- Kotlin协程通过调度器管理线程,Dart单线程事件循环+Isolate多线程
五、生态系统与工具链
维度 | Dart | Kotlin |
---|---|---|
包管理 | pub.dev + pubspec.yaml |
Maven /Gradle + 中央仓库 |
跨平台 | Flutter(iOS/Android/Web/桌面) | KMM(共享业务逻辑层) |
调试工具 | DevTools(热重载强大) | Android Studio集成 |
主要IDE | VS Code/Android Studio | IntelliJ IDEA/Android Studio |
六、流程图:Dart vs Kotlin 选择指南
七、总结
-
选择Dart当:
- 构建跨平台UI(Flutter)
- 需要极致的热重载开发体验
- 项目侧重移动端且需统一代码库
-
选择Kotlin当:
- 开发Android应用或JVM后端
- 需要与Java代码深度互操作
- 使用KMM共享业务逻辑层
两者在语法现代性上趋同,但生态定位不同,常在实际项目中配合使用(如KMM+Flutter)。