百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Flutter必备——Dart入门(2)(flutterdemo)

toyiye 2024-07-06 00:32 13 浏览 0 评论

欢迎大家关注我的微信公众号“IT工匠”获取更多资源(涉及算法、数据结构、java、深度学习、计算机网络、python、Android等互联网技术资料)。

本文目录:

Dart 是一个面向对象的编程语言,同时支持基于 mixin 的继承机制。 每个对象都是一个类的实例,所有的类都继承于 Object类。 基于 Mixin 的继承 意味着每个类(Object 除外) 都只有一个超类,一个类的代码可以在其他 多个类继承中重复使用。

使用 new 关键字和构造方法来创建新的对象。 构造方法名字可以为 ClassName 或者 ClassName.identifier。例如:

var jsonData = JSON.decode('{"x":1, "y":2}');
// Create a Point using Point().
var p1 = new Point(2, 2);
// Create a Point using Point.fromJson().
var p2 = new Point.fromJson(jsonData);

对象的成员包括方法和数据 (方法 和 示例变量)。当你调用一个方法的时候,你是在一个对象上调用: 需要访问对象的方法和数据。

使用点.来引用对象的变量或者方法:

var p = new Point(2, 2);
// Set the value of the instance variable y.
p.y = 3;
// Get the value of y.
assert(p.y == 3);
// Invoke distanceTo() on p.
num distance = p.distanceTo(new Point(4, 4));

使用 ?. 来替代 . 可以避免当左边对象为 null 时候抛出异常,当?.左边的对象不为null时?.后面的方法或者对象才会被执行:

// If p is non-null, set its y value to 4.
p?.y = 4;

有些类提供了常量构造方法。使用常量构造方法可以创建编译时常量,要使用常量构造方法只需要用 const 替代 new 即可:

var p = const ImmutablePoint(2, 2);

两个一样的编译时常量其实是 同一个对象:

var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // They are the same instance!

可以使用 Object 的 runtimeType 属性来判断实例的类型,该属性返回一个 Type对象。

print('The type of a is ${a.runtimeType}');

下面介绍如何实现一个类。

实例变量

下面是如何定义实例变量的示例:

class Point {
 num x; // Declare instance variable x, initially null.
 num y; // Declare y, initially null.
 num z = 0; // Declare z, initially 0.
}

所有没有初始化的变量值都是 null。

每个实例变量都会自动生成一个 getter 方法(隐含的)。非final实例变量还会自动生成一个 setter (隐含的)方法,注意,这里提到的”隐含”的意思是,你可以通过.的形式直接对对象的实例变量取值或者设置值,形式上看起来是直接对实例变量进行操作,但是实际上是通过隐含的getter以及setter方法进行的,比如:

class Point {
 num x;
 num y;
}
main() {
 var point = new Point();
 point.x = 4; // Use the setter method for x.
 assert(point.x == 4); // Use the getter method for x.
 assert(point.y == null); // Values default to null.
}

这个getter以及setter方法我会在下面专门展开讲解。

如果你在实例变量定义的时候初始化该变量(不是在构造方法或者其他方法中初始化),该值是在实例创建的时候 初始化的,也就是在构造方法和初始化参数列表执行之前。

构造方法

定义一个和类名字一样的方法就定义了一个构造方法,当然还可以带有其他可选的标识符,详情参考命名构造方法一节。 常见的构造方法生成一个对象的新实例:

class Point {
 num x;
 num y;
 Point(num x, num y) {
 // There's a better way to do this, stay tuned.
 this.x = x;
 this.y = y;
 }
}

this 关键字指当前的实例。

注意: 只有当名字冲突的时候才使用 this。否则的话, Dart 代码风格样式推荐忽略 this。

由于把构造方法参数赋值给实例变量的场景太常见了, Dart 提供了一个语法糖来简化这个操作:

class Point {
 num x;
 num y;
 // Syntactic sugar for setting x and y
 // before the constructor body runs.
 Point(this.x, this.y);
}

默认构造方法

如果你没有定义构造方法,则会有个默认构造方法。 默认构造方法没有参数,并且会调用超类的没有参数的构造方法。

构造方法不会继承

子类不会继承超类的构造方法。 子类如果没有定义构造方法,则只有一个默认构造方法 (没有名字没有参数),这里的”子类不会继承超类的方法”可能有点抽象,我们来看如下代码:

class Child extends Dad {
 Child(name) : super(name);
}
class Dad {
 var nama = 'Joey';
 var age = 0;
 Dad(name) {
 this.nama = nama;
 }
 Dad.fromNameAndAge(name, age) {
 this.nama = nama;
 this.age = age;
 }
}
void main() {
 var child=Child('tony');
 var child2=Child.fromNameAndAge('name',12);
}

