Dart 是 Flutter 的主要开发语言,Flutter 是一个来自 Google 用于移动应用开发的 SDK。本 codelab 将着重向你介绍 Java 开发人员可能并不了解的一些关于 Dart 的特性。
你可以在 1 分钟之内快速地写出 Dart 的函数,在 5 分钟内编写出脚本,在 10 分钟内快速编写出移动端的应用。
你只需要一个浏览器就能完成这个 codelab !
你将在 DartPad 中编写和运行所有的示例代码,DartPad 是一个集成了 Dart 语言特性和核心代码库的一个交互式工具,它可以直接运行于浏览器中。当然,如果你喜欢的话,也可以使用别的 IDE 工具来进行编码,比如 WebStorm,提供了 Dart 插件的 IntelliJ 工具,或者是集成了 Dart Code extension 的 Visual Studio Code 编辑器。
你将从创建一个简单的 Bicycle 类来开始学习 Dart,这个类同在 Java 教程中所创建的类拥有同样的功能。 Bicycle 类包含了一些私有的成员变量,并对外暴露了 getter 和 setter 方法用来对成员变量进行操作。而在 main()
方法中会进行对 Bicycle 类的实例化操作并将其在控制台中打印出来。
这个 codelab 对每一组练习都提供了独立的实例。以下的链接会打开一个全新的默认包含 ‘Hello' 例子的 DartPad 实例。你可以在整个 codelab 的学习过程中一直使用同一个 DartPad,但一旦你点击了 Reset 按钮,DartPad 将会丢失你所有的工作而返回到默认状态。
DartPad 会在顶栏显示当前实例的状态,方便进行查看。
注意,DartPad 会对它的代码及时运行。
在 main()
函数的上方,增加一个 Bicycle 类,它有三个实例变量,然后移除 main()
函数中的内容,如下:
class Bicycle {
int cadence;
int speed;
int gear;
}
void main() {
}
注意
main()
方法是 Dart 的主方法,如果你需要访问命令行传递过来的参数,可以使用 main(List<String> args
方法。main()
方法存在于最外层的作用域,在 Dart 中你可以在类之外编写代码,变量、方法、存取方法都可以独立于类之外维持生命周期。main()
还是 Bicycle 类都声明为 public 的,默认情况下都是 public 的,在 Dart 中没有诸如 public、private、protected 这样的关键词。在 Bicycle 类中增加以下的构造函数:
Bicycle(this.cadence, this.speed, this.gear);
注意
Bicycle(int cadence, int speed, int gear) {
this.cadence = cadence;
this.speed = speed;
this.gear = gear;
}
在任何时候都可以点击 DartPad 界面上的 Format 按钮来进行代码的格式化,此功能在向 DartPad 中粘贴代码时特别有用。
点击 Format 按钮
向 main()
方法中增加下列代码:
void main() {
var bike = new Bicycle(2, 0, 1);
print(bike);
}
移除可选的 new
关键字:
var bike = Bicycle(2, 0, 1);
注意
点击 DartPad 上方的 Run 按钮来执行代码,如果 Run 按钮不可点击则代表有错误,可以通过 Problems 模块进行查看。
你将会看到下列这样的输出文本:
Instance of 'Bicycle'
注意
var bike = ...
定义了一个 Bicycle 的实例。刚刚得到的输出 "Instance of ‘Bicycle'" 虽然是正确的,但是并没有显示出更具体的信息。所有的 Dart 类中都有一个 toString()
方法,你可以复写这个方法来提供更具体的输出信息。
在 Bicycle 类中的任意位置增加 toString()
方法:
@override
String toString() => 'Bicycle: $speed mph';
注意
@override
会告诉分析器你当前是在复写某个成员方法,如果该复写不成功,分析器就会报错。${expression}
的方式来实现字符串模板的效果,如果该表达式仅仅是一个标识符,还可以去掉花括号 $variableName
。=>
来简化方法的书写。点击 Run
你将会看到如下输出:
Bicycle: 0 mph
遇到了问题?
之前的 Java 示例定义了一个只读的变量 speed
,并将其声明为 private 的,提供了一个 getter 方法。接下来,你将使用 Dart 来实现同样的方法。
在 DartPad 中打开 bicycle.dart (或者继续使用你当前的副本)
你可以在变量名前增加下划线 _ 来标记为它是私有的,也就是说可以仅仅通过改变变量名来实现将 speed
标记为只读的。
将 speed 标记为私有、只读的成员变量
在 Bicycle 构造函数中,移除 speed
参数:
Bicycle(this.cadence, this.gear);
在 main() 方法中,移除 Bicycle 构造函数调用中的第二个参数 (speed) :
var bike = Bicycle(2, 1);
将剩余使用到 speed 的地方将其更改为 _speed (有两个地方需要更改)
初始化 _speed 为 0:
int _speed = 0;
在 Bicycle 类中增加下列的 getter 方法
int get speed => _speed;
注意
完成将 speed 作为实例变量的代码
在 Bicycle 类中增加下列代码:
void applyBrake(int decrement) {
_speed -= decrement;
}
void speedUp(int increment) {
_speed += increment;
}
最终的 Dart 实例和一开始的 Java 版本看上去很像,但是 Dart 书写的代码只有 23 行,而 Java 的则有 40 行:
class Bicycle {
int cadence;
int _speed = 0;
int get speed => _speed;
int gear;
Bicycle(this.cadence, this.gear);
void applyBrake(int decrement) {
_speed -= decrement;
}
void speedUp(int increment) {
_speed += increment;
}
@override
String toString() => 'Bicycle: $_speed mph';
}
void main() {
var bike = Bicycle(2, 1);
print(bike);
}
遇到了问题?
The next exercise defines a Rectangle class, another example from the Java Tutorial.
接下来的练习是定义另外一个来自 Java 教程的 Rectangle 类。
之前的 Java 代码中,使用了重载构造函数的方法,该方法在 Java 中很普遍,重载的构造函数和之前的构造函数具有相同的方法名,但是在方法的参数个数或者参数类型上有些许不同。Dart 并不支持构造函数的重载,而采用了另外一种方法来处理这种情况,我们将在本章接下来的地方看到它是如何实现的。
同之前的 Java 示例代码中不同,可以直接添加下列的一个没有方法体的构造方法来替代 Java 代码中的四个构造方法。
Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});
这个构造方法使用了可选类型参数。
注意
this.origin
, this.width
和 this.height
使用了 Dart 提供的简便方法来直接对类中的实例变量进行赋值。this.origin
, this.width
和 this.height
嵌套在闭合的花括号中 ({}
) ,用来表示它们是可选的命名参数。this.origin = const Point(0, 0)
这样的代码表明给实例变量 origin
提供了默认的值 Point(0,0)
,默认值必须是在编译期就可以确定的常量。上述代码中的构造方法为三个实例变量都提供了默认参数。 在 Rectangle 类中增加下列 toString()
方法:
@override
String toString() =>
'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';
使用下列方法来替代 main()
方法,用来验证传递不同数量的参数来实例化 Rectangle 是否成功。
main() {
print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
print(Rectangle(origin: const Point(10, 10)));
print(Rectangle(width: 200));
print(Rectangle());
}
注意
Rectangle 的构造方法在 Dart 中只需要 1 行代码即可,而在 Java 中则需要 16 行代码。
你将会看到如下的输出:
Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0
遇到了问题?
在 Java 中工厂类是一个广泛使用的设计模式,它相比较直接对类进行实例化来说,具有诸多优势,比如隐藏实例化的具体细节,提供可以配置为其他类的能力,直接返回一个已有的对象而不是直接返回一个新的对象。
在该步骤中,将向你展示两种实现一个创建形状的工厂类的方法。
在这个练习中,你将会使用一个 Shapes 实例来实例化形状的类,并输出打印出他们的面积:
import 'dart:math';
abstract class Shape {
num get area;
}
class Circle implements Shape {
final num radius;
Circle(this.radius);
num get area => pi * pow(radius, 2);
}
class Square implements Shape {
final num side;
Square(this.side);
num get area => pow(side, 2);
}
main() {
final circle = Circle(2);
final square = Square(2);
print(circle.area);
print(square.area);
}
在命令行输出框中,你将会看到关于圆行和方形计算出的面积值:
12.566370614359172
4
注意
PI
);在 Dart 2 的版本中,都是小写的(pi
)。num get area => pi * pow(radius, 2); // Circle
num get area => pow(side, 2); // Square
在最外层作用域中(在所有类的作用域之外)实现一个工厂方法。
Shape shapeFactory(String type) {
if (type == 'circle') return Circle(2);
if (type == 'square') return Square(2);
throw 'Can\'t create $type.';
}
将 main() 方法中前两行代码替换为下列代码来调用刚刚实现的工厂方法。
final circle = shapeFactory('circle');
final square = shapeFactory('square');
命令行输出结果同之前的输出结果看起来一样。
注意
'circle'
或者 'square'
,那么将会抛出一个异常。Uncaught
,你可以将代码封装在 try-catch
语句中来捕获异常。你可以选择通过这个示例代码中来进行相关的练习。'Can\'t create $type.'
),如果字符串是使用双引号进行声明的"Can't create $type."
),则什么都不需要做。遇到了问题?
使用 Dart 的 factory
关键字来创建一个工厂模式的构造方法
在抽象类 Shape 中增加一个工厂模式的构造方法
abstract class Shape {
factory Shape(String type) {
if (type == 'circle') return Circle(2);
if (type == 'square') return Square(2);
throw 'Can\'t create $type.';
}
num get area;
}
将 main() 方法中的前两行替换为下列的代码来实例化 Shape 类:
final circle = Shape('circle');
final square = Shape('square');
删除之前添加的 shapeFactory()
方法
注意
shapeFactory()
方法在效果上是一致的。遇到了问题?
Dart 语言并没有提供 interface
关键字,但是每一个类都隐式地定义了一个接口。
在 DartPad 中打开 Shapes 示例代码(或者继续在你当前副本中使用)
扩展 Circle 类,增加一个 CircleMock:
class CircleMock implements Circle {}
你将会看到一个"Missing concrete implementations" 的错误,添加两个实例变量 area
和 radius
即可修复这个问题
class CircleMock implements Circle {
num area;
num radius;
}
注意
遇到了问题?
在函数式编程中,你可以做到:
Dart 支持所有的这些特性,在 Dart 中,每个函数都是一个对象,并且每个函数都有它的类型 Function,这意味着所有函数都可以支持赋值操作,以及都可以作为参数传递给其他的函数。你可以将实例化 Dart 类当做一个函数的调用行为,类似于这个例子。
下列代码使用了命令式编程的方法,并没有使用函数式:
String scream(int length) => "A${'a' * length}h!";
main() {
final values = [1, 2, 3, 5, 10, 50];
for (var length in values) {
print(scream(length));
}
}
你将会看到类似于下列的输出:
Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!
注意
${'a' * length}
这样的代码执行效果是将字符 'a'
重复 length
次 将上述代码中的 main()
方法中的 for() {...}
替换为下面的一行代码,它直接使用了链式调用的方式:
values.map(scream).forEach(print);
该函数式的方法打印出来的结果同之前的结果一样。
遇到了问题?
dart:collection 库中的 List 和 Iterable 支持 fold
, where
, join
, skip
等函数式方法,另外 Dart 还支持 Map 和 Set 类型。
将 main()
方法中的 values.map()
一行代码替换为下列代码:
values.skip(1).take(3).map(scream).forEach(print);
你将会看到下列的输出:
Aaah!
Aaaah!
Aaaaaah!
注意
skip(1)
会忽略迭代中的第一个值take(3)
会获取接下来的 3 个值,也就是 2,3 和 5遇到了问题?
在本篇 codelab 中,你了解到了 Java 和 Dart 中的一些差异。Dart 是很容易学习的,除此之外 Dart 的核心库和丰富的第三方代码库也会大大提高你的工作效率。Dart 在大型应用中表现优秀,在 Google 内部有几百个工程师将 Dart 运用于很多个优异的应用中,并持续地为 Google 创造价值。
短短的 20 分钟的 codelab 并不能像你完全展示出 Java 和 Dart 的区别。这个 codelab 并没有向你介绍:
如果你想学习更多 Dart 相关的运用,可以去尝试一下 Flutter codelab
你可以通过下列文章、资源、网站等学习更多跟 Dart 相关的知识
文章
资源
网站
感谢来自社区的 iCell 对本 Codelab 的翻译。
感谢反馈区 @TangSirOnGit, @Vadaski, @DanielFok99, @poel, @zejiang, 发现并报告错误。