如果你是一名Java开发人员,可以通过 codelabs 快速入门Dart。
1. 简介
Dart是一门用于Flutter的编程语言,而Flutter是Google开发的移动应用SDK。
阅读本文,你将学习到:
- 如何编写构造方法
- 为对象赋值的多种方式
- 如何 & 何时创建getter和setter
- 如何处理访问权限
- 如何创建工厂模式
- 函数式编程
- 其他主要的概念
此外,codelabs提供了 DartPad 供大家编写Dart代码。
2. 创建一个简单的Dart类
我们以一个简单的Bicycle
类来进行学习,Bicycle
类包含一些私有的成员变量,以及对应的setter和getter。
我们尝试编写Bicycle
类然后通过main()
方法将Bicycle
实例打印到控制台。
2.1 启动DartPad
现在可以打开 DartPad ,一边编写Dart代码。
2.2 定义Bicycle类
在DartPad定义一个Bicycle
类,其中包含三个成员变量:cadence
、speed
和gear
。
1 | class Bicycle { |
在上面的代码中,我们定义了Bicycle
类,以及一个main()
方法,代码相对比较简单。其中有一些Dart和Java的区别需要留意:
区别 | Dart | Java |
---|---|---|
main() 方法 |
1. 不带参数则定义为void main() ,带参数定义为void main(List<String> args) 2. 必须在最外层,位于类外部 |
1. 定义为public static void main(String[] args) 2. 位于类内部 |
方法、变量定义 | 能够位于类外 | 面向对象语言,所有变量、方法必须位于类内部 |
访问类型 | 不支持private、public、protected修饰词,全部类、成员变量和方法默认定义为public | 通过修饰词private、public、protected修饰 |
类成员私有化 | 成员命名以下划线开始(下一节将学习到) | 通过private修饰 |
缩进 | 默认使用两个空格 | 默认使用四个空格 |
2.3 定义构造方法
1 | Bicycle(this.cadence, this.speed, this.gear); |
上面代码便是Dart的构造方法,初次阅读会觉得比较奇怪,但是在Dart:
- 构造方法没有函数体是允许的!
- 如果没函数体的构造方法行末尾漏了分号
;
,DartPad会提示:A function body must be provided.
- 在构造方法的参数列表中使用this,是Dart为实例变量赋值的一种快捷方式。也即是等同于以下代码,大家记住可以这么用就可以了。
1
2
3
4
5Bicycle(int cadence, int speed, int gear) {
this.cadence = cadence;
this.speed = speed;
this.gear = gear;
}
2.4 实例化并打印
在我们的main()
方法中添加实例化的代码:
1 | void main() { |
以上四种实例化方式都是允许的,其中:第二种方式指明类型会加快编译速度,第一种并没有指明类型;在Dart 2之后new可以省略(也就是第三种方式);如果明确bike4
是不变的则可以使用final代替var(第四种方式)。print()
就如同Java中的System.out.print()
,这里打印结果是Instance of 'Bicycle'
,如果想要打印具体的信息,需要重写toString()
方法(思想与Java一致):
1 | @override |
这里的重写代码也是有点独特:
@overrid
同Java一致,表明这是重写类方法,Java中字母o是大写- 字符串支持单引号或双引号进行表示
- 如果要在字符串中插入表达式,则可以使用
${表达式}
,如果是插入成员变量值,可以直接使用$成员变量
- 上面包含了一个
=>
,该符号表示,当方法的实现只有一行代码的时候可以使用=>
简写。
2.5 添加只读成员变量
并不是所有类成员变量都是对外开放的,有时可能会需要一些私有的成员变量,例如将Bicycle
的speed
属性定义为私有的,在Java的实现方式便是:
- 将speed成员变量使用private修饰;
- 提供speed的getter方法;
而Dart并没有访问修饰词(默认都是public),那如何表示私有化呢?答案是命名使用下划线,如下:
1 | int _speed = 0; |
注意:
- 未初始化的成员变量默认值都为
null
,即使是表示数值的变量 - Dart编译器会把以下划线开头的成员变量认定为私有private
- 默认地,Dart为所有public的成员变量隐式提供了对应的setter和getter方法,我们不需要特地去编写对应的setter和getter,除非将变量强制为只读 or 只写,或者在getter or setter中添加额外的操作等。
- 因为默认成员变量为public,例如
Bicycle
类的cadence
和gear
,所以直接通过bike.cadence
和bike.gear
进行访问
最后,将speed定义为私有后的Bicycle
类:
1 | class Bicycle { |
3. 可选命名参数(替代重载)
在Java中,经常会重载方法:也就是编写具有相同函数名,但参数列表不同的多个方法。而Dart不允许重载,并通过可选命名参数代替了重载。
现在定义一个矩形类Rectangle
,包含三个成员变量:origin
、width
和height
:
1 | class Rectangle { |
如何通过可选命名参数来代替重载,我们以构造方法为例:
1 | Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0}); |
注意可选命名参数必须包含在花括号中{ }
,且指定的默认值必须是编译期常量(也就是编译期便可确定值)。又例如:
1 | // 其中x、y为可选命名参数,r不是 |
使用可选命名参数的方法,在调用时可选命名参数必须指明赋值的局部变量,而非可选命名参数则不需要:
1 | void main() { |
4. 创建工厂模式
这一小节将介绍两种创建Shape的工厂,先介绍一下Shape类:
1 | import 'dart:math'; |
上面的代码中:
- Dart支持抽象类(abstract)
- 在同一个文件中能够定义多个class
- Dart的核心库包括了:
dart.math
、dart:core
、dart:async
、dart:convert
和dart:collection
- 在Dart 1.x版本常量均为大写(例如PI),在Dart 2都改为小写(例如pi)
接下来介绍一下创建工厂模式两种方式。
4.1 顶层方法
在所有类的外部创建一个工厂方法:
1 | Shape shapeFactory(String type) { |
说明一下:
- Dart同Java一样能够抛出异常,也定义了很多常用的异常类,我们能够继承、使用这些常用的异常类。
- Dart支持通过throw一个字符串表示遇到的异常信息:
throw 'Occurred NPE'
- Dart同样使用try/catch来进行异常的捕获:
1
2
3
4
5try {
// exception occurred
} catch(err) {
print(err);
}
4.2 工厂构造器
Dart提供了factory
关键字用于创建工厂构造器。下面在Shape
的抽象类中添加一个工厂构造器:
1 | abstract class Shape { |
5. 实现接口
Dart同样也有接口的思想,但Dart并没有interface
的关键字,因为每个类都被定义为接口,也就是一个类,既能被当成父类进行继承extends,也能当做接口进行实现implements:
1 | class A { |
我们定义一个CircleMock
实现我们上面的Circle
类:
1 | class Circle implements Shape { |
此时DartPad会提示编译错误:Error: The non-abstract class 'CircleSub' is missing implementations for these members: 'radius', 'area'.
,编译出错可以这么理解:
1 | // Circle被作为了接口实现 |
6. 函数式编程
在函数式编程中,可以把方法作为为参数、将方法赋值给变量、支持lambda表达式。
Dart支持函数式编程,所以也支持以上提到的特点,此外,Dart中方法也是一个对象,且具有对应的类型Function,因此Dart中可以将方法赋值给变量或传递给参数。
6.1 类实例作为方法调用
Dart还可以将类实例作为方法一样调用(但该类必须包含call()
方法),例如:
1 | class WannabeFunction { |
6.2 命令式代码(非功能性)
1 | String scream(int length) => "A${'a' * length}h!"; |
打印的结果是:
1 | Aah! |
上面使用了字符串插值器A${'a' * length}h!
,表示将a
字母重复length
次。
6.3 将命令式代码转化为功能性代码
我们将上面的代码的for(){ }
循环使用方法链的形式进行替换:
1 | String scream(int length) => "A${'a' * length}h!"; |
上面的代码挺难理解的,可以这么理解:
map
可以理解为一个迭代器,values.map()
便是对values
中的数值进行迭代,迭代所做的操作就交由map()
方法的参数,这里传入的就是scream
,也就是scream()
方法,用Java代码表示便是:1
2
3
4
5
6// for相当于map,进行迭代
for (int val : values) {
// map()的参数便是对迭代进行的操作
// 这里便是调用scream()方法,因为函数式编程中方法可以作为参数进行传递
scream(val);
}forEach()
可以理解为对处理结果进行的最后操作,例如打印(传入print
方法)。forEach()
这个在Java 8也已支持。- 综上,
values.map(scream).forEach(print);
这行代码意思便是,对values
进行遍历,对其中每一个值进行scream()
处理,然后调用print()
进行处理后的最后操作。
6.4 集合的新功能点
Dart在dart:collection
库中还包含了一些迭代的新功能点,例如fold
、where
、join
和skip
等:
1 | // values.map(scream).forEach(print); |
打印的结果:
1 | Aaah! |
其中:
skip(1)
表示遍历values
时跳过第一个take(3)
表示遍历时,获取接下来的三个值,剩下的值忽略掉map()
和forEach()
如上面解释,进行遍历和处理
7. 写在最后
以上是关于Java和Dart的一些区别,只要会Java上手Dart应该还是比较简单的。但上面也只讲了一小部分内容,尚未包括异步/同步、方法级联、Null操作。
当然,笔者个人觉得,并不需要要完全学习完Dart才能上手Flutter,学习一下基本的Dart,在Flutter中一边摸索中一边学习Dart应该是最合适的。
codelabs上面还提供了一些学习的链接:
文章:
- Why Flutter Uses Dart
- Announcing Dart 2: Optimized for Client-Side Development
- Why I moved from Java to Dart
资源:
- A Tour of the Dart Language
- A Tour of the Dart Libraries
- Effective Dart
网页:
- Dart Language: www.dartlang.org
- Dart for the web, including AngularDart: webdev.dartlang.org
- Flutter mobile app SDK: flutter.io