Child是Dad的子类,Child继承了Dad的默认构造方法,但是不能继承Dad的其他构造方法

命名构造方法

使用命名构造方法可以为一个类实现多个构造方法(你只能声明一个默认构造方法), 或者使用命名构造方法来更清晰的表明你的意图:

class Point {
 num x;
 num y;
 Point(this.x, this.y);
 // Named constructor
 Point.fromJson(Map json) {
 x = json['x'];
 y = json['y'];
 }
}

注意:构造方法不能继承,所以超类的命名构造方法也不会被继承。如果你希望子类也有超类一样的命名构造方法, 你必须在子类中自己实现该构造方法。

调用超类构造方法

默认情况下,子类的构造方法会自动调用超类的无名无参数的默认构造方法。 超类的构造方法在子类构造方法体开始执行的位置调用。 如果提供了一个初始化参数列表 ,则初始化参数列表在超类构造方法执行之前执行。 下面是构造方法执行顺序:

  1. 子类的具有初始化参数列表的构造方法
  2. 超类的无名默认构造方法
  3. 本类的无名默认构造方法

如果超类没有无名无参数构造方法, 则你需要手工的调用超类的其他构造方法。 在构造方法参数后使用冒号 (:) 可以调用超类构造方法。

下面的示例中,Employee 类的构造方法调用 了超类 Person 的命名构造方法,因为在调用构造方法之前会计算超类构造方法的参数,所以参数可以是一个表达式,例如方法调用:

class Employee extends Person {
 // ...
 Employee() : super.fromJson(findDefaultData());
}

注意: 如果在构造方法的初始化列表中使用 super(),需要把它放到最后。

警告: 传入超类构造方法中的参数无法访问 this。 例如,参数可以为静态方法但是不能是实例方法,比如:

如图,a()是Child类中的一个实例方法,试图将a()传入Child的超类的构造方法中,编译器会提示"Only static members can be accessed in initalizers",但是我们将b()作为参数传入是没有任何问题的,编译器不会报错,因为b()是一个静态方法:

初始化列表

在构造方法体执行之前除了可以调用超类构造方法之外,还可以初始化实例参数, 使用逗号分隔初始化表达式。

初始化列表非常适合用来设置 final 变量的值。 下面示例代码中初始化列表设置了三个 final 变量的值:

class Point {
 final num x;
 final num y;
 final num distanceFromOrigin;
 Point(x, y)
 : x = x,
 y = y,
 distanceFromOrigin = sqrt(x * x + y * y);
}

警告: 初始化表达式等号右边的部分不能访问 this。

重定向构造方法

有时候一个构造方法会调动类中的其他构造方法。 一个重定向构造方法是没有代码的,在构造方法声明后,使用 冒号调用其他构造方法。

class Point {
 num x;
 num y;
 // The main constructor for this class.
 Point(this.x, this.y);
 // Delegates to the main constructor.
 Point.alongXAxis(num x) : this(x, 0);
}

常量构造方法

如果你的类提供一个状态不变的对象,你可以把这些对象定义为编译时常量。要实现这个功能,需要定义一个 const 构造方法, 并且声明所有变量为 final的:

class ImmutablePoint {
 final num x;
 final num y;
 const ImmutablePoint(this.x, this.y);
 static final ImmutablePoint origin =
 const ImmutablePoint(0, 0);
}

工厂方法构造方法

如果一个构造方法并不总是返回一个新的对象,则使用 factory 来定义这个构造方法。例如,一个工厂构造方法 可能从缓存中获取一个实例并返回,或者返回一个子类型的实例。

下面代码演示工厂构造方法如何从缓存中返回对象。

class Logger {
 final String name;
 bool mute = false;
 // _cache is library-private, thanks to the _ in front
 // of its name.
 static final Map<String, Logger> _cache =
 <String, Logger>{};
 factory Logger(String name) {
 if (_cache.containsKey(name)) {
 return _cache[name];
 } else {
 final logger = new Logger._internal(name);
 _cache[name] = logger;
 return logger;
 }
 }
 Logger._internal(this.name);
 void log(String msg) {
 if (!mute) {
 print(msg);
 }
 }
}

注意: 工厂构造方法无法访问 this。

使用 new 关键字来调用工厂构造方法。

var logger = new Logger('UI');
logger.log('Button clicked');

方法

肯定有的读者看到这里心里会有疑惑,前面不是讲了Function(函数)了吗,怎么这里又出来了个方法,方法和函数有什么区别?其实说白了,“方法”是相对于“类”来说的,我们说方法,一定指的是类中的方法,熟悉Java的同学可能都听过一句话,叫做“Java中没有函数、只有方法”,为什么这么说?因为Java中你写的所有函数都一定是处于某个类中的,那么既然函数处于类中,就应该称之为方法,而不是函数。但是在Dart中就不一定了,Dart中你可以在类中写函数,此时写的函数我们称之为方法,你也可以不将函数置于类中,比如:

void main() {
 var child = Child('tony');
}

这个函数可以脱离于类单独存在,它就可以称为函数。但是在Java中是不能这么写的,就算是Java中的主方法,也一定要依附于一个类。

所以总结一下,方法是类中定义的函数,是类对应的对象的行为。

实例方法

对象的实例方法可以访问 this。 例如下面示例中的 distanceTo() 方法 就是实例方法:

import '` Dart`:math';
class Point {
 num x;
 num y;
 Point(this.x, this.y);
 num distanceTo(Point other) {
 var dx = x - other.x;
 var dy = y - other.y;
 return sqrt(dx * dx + dy * dy);
 }
}

Getter、Setter

Getter 和 Setter 是用来设置和访问对象属性的特殊方法。每个实例变量都隐含的具有一个 getter, 如果变量不是 final 的则还有一个 setter。 你使用 get 和 set 关键字来定义 getter 和 setter,这样你可以在getter或setter中添加一些额外的操作:

class Rectangle {
 num left;
 num top;
 num width;
 num height;
 Rectangle(this.left, this.top, this.width, this.height);
 // Define two calculated properties: right and bottom.
 num get right => left + width;
 set right(num value) => left = value - width;
 num get bottom => top + height;
 set bottom(num value) => top = value - height;
}
main() {
 var rect = new Rectangle(3, 4, 20, 15);
 assert(rect.left == 3);
 rect.right = 12;
 assert(rect.left == -8);
}

注意: 像 (++) 这种操作符不管是否定义 getter 都会正确的执行。 为了避免其他副作用, 操作符只调用 getter 一次,然后 把其值保存到一个临时变量中。

抽象方法

实例方法、 getter、和 setter 方法可以为抽象方法, 抽象方法是只定义方法接口但是没有实现的方法,由子类来实现该方法。如果用分号来替代方法体则这个方法就是抽象方法。

abstract class Doer {
 // ...Define instance variables and methods...
 void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
 void doSomething() {
 // ...Provide an implementation, so the method is not abstract here...
 }
}

调用一个没实现的抽象方法会导致运行时异常。

可覆写的操作符

下表中的操作符可以被覆写。 例如,如果你定义了一个 Vector 类, 你可以定义一个 + 方法来实现两个向量相加。

<+``[]>/^[]=<=~/&~>=*<<==–%>>

下面是覆写了 + 和 - 操作符的示例:

class Vector {
 final int x;
 final int y;
 const Vector(this.x, this.y);
 /// Overrides + (a + b).
 Vector operator +(Vector v) {
 return new Vector(x + v.x, y + v.y);
 }
 /// Overrides - (a - b).
 Vector operator -(Vector v) {
 return new Vector(x - v.x, y - v.y);
 }
}
main() {
 final v = new Vector(2, 3);
 final w = new Vector(2, 2);
 // v == (2, 3)
 assert(v.x == 2 && v.y == 3);
 // v + w == (4, 5)
 assert((v + w).x == 4 && (v + w).y == 5);
 // v - w == (0, 1)
 assert((v - w).x == 0 && (v - w).y == 1);
}

如果你覆写了 == ,则还应该覆写对象的 hashCode getter 方法。

抽象类

使用 abstract 修饰符定义一个 抽象类:一个不能被实例化的类。 抽象类通常用来定义接口,以及部分实现。如果你希望你的抽象类是可实例化的,则可以定义一个工厂构造方法。

抽象类通常具有抽象方法。 下面是定义具有抽象方法的抽象类的示例:

// 这个类被声明为抽象类,不能被实例化
abstract class AbstractContainer {
 // ...定义构造方法、实例变量...
 void updateChildren(); // 抽象方法
}

下面的类不是抽象的,但是定义了一个抽象方法,这样的类是可以被实例化的:

class SpecializedContainer extends AbstractContainer {
 // ...Define more constructors, fields, methods...
 void updateChildren() {
 // ...Implement updateChildren()...
 }
 //实例化具有抽象方法的非抽象类时会导致警告,但不会报错
 void doSomething();
}

隐式接口

每个类都隐式的定义了一个包含所有实例成员的接口, 并且这个类实现了这个接口,也就是说,只要你定义了一个类,都会随之产生一个该类逇隐式接口,这样你不仅可以继承(extends)该类,还可以实现(implements)该类,比如你想让类 A 具有类 B 中定义的 api,一种方法是你可以让类A继承(extends)类B,然后在类A中覆写类B中的api,另一种方法是你可以直接实现(implements)B的隐式接口。

一个类可以通过 implements 关键字来实现一个或者多个接口, 并实现每个接口定义的 API。 例如:

// Person类,其隐式接口中含有一个greet()方法(_name是私有成员,其隐式接口中不可见)
class Person {
 final _name;
 // 构造方法不在隐式接口中
 Person(this._name);
 String greet(who) => 'Hello, $who. I am $_name.';
}
// Person 接口的实现类.
class Imposter implements Person {
 // We have to define this, but we don't use it.
 final _name = "";
 String greet(who) => 'Hi $who. Do you know who I am?';
}
greetBob(Person person) => person.greet('bob');
main() {
 print(greetBob(new Person('kathy')));
 print(greetBob(new Imposter()));
}

下面是实现多个接口的示例:

class Point implements Comparable, Location {
 // ...
}

扩展类

使用 extends 定义子类, supper 引用超类:

class Television {
 void turnOn() {
 _illuminateDisplay();
 _activateIrSensor();
 }
 // ...
}
class SmartTelevision extends Television {
 void turnOn() {
 super.turnOn();
 _bootNetworkInterface();
 _initializeMemory();
 _upgradeApps();
 }
 // ...
}

子类可以覆写父类的实例方法gettersetter。 下面是覆写 Object 类的 noSuchMethod() 方法的例子, 如果调用了对象上不存在的方法,则就会触发 noSuchMethod() 函 数。

class A {
 // 除非你覆写noSuchMethod方法,否则如果你调用A的对象的一个不存在的方法,就会抛出NoSuchMethodError.
 void noSuchMethod(Invocation mirror) {
 print('You tried to use a non-existent member:' +
 '${mirror.memberName}');
 }
}

还可以使用 @override 注解来显式表明你的方法是想覆写超类的一个方法:

class A {
 @override
 void noSuchMethod(Invocation mirror) {
 // ...
 }
}

如果你使用 noSuchMethod() 方法来实现每个可能的 getter 、setter、 以及其他类型的方法,你可以使用 @proxy 注解来避免警告信息:

@proxy
class A {
 void noSuchMethod(Invocation mirror) {
 // ...
 }
}

如果你知道编译时的具体类型,则可以实现这些类来避免警告,和 使用 @proxy效果一样:

class A implements SomeClass, SomeOtherClass {
 void noSuchMethod(Invocation mirror) {
 // ...
 }
}

枚举类型

枚举类型通常称之为 enumerations 或者 enums, 是一种特殊的类,用来表现一个固定数目的常量。

使用 enum 关键字来定义枚举类型:

enum Color {
 red,
 green,
 blue
}

枚举类型中的每个值都有一个 index 的getter 方法, 该方法返回该值在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的位置为 0, 第二个为 1.

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

枚举的 values 常量可以返回 所有的枚举值。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

可以在 switch 语句中使用枚举。 当 switch (e) 中的 e 的类型为枚举类, 而你又没有处理所有该枚举类型的值,则会抛出一个警告:

enum Color {
 red,
 green,
 blue
}
// ...
Color aColor = Color.blue;
switch (aColor) {
 case Color.red:
 print('Red as roses!');
 break;
 case Color.green:
 print('Green as grass!');
 break;
 default: // Without this, you see a WARNING.
 print(aColor); // 'Color.blue'
}

枚举类型具有如下的限制:

  • 无法继承枚举类型、无法使用 mixin(下面介绍)、无法实现(implements)一个枚举类型
  • 无法显式地初始化一个枚举类型

为类添加新的功能(class: mixins)

在介绍mixins的语法之前,我先来解释一下什么是mixins、为什么需要mixins:

如上图是一个类图,我们直接看重点,软件工程师和信息老师具有修电脑的技能,建筑工程师和美术老师都具有绘图的技能,我们如何去实现这种关系?最直接的方法是我们将修电脑和绘图写成两个类,然后让软件工程师、信息老师继承修电脑这个类,让建筑工程师、美术老师继承绘图这个类,但是注意,软件工程师已经继承了工程师这个类,在Dart中是单继承的,他不可能在继承工程师这个类的基础上再继承修电脑类,建筑工程师、美术老师、信息老师也有一样的问题,所以不能通过继承的方法实现这几类人群和技能的耦合关系。

可能有读者会想到用接口,没错,可以用接口,那么我们想一下如果使用接口需要我们怎么去实现,我们应该定义修电脑和绘图这两个接口,然后让软件工程师、信息老师实现修电脑这个接口,让建筑工程师、美术老师实现绘图这个接口,这样做功能是实现了,但是问题在于,软件工程师、信息老师他们两一共实现了两次修电脑这个接口,而实现代码中的内容是一样的,也就是说出现了冗余的代码,现在是2个类,如果有100个类都具有修电脑的功能呢?实现100次相同的代码吗?为了解决这一问题,Dart引入了mixins特性,我们可以分别实现一个修电脑和绘图对应的mixins类,然后使用with 关键字来直接引入修电脑或绘图中的代码,这样相当于代码重用,我们来看看具体用法:

使用 with 关键字,后面为一个或者多个使用 mixin 修饰的类的类名。 下面是示例显示了如何使用 mixin:

class Maestro extends Person
 with Musical, Aggressive, Demented {
 ...
}

要实现mixin,请创建一个继承自Object的类,并且不声明构造函数。除非你希望mixin可用作常规类,否则请使用mixin关键字而不是class。例如:

mixin Musical {
 bool canPlayPiano = false;
 bool canCompose = false;
 bool canConduct = false;
 void entertainMe() {
 if (canPlayPiano) {
 print('Playing piano');
 } else if (canConduct) {
 print('Waving hands');
 } else {
 print('Humming to self');
 }
 }
}

如果要指定仅仅特定类可以通过with关键字使用本mixins类的代码,则可以在实现mixins类的时候加上关键字on:

mixin MusicalPerformer on Musician {
 // ···
}

如上代码,这样只有Musician类可以使用with MusicalPerformer。

注意: 从 Dart` 1.13` 开始, 这两个限制在 DartVM 上 没有那么严格了:

  • Mixins 可以继承其他类,不再限制为继承 Object
  • Mixins 可以调用 super()。

类变量和方法

使用 static 关键字来实现类级别的变量和方法。

静态变量

静态变量(类变量)对于类范围的状态和常量是很有用的:

class Color {
 static const red =
 const Color('red'); // A constant static variable.
 final String name; // An instance variable.
 const Color(this.name); // A constant constructor.
}
main() {
 assert(Color.red.name == 'red');
}

静态变量在第一次使用的时候才被初始化。

静态方法

静态方法和对象是解耦合的,也就是说,不能通过类的对象来调用类的静态方法,不能在静态方法中访问类的非静态变量:

import '` Dart`:math';
class Point {
 num x;
 num y;
 Point(this.x, this.y);
 static num distanceBetween(Point a, Point b) {
 var dx = a.x - b.x;
 var dy = a.y - b.y;
 return sqrt(dx * dx + dy * dy);
 }
}
main() {
 var a = new Point(2, 2);
 var b = new Point(4, 4);
 var distance = Point.distanceBetween(a, b);
 assert(distance < 2.9 && distance > 2.8);
}

注意:对于常见或广泛使用的实用程序和功能,请考虑使用顶级函数(全局函数,注意是函数而不是方法)而不是静态方法。

静态方法还可以当做编译时常量使用。例如, 你可以把静态方法当做常量构造方法的参数来使用。

泛型

如果你查看List类的 API 文档, 则可以看到其实际的类型为 List<E>。 这个 <…> 声明 list 是一个 泛型 (或者 参数化) 类型。 通常情况下,使用一个字母来代表类型参数, 例如 E, T, S, K, 和 V 等。

为何使用泛型

类型安全通常需要泛型,但它们比仅允许代码运行有更多好处:

  • 正确指定泛型类型会产生更好的生成代码。
  • 你可以使用泛型来减少代码的重复。

例如,如果你希望一个 list 只包含字符串对象,你可以 定义为 List<String> (代表 “list of string”)。这样你、 你的同事、以及所使用的工具 ( IDE 以及 检查模式的 ` Dart VM)可以帮助你检查你的代码是否把非字符串类型对象给放到 这个list` 中了。下面是一个示例:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// ...
names.add(42); // Fails in checked mode (succeeds in production mode).

另外一个使用泛型的原因是减少重复的代码。 泛型可以在多种类型之间定义同一个实现, 同时还可以使用检查模式和静态分析工具提供的代码分析功能。 例如,你创建一个保存缓存对象的接口:

abstract class ObjectCache {
 Object getByKey(String key);
 setByKey(String key, Object value);
}

后来你发现你需要一个用来缓存字符串的实现, 则你又定义另外一个接口:

abstract class StringCache {
 String getByKey(String key);
 setByKey(String key, String value);
}

然后,你又需要一个用来缓存数字的实现, 在后来,你又需要另外一个类型的缓存实现,等等。。。

泛型可以避免这种重复的代码。 你只需要创建一个接口即可:

abstract class Cache<T> {
 T getByKey(String key);
 setByKey(String key, T value);
}

在上面的代码中,T 是一个备用类型。这是一个类型占位符, 在开发者调用该接口的时候会指定具体类型。

使用集合字面量

List 和 map 字面量也是可以参数化的。 参数化定义 list 需要在中括号之前 添加 <type> , 定义 map 需要在大括号之前 添加 <keyType, valueType>。 如果你需要更加安全的类型检查,则可以使用参数化定义。下面是一些示例:

var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
 'index.html': 'Homepage',
 'robots.txt': 'Hints for web robots',
 'humans.txt': 'We are people, not machines'
};

在构造方法中使用泛型

在调用构造方法的时候, 在类名字后面使用尖括号(<...>)来指定 泛型类型。例如:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = new Set<String>.from(names);

下面代码创建了一个 key 为 integer, value 为 View 类型 的 map:

var views = new Map<int, View>();

通用集合及其包含的类型

Dart 的泛型类型是固化的,在运行时有也可以判断具体的类型。例如在运行时也可以检测集合里面的对象类型:

var names = new List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

注意 is 表达式只是判断集合的类型,而不是集合里面具体对象的类型。

注意: Java 中的泛型信息是编译时的,泛型信息在运行时是不纯在的(类型擦除)。 在 Java 中你可以测试一个对象是否为 List, 但是你无法测试一个对象是否为 List<String>。

限制泛型类型

当使用泛型类型的时候,你可能想限制泛型的具体类型。 使用 extends 可以实现这个功能:

// T must be SomeBaseClass or one of its descendants.
class Foo<T extends SomeBaseClass> {...}
class Extender extends SomeBaseClass {...}
void main() {
 // It's OK to use SomeBaseClass or any of its subclasses inside <>.
 var someBaseClassFoo = new Foo<SomeBaseClass>();
 var extenderFoo = new Foo<Extender>();
 // It's also OK to use no <> at all.
 var foo = new Foo();
 // Specifying any non-SomeBaseClass type results in a warning and, in
 // checked mode, a runtime error.
 // var objectFoo = new Foo<Object>();
}

使用泛型方法

一开始,泛型只能在 Dart 类中使用。 新的语法也支持在方法上使用泛型了:

T first<T>(List<T> ts) {
 // ...Do some initial work or error checking, then...
 T tmp ?= ts[0];
 // ...Do some additional checking or processing...
 return tmp;
}

这里的 first (<T>) 泛型可以在如下地方使用 参数 T :

  • 方法的返回值类型 (T).
  • 参数的类型 (List<T>).
  • 局部变量的类型 (T tmp).

版本说明: 在 Dart SDK 1.21开始可以使用泛型方法。

如果你使用了泛型方法,则需要 设置 SDK 版本号为 1.21 或者更高版本。

库和可见性

使用 import 和 library 指令可以帮助你创建模块化的可共享的代码。库不仅仅提供 API, 还是一个私有单元:以下划线 (_) 开头的标识符只有在库内部可见。每个 Dart app 都是一个库, 即使没有使用 library 命令也是一个库。

使用库

使用 import 来指定一个库如何使用另外一个库。

例如, Dart web 应用通常使用 Dart:html库,然后可以这样导入库:

import 'Dart:html';

import 必须参数为库 的 URI。 对于内置的库,URI 使用特殊的 Dart:scheme。 对于其他的库,你可以使用文件系统路径或者 package: scheme。 package: scheme 指定的库通过包管理器来提供, 例如 pub 工具。

import ' Dart:io';
import 'package:mylib/mylib.Dart';
import 'package:utils/utils.`Dart`';

注意: URI 代表 uniform resource identifier。 URLs (uniform resource locators) 是一种常见的 URI。

指定库前缀

如果你导入的两个库具有冲突的标识符, 则你可以使用库的前缀来区分。 例如,如果 library1 和 library2 都有一个名字为 Element 的类, 你可以这样使用:

import 'package:lib1/lib1.Dart';
import 'package:lib2/lib2.Dart' as lib2;
// ...
Element element1 = new Element(); // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.

导入库的一部分

如果你只使用库的一部分功能,则可以选择需要导入的 内容。例如:

// Import only foo.
import 'package:lib1/lib1.` Dart`' show foo;
// Import all names EXCEPT foo.
import 'package:lib2/lib2.` Dart`' hide foo;

延迟载入库

Deferred loading (也称之为 lazy loading) 可以让应用在需要的时候再加载库。 下面是一些使用延迟加载库的场景:

  • 减少 APP 的启动时间。
  • 执行 A/B 测试,例如 尝试各种算法的不同实现。
  • 加载很少使用的功能,例如可选的屏幕和对话框。

要延迟加载一个库,需要先使用 deferred as 来 导入:

import 'package:deferred/hello.` Dart`' deferred as hello;

当需要使用的时候,使用库标识符调用 loadLibrary() 方法来加载库,在一个库上你可以多次调用 loadLibrary() 方法, 但是该库只载入一次:

greet() async {
 await hello.loadLibrary();
 hello.printGreeting();
}

在前面的代码, 使用 await 关键字暂停代码执行一直到库加载完成。 关于 async 和 await 会在下面的”异步支持”一节介绍。

使用延迟加载库的时候,请注意以下问题:

  • 延迟加载库的常量在导入的时候是不可用的, 只有当库加载完毕的时候,库中常量才可以使用。
  • 在导入文件的时候无法使用延迟库中的类型, 如果你需要使用类型,则考虑把接口类型移动到另外一个库中, 让两个库都分别导入这个接口库。
  • Dart 隐含的把 loadLibrary() 方法导入到使用 deferred as namespace 中。 loadLibrary() 方法返回一个 Future。

异步支持

Dart 有一些语言特性来支持异步编程,最常见的特性是 async 方法和 await 表达式。

Dart 库中有很多返回 Future 或者 Stream 对象的方法。 这些方法是 异步的: 这些方法在设置完基本的操作后就返回了, 而无需等待操作执行完成。 例如读取一个文件,在打开文件后就返回了。

有两种方式可以使用 Future 对象中的数据:

  • 使用 async 和 await
  • 使用 Future API

同样,从 Stream 中获取数据也有两种 方式:

  • 使用 async 和一个 异步 for 循环 (await for)
  • 使用 Stream API

使用 async 和 await 的代码是异步的, 但是看起来有点像同步代码。 例如,下面是一些使用 await 来等待异步方法返回的示例:

await lookUpVersion()

要使用 await,其宿主方法必须带有 async 关键字:

checkVersion() async {
 var version = await lookUpVersion();
 if (version == expectedVersion) {
 // Do something.
 } else {
 // Do something else.
 }
}

可以使用 try, catch, 和 finally 来处理使用 await 的异常:

try {
 server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
 // React to inability to bind to the port...
}

声明异步方法

一个 async 方法是方法体被标记为 async 的方法。 虽然异步方法的执行可能需要一定时间,但是异步方法会立刻返回( 在方法体还没执行之前就返回了):

checkVersion() async {
 // ...
}
lookUpVersion() async => /* ... */;

在一个方法上添加 async 关键字,则这个方法返回值为 Future。 例如,下面是一个返回字符串 的同步方法:

String lookUpVersionSync() => '1.0.0';

如果使用 async 关键字,则该方法 返回一个 Future,并且认为该方法是一个耗时的操作。

Future<String> lookUpVersion() async => '1.0.0';

注意,方法的方法体并不需要使用 Future API。 Dart 会自动在需要的时候创建 Future 对象。

使用 await 表达式

await 表达式具有如下的形式:

await expression

在一个异步方法内可以使用多次 await 表达式。 例如,下面的示例使用了三次 await 表达式 来执行相关的功能:

var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);

在 await expression 中, expression 的返回值通常是一个 Future, 如果返回的值不是 Future,则 Dart 会自动把该值放到 Future 中返回。 Future 对象代表返回一个对象的承诺(promise)。 await expression执行的结果为这个返回的对象。 await expression 会阻塞,直到需要的对象返回为止。

如果 await 无法正常使用,请确保是在一个 async 方法中。 例如要在 main() 方法中使用 await, 则 main() 方法的方法体必须标记为 async:

main() async {
 checkVersion();
 print('In main: version is ${await lookUpVersion()}');
}

在循环中使用异步

异步 for 循环具有如下的形式:

await for (variable declaration in expression) {
 // Executes each time the stream emits a value.
}

上面 expression 返回的值必须是 Stream 类型的。 执行流程如下:

  1. 等待直到 stream 返回一个数据
  2. 使用 stream 返回的参数 执行 for 循环代码,
  3. 重复执行 1 和 2 直到 stream 数据返回完毕。

使用 break 或者 return 语句可以 停止接收 stream 的数据, 这样就跳出了 for 循环并且 从 stream 上取消注册了。

如果异步 for 循环不能正常工作, 确保是在一个 async 方法中使用。 例如,要想在 main() 方法中使用异步 for 循环,则需要把 main() 方法的方法体标记为 async:

main() async {
 ...
 await for (var request in requestServer) {
 handleRequest(request);
 }
 ...
}

可调用的类

如果 Dart 类实现了 call() 方法则 可以当做方法来调用。

在下面的示例中,WannabeFunction 类定义了一个 call() 方法,该方法有三个字符串参数,并且返回三个字符串串联起来的结果。:

class WannabeFunction {
 call(String a, String b, String c) => '$a $b $c!';
}
main() {
 var wf = new WannabeFunction();
 var out = wf("Hi","there,","gang");
 print('$out');
}

Isolates

现代的浏览器以及移动浏览器都运行在多核 CPU 系统上。 要充分利用这些 CPU,开发者一般使用共享内存数据来保证多线程的正确执行。然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。

所有的 Dart 代码在 isolates中运行而不是线程。 每个 isolate 都有自己的堆内存,并且确保每个 isolate 的状态都不能被其他 isolate 访问。

Typedefs

在 Dart 语言中,方法也是对象。 使用 typedef 或者 function-type alias来为方法类型命名, 然后可以使用命名的方法。 当把方法类型赋值给一个变量的时候,typedef 保留类型信息。

下面的代码没有使用 typedef:

class SortedCollection {
 Function compare;
 SortedCollection(int f(Object a, Object b)) {
 compare = f;
 }
}
 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;
main() {
 SortedCollection coll = new SortedCollection(sort);
 // 我们只知道 compare 是一个 Function 类型,
 // 但是不知道具体是何种 Function 类型?
 assert(coll.compare is Function);
}

当把 f 赋值给 compare 的时候, 类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 当然该类型是一个 Function。如果我们使用显式的名字并保留类型信息, 开发者和工具可以使用 这些信息:

typedef int Compare(Object a, Object b);
class SortedCollection {
 Compare compare;
 SortedCollection(this.compare);
}
 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;
main() {
 SortedCollection coll = new SortedCollection(sort);
 assert(coll.compare is Function);
 assert(coll.compare is Compare);
}

注意: 目前,typedefs 只能使用在 function 类型上,但是将来可能会有变化。

由于 typedefs 只是别名,他们还提供了一种判断任意 function 的类型的方法。例如:

typedef int Compare(int a, int b);
int sort(int a, int b) => a - b;
main() {
 assert(sort is Compare); // True!
}

元数据

使用元数据给你的代码添加其他额外信息。 元数据注解是以 @ 字符开头,后面是一个编译时常量(例如 deprecated)或者 调用一个常量构造方法。

所有的 Dart 代码都可以使用三个注解: @deprecated、 @override、 和 @proxy。关于 @override 和 @proxy 类的拓展部分的示例,下面是使用 @deprecated 的 示例:

class Television {
 /// _Deprecated: Use [turnOn] instead._
 @deprecated
 void activate() {
 turnOn();
 }
 /// Turns the TV's power on.
 void turnOn() {
 print('on!');
 }
}

你还可以定义自己的元数据注解。 下面的示例定义了一个带有两个参数的 @todo注解:

library todo;
class todo {
 final String who;
 final String what;
 const todo(this.who, this.what);
}

使用 @todo 注解的示例:

import 'todo.` Dart`';
@todo('seth', 'make this do something')
void doSomething() {
 print('do something');
}

元数据可以在 library、 class、 typedef、 type parameter、 constructor、 factory、 function、 field、 parameter、或者 variable 声明之前使用,也可以在 import 或者 export 指令之前使用。 使用反射可以在运行时获取元数据信息。

注释

Dart 支持单行注释、多行注释和 文档注释。

单行注释

单行注释以 // 开始。 // 后面的一行内容 为 Dart 代码注释。

main() {
 // TODO: refactor into an AbstractLlamaGreetingFactory?
 print('Welcome to my Llama farm!');
}

多行注释

多行注释以 /* 开始, */ 结尾。 多行注释 可以 嵌套。

main() {
 /*
 * This is a lot of work. Consider raising chickens.
 Llama larry = new Llama();
 larry.feed();
 larry.exercise();
 larry.clean();
 */
}

文档注释

文档注释可以使用 /// 开始, 也可以使用 /** 开始 并以 */ 结束。

在文档注释内, Dart 编译器忽略除了中括号以外的内容。 使用中括号可以引用 classes、 methods、 fields、 top-level variables、 functions、 parameters。中括号里面的名字使用当前注释出现地方的语法范围查找对应的成员。

下面是一个引用其他类和成员 的文档注释:

/// A domesticated South American camelid (Lama glama).
///
/// Andean cultures have used llamas as meat and pack
/// animals since pre-Hispanic times.
class Llama {
 String name;
 /// Feeds your llama [Food].
 ///
 /// The typical llama eats one bale of hay per week.
 void feed(Food food) {
 // ...
 }
 /// Exercises your llama with an [activity] for
 /// [timeLimit] minutes.
 void exercise(Activity activity, int timeLimit) {
 // ...
 }
}

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码