面向Android开发者的Dart学习教程

简介: 近日Google发布了Flutter2.0, 使用Flutter开发的App可以在不做修改的情况下发布到更多的主流平台;再加上早些时候Fuchsia也宣布将Dart作为主要的UI开发语言,如果未来你想

0x00 前言

Note: 本文内容基于Dart 2.10 语法

近日Google发布了Flutter2.0, 使用Flutter开发的App可以在不做修改的情况下发布到更多的主流平台;再加上早些时候Fuchsia也宣布将Dart作为主要的UI开发语言,如果未来你想构建跨平台应用,Dart可能会成为主要选项甚至必备技能。

Dart在语法上有很多Java以及Kotlin的影子,本教程通过与Java、Kotlin的对比学习Dart,有利于Android开发者更好地理解和掌握相关语法。

0x01 内置类型

数值

数值类型包括整形int, BigInt,以及浮点型doubleint是64bit的,超出这个范围使用Bigint
数值类型的字面值在编译期可以确定,所以使用const声明数值常量。

const A = 3;
const B = 9;
const C = A * B;

字符串

var str = "可以使用双引号字符串";
var str = '也可以向JS一样使用单引号字符串'
var str = "字符串可以" + "通过运算符链接";
var str = "像Kotlin一样 $exprName 使用字符串模板参数";
var str = """
    像kotlin一样,
    三个双引号或者单引号内的文字可以随意换行
    也无需转义
""";
var str = '相邻字符串 '
    '不使用加号'
    " 也会连接成一个字符串";

布尔型

bool表示布尔型,就像Java的Boolean,但是如前所述bool的默认值是null,需在初始化时显式的设置初值

List

Dart中没有Array只有List,Java中经常会纠结于Array与List的选择,不让用户做选择才是好产品。
List的API与Java很相近,但是Dart允许List像Array或者Kotlin的listOf一样使用字面值创建

// list的类型为List<Int>
final list = [1, 3, 5, 7, 9];

print(list[1]);    // 3

Dart2.3开始,加入了spread操作符进行list的展开,spread借鉴了ES6的三点语法

final listA = [1, 2, 3, 4];
final listB = [...listA, 5, 6, 7, 8, 9];

print(listB);    // [1, 2, 3, 4, 5, 6, 7, 8, 9]

Dart2.3还加入Collection-If功能,在list内可以写条件表达式;

final list = [
    if (true) 1,
    if (false) 2,
    3
];

print(list);    // [1, 3]

以及Collection-For功能,list内写循环表达式

final listA = [1, 2, 3, 4];
final listB = [
    for (var a in listA) a * 2,
];

print(listB);    // [2, 4, 6, 8]

Map

Dart的Map的API与Java的几乎一样,但是允许字面值创建Map

var map = {
    1:"壹",
    2:"贰",   // 允许尾逗号的存在,据说Kotlin 1.4 中也准备支持尾逗号
};

print(map[1]);   // "壹"
print(map[3]);   // null

Set

API几乎与Java的一样,同样可以字面值创建

final s = {1, 2, 1, 2, 1, 2};

print(s);    // {1, 2}

符号文字(Rune)

Rune是UTF-32编码的字符串。它可以通过文字转换成符号表情或者代表特定的文字。

var clapping = '\u{1f44f}';
print(clapping);   // 👏

Java中没有专用的Rune类型,通过\uXXXX的String表示Unicode字符,例如

String s = "\u00X1";

符号(Symbol)

Symbol可以看作C中的宏,表示编译时的一个常量,使用#后跟一个标识符表示。
在Java和Kotlin中没有对应的类型,在ruby中倒是有Symbol类型的存在。

main() {
  print(i);
  switch(i){
    case #A:  ///#A是一个Symbol
      print("A");
      break;
    case #B:
      print("B");
      break;
  }

  var b = new Symbol("b");
  print(#b == b); ///#b是一个Symbol

}

SymbolRune虽然在Java和Kotlin中不存在对应的类型,但在使用中也比较少,所以不会成为学习Dart的障碍。

0x02 函数

函数签名

Dart采用与Java一样的函数签名写法

String greet(String name) {
    return  "Hello, $name";
}

Dart可以通过类型推导确定返回值类型,随意允许在签名中省略返回值类型:

greet(String name) {
    return  "Hello, $name";
}

个人不推荐省略返回值类型的写法,函数定义和函数调用变得不容易区分影响可读性。

函数是一等公民

Dart中万物皆对象,函数本质上也是Function类型的实例,类似于Kotlin中也有对应的FunctionN类型,都继承自kotlin.jvm.functions.Function接口。 可以将函数像作为一个实例进行参数传递。

void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

// Pass printElement as a parameter.
list.forEach(printElement);

lambda传参

像Kotlin一样,可以将lambda赋值给函数类型参数

var l = [1,2,3];

l.forEach((i) { print(i); });

lambda内只有一行时,可以使用箭头函数:

l.forEach((i) => print(i));

() => exp 相当于 () { return exp; } 的语法糖,lambda表达式以外,在函数体只有一行的函数声明时也可以使用

 bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

可选参数(Optional parameter)

Dart的函数参数分为必选参数和可选参数,可选参数在函数调用时可省略传值,Java中没有相对应的概念,Kotlin的默认值参数可以实现同样功能。

  • 可选参数分为命名可选参数位置可选参数
  • 定义参数列表时,必选参数需要放到可选参数前面
  • 命名可选参数使用{}包裹;位置可选参数需使用[]包裹。

命名可选参数(Optional named parameter)

命名可选参数传值时需要携带参数名称,如果不需要传值则不需要携带参数名称。
不传值的参数会使用函数定义时的默认值,如果函数定义中没有默认参数值,则会传null

void enableFlags({bool bold, bool hidden}) {...}

// bold中传入了null、hidden中传入了true
enableFlags(hidden: true);

如果命名可选参数添加了@required注解,则此参数必须在在函数调用时指定,不可缺省。
@required的使用需要引入package:meta/meta.dart

// child中不传值调用会报错
const Scrollbar({Key key, @required Widget child})

位置可选参数(Optional positional parameter)

位置可选参数中可选取任意位置,省略其后的所有传值,
若函数定义没有默认参数值,则会被传入null

String say(String from, String msg,
    [String device, String mood]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device';
  }
  if (mood != null) {
    result = '$result (in a $mood mood)';
  }
  return result;
}

// 第三个参数起可省略
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

// 函数调用无需携带参数名
// 也可以之省略第四个参数
assert(say('Bob', 'Howdy', 'smoke signal') ==
    'Bob says Howdy with a smoke signal');

默认参数值

函数定义时,可以指定默认参数值。与Kotlin的默认值参数区别如下:

  • Kotlin 默认值参数:有默认值的参数在函数调用时自动变为可选参数,允许缺省传参。若位置参数的方式传参(省略指定参数名称),则指定默认值的参数起,后面的所有参数都需要指定默认值:
//kotlin

fun reformat(str: String,
            normalizeCase: Boolean = true,
            upperCaseFirstLetter: Boolean = true,
            divideByCamelHumps: Boolean = false,
            wordSeparator: String= "bbb") {
}

//位置参数:后面的参数可以都省略,但必须都有默认值
fun reformat("aaa")

//命名参数:通过参数名指定参数,不要求默认值参数后必须都指定默认值
fun reformat(str = "aaa",  upperCaseFirstLetter = false)
  • Dart 默认参数值:可选参数不一定有默认参数值,如果不指定默认参数值,则缺省传参时会被传入null

因为Kotlin的默认值参数自动变为可选参数,所以不用像Dart那样使用[]{}单独定义可选参数,个人认为Kotlin的做法更优雅简洁。

扩展函数

Dart 2.6起,开始支持像Kotlin那样的扩展函数。但是写法没有Kotlin简洁

extension NumExtention<T extends num> on T {
  T pow(int p) => math.pow(this, p) as T;
}

print(2.pow(5));  // 32
  • 如上,扩展函数也可以使用泛型
  • extension前添加下划线可以private
  • 扩展函数只能在top-level声明,Kotlin可以在Class内部声明(但用处不大)。

扩展函数是Kotlin能提供强大DSL的基础,DSL非常适合声明式UI的书写,例如Kotlin的Anko比起Flutter写法上要优雅的多。个人认为扩展函数未来将会为Dart的生态带来很大变化。

0x03 运算符、操作符

?操作符

与Kotlin一样,使用?.在方法调用的同时进行判空

List<dynamic> list = null;
list?.add(3);    // list为null则add不会调用

算数操作符

与Java大体相同,区别仅在于除法运算:

  • Java没有专门的除法运算符, /表示取模运算
  • Dart中使用 ~/ 表示取模,/表示除法 ,返回double
int div = 4 ~/ 2;      // 2
div = 4 / 2;           // 返回double类型,编译异常

类型判断

与Kotlin相同,相当于Java的instanceof

bool isHoge = h is Hoge;
bool isNotHoge = h is! Hoge;   // == !(h is Hoge)

类型转换

类似Kotlin的Smart Cast

if (p is Person) {
    print(p.firstName);
}

Elvis操作符

类似Kotlin的 ?: ,Dart中是??

var b = a ?? "hoge";

若a为null则赋值”hoge“,Kotlin好像没有对应的功能。。。

a ??= "hoge";

级联操作符(..)

Strictly speaking, the “double dot” notation for cascades is not an
operator. It’s just part of the Dart syntax.

严格意义上 级联(..)不能算操作符,是Dart的语法。
级联允许对同一个对象进行连续操作,相当于Kotlin作用域函数的apply

querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));
/*
上面代码相当于:
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
*/

借助()可以嵌套加里奥

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

0x04 控制流程、异常处理

流程控制

Control Flow与Java基本一样,甚至for-in的使用也无差。

Iterable实例可以像Kotlin一样使用foreach,不需要获取当前i时,可以使用

candidates.forEach((candidate) => candidate.interview());

Dart中switch-case的用法上与Java有少许区别,需注意:

  • switch后必须跟编译时常量,类型上无限制

intstringenum或其他可以用常量构造函数创建的实例,但要求此类型不能重写==,因为switch基于==进行比较,重写==有可能会违反互换性原则,即 父类 == 子类子类型 !== 父类型

  • case之后必须要跟break或者continue
  • default可省略(与Java相同)
  • contine label; 语句可以跳转到label: 的位置

(避免Java中漏写break进入下一个case的问题)

var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

异常

与Kotlin相似,Dart中所有的异常都是未检查的异常,方法不声明它们可能抛出哪些异常,也不要强制捕获任何异常。

Dart可以抛出任何非空对象,不仅仅是Exception和Error

//抛出异常的例子:
throw FormatException('Expected at least 1 section');

//也可以抛出任意对象:
throw 'Out of llamas!';

throw语句是一个表达式,所以可以出现在=>中

void distanceTo(Point other) => throw UnimplementedError();

可以使用oncatch或者on...catch捕捉异常:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

要在捕获中处理异常,同时允许其继续传播,使用rethrow关键字。

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

finally的使用与Java无差别

0x05 类、构造函数

类(Class)

与Java相同,Dart中所有的类都是Object的子类。

与Java和Kotlin一样,this关键字指向当前实例,Dart的代码风格建议省略this,仅在名称冲突等必要的时候使用。

Dart1的对象实例化与Java一样使用new关键字,Dart2开始可以向Kotlin一样不使用new创建实例

实例通过instance.runtimeType可以获取一个类型对象,相当于Java的instance.class

构造函数

构造函数可以有两种形式

  • ClassName:类名作为构造函数,像Java、Kotlin一样
  • ClassName.identifier : 命名的构造函数,类似工厂方法,可以让构造函数的作用更有针对性
var p1 = Point(2, 2);

//Named constructor
var p2 = Point.fromJson({'x': 1, 'y': 2});

当然,这里我们省略了new关键字,Dart1必须像下面这样:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

Dart的函数不支持重载,所以当需要定义多个构造函数时,有如下两个选择:

  • 使用可选参数
  • 增加多个命名构造函数

构造参数为成员赋值

Kotlin的构造参数添加var/val,可以声明为类的成员变量,并在调用构造函数时直接为其赋值。
Dart与Kotlin类似,通过添加this.也可以直接为成员赋值,但是成员依然需要单独声明:

class Person {
  // 成员变量声明
  final String firstName;
  final String lastName;

  // 调用时直接为成员变量赋值
  Person(this.firstName, this.lastName) {
    ...
  }
}

构造函数的继承

像Java一样,没有声明构造函数的子类只有默认的构造函数(无参的类名构造函数),而不会自动继承父类的任何有参构造函数。所以 想调用父类特定构造函数时,需在子类中显示声明一个构造函数并通过super关键字调用

class Mutant extends Person {
  Mutant(String firstName, String lastName) : super(firstName, lastName);

  Mutant.anonymous() : super.anonymous() {
    print("anonymous mutant");
  }
}

重定向构造函数

子类构造函数要么通过super继承自父类的构造函数,要么通过this重定向到另一个自己的构造函数,有点类似于类似与Kotlin的次级构造函数指向主构造函数。

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}

初始化列表

构造函数体执行之前,通过初始化列表可以进行成员初始化。 各参数的初始化用逗号分隔。

// 在构造函数体执行之前,
// 通过初始列表设置实例变量。
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

final成员的初始化可以放到初始化列表进行,但是不能放到构造函数体内

class Hoge {
  final String fuga;
  Hoge(String moge)
          : assert(moge != null),
          fuga = moge.toUpperCase() {
    print(fuga);
  }
}

/*
👇放到函数体内的初始化,编译器会报错
class Dame {
  final String fuga;
  Hoge(String moge) {
    fuga = moge.toUpperCase();
  }
}
*/

常量构造函数

类成员变量全为final、且前置了const关键字的构造函数,我们称为常量构造函数
常量构造函数实例化的对象成为编译时常量。

class Point {
    final int x;
    final int y;

    const Point(this.x, this.y);
}

const p = Point(1, 2);

工厂构造函数

前置factory关键字后称为工厂构造函数
工厂构造函数与Java的工厂方法类似,需要返回一个类型实例,可以配合缓存实现单例模式。

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 = Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

0x06 类的成员

成员方法

与Java的成员方法无论在定义或是调用上没有区别。
惟一的区别是Dart的成员方法无法重载,但是有可选参数的加持,也没有大问题
<br/>

getter/setter

Dart的getter/setter与Kotlin的类似,但是没有backing filed的概念,所有没有filed变量用来存储数据,需要自己声明一个私有变量来充当所谓的backing field

class Rect {
  final Point point;
  final Size size;

  num get area => size.width * size.height;


  // 定义私有变量用来充当backing filed
  Color _color;

  Color get color => _color;
  set color(Color newColor) {
    _color = newColor;
    notifyColorChanged();
  }
}

抽象方法

与Java的抽象方法相似:

  • 抽象方法没有方法体
  • 抽象方法只能存在于抽象类中,但抽象类中可以非抽象方法(有方法体的方法)
  • 抽象方法前不需加abstract关键字,这点与Java和Kotlin不同
abstract class Animal {
    void hello(); //抽象方法
}

class Cat extends Animal {
  void hello() {
    print("meow");
  }
}

可调用类(Callable classes)

类中只有一个call()方法,所以可以通过类直接调用,有点像Java8的SAM(Single Abstract Method)接口。

class WannabeFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = new WannabeFunction();
  var out = wf("Hi","there,","gang");
}

0x07 继承与Mixins

隐式接口

Java中我们总是把接口和抽象类放到一起理解:接口是极致抽象的抽象类。
Dart中接口和类是统一的,每个类同时也是一个接口:

  • 没有interface关键字,声明class的同时隐式地创建同名接口
  • 类中的非私有的成员可以通过接口对外暴露,但需要被实现
  • 与Java一样,使用implements 关键字实现接口
  • 抽象类同样具有隐式接口
// Person声明的同时,创建同名接口,内有方法greet()
class Person {
  // 接口不含有private变量
  final _name;

  // 接口不包含构造函数
  Person(this._name);

  // 接口中没有方法实现
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// 实现Person接口
class Impostor implements Person {
  get _name => '';

  // 必须要实现greet方法
  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

因为类同时也是接口,extendsimplements关键字都是合法的,但意义完全不同,需要注意

继承

与Java的继承没有区别:

  • 使用extends关键字
  • 只允许单继承

方法重写

注意是重写不是重载,Dart没有方法重载

  • 与Java一样,可通过@Override注解表示一个重写方法,当然可以省略
  • 方法重写时可以通过covariant关键字缩小参数类型,这是Java或Kotlin中没有的
class Animal {
  void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
  void chase(covariant Mouse x) { ... }
}

运算符重载

虽然正确的英文是Overridable operators,但是类似功能总是被译成运算符重载,whatever。
虽然Java中没有,Kotlin、C++等都有此功能。

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
}

Java中我们要求重写equals的同时必须重写hashcode,Dart中也是一样的, 所以我们对==进行运算符重载时,不要忘了hashcode

class Person {
  final String firstName, lastName;

  Person(this.firstName, this.lastName);

  // Override hashCode using strategy from Effective Java,
  // Chapter 11.
  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + firstName.hashCode;
    result = 37 * result + lastName.hashCode;
    return result;
  }

  // You should generally implement operator == if you
  // override hashCode.
  @override
  bool operator ==(dynamic other) {
    if (other is! Person) return false;
    Person person = other;
    return (person.firstName == firstName &&
        person.lastName == lastName);
  }
}

void main() {
  var p1 = Person('Bob', 'Smith');
  var p2 = Person('Bob', 'Smith');
  var p3 = 'not a person';
  assert(p1.hashCode == p2.hashCode);
  assert(p1 == p2);
  assert(p1 != p3);
}

noSuchMethod()

Java或Kotlin中没有类似功能,类似于Ruby的method_missing
因为Dart中存在dynamic类型,无法保证类型安全的情况下有可能被调用一个不存在的方法。通过重写noSuchMethod()方法,可以拦截这种非法调用。

只有当被调用对象满足以下三个条件其中之一时,noSuchMethod()才有可能被调用:

  • 被调用者是一个dynamic类型(此时若不重写noSuchMethod,会调用Object#noSuchMethod
  • 被调用者是一个抽象类类型(此时若不重写noSuchMethod,会调用Object#noSuchMethod
  • 被调用者是一个普通类类型,此时如果重写了noSuchMethod,则允许存在不被实现的方法(有可能被错误调用)
class A {
  @override
  void noSuchMethod(Invocation invocation) => print("method ${invocation.memberName} is missing");
}

class B {
  // 因为重写了noSuchMethod,所以hoge()可以没有实现
  void hoge();

  @override
  void noSuchMethod(Invocation invocation) => print("method ${invocation.memberName} is missing");
}


void main() {
  dynamic a = A();
  a.hoge();

  // hoge()没有实现也照样能实例化
  B b = B();
  b.hoge();
}

Mixins

mixin是实现多继承的重要方式,Java和Kotlin中没有类似功能,Javascript和Python中有相似概念。

  • 使用with关键字,可以同时“继承”多个mixin类
  • mixin类mixin关键字替代class关键字,无构造方法,也不需要调用supermixin关键字是2.1之后引入的,2.1之前使用 abstract class,再阅读一些老项目代码时需要注意
mixin AndroidEngineer {
  void implementAndroidApp() {
    print("got it!");
  }
}

mixin IOSEngineer {
  void implementIOSApp() {
    print("Jobs!!");
  }
}

class Person {
}

class FlutterEngineer extends Person with AndroidEngineer, IOSEngineer {
}

void main() {
  final e = FlutterEngineer();
  e.implementAndroidApp();
  e.implementIOSApp();
}

extends不可缺

with的使用至少要伴随一个extends,干爹可以有多个,但必须有且仅有一个亲爹。如果只想继承mixin类时,最不济可以这样写 :extends Object with 〜〜

菱形继承问题?

OOP之所以不允许多继承,主要是为了避免菱形继承问题
在这里插入图片描述

Derivefun1究竟来自Base1还是Base2

  • 继承关系会按照如下优先级选取fun1with > extend > implements
  • 当with后有多个mixin类时,会按照从右往左的顺序选取fun1

mixin类的继承

mixin类要么直接继承Object,要么通过on关键字继承其子类extends的目标类。

因为mixin类需要使用on目标类的方法,所以要使用mixim类就必须继承on目标类,以提供mixin所需的方法
abstract class Super {
  void method() {
    print("Super"); 
  }
}

class MySuper implements Super {
  void method() {
    print("MySuper"); 
  }
}

mixin MyMixin on Super {
  void method() {
    super.method();//15行
    print("Sub");
  }
}

class Client extends MySuper with MyMixin {}

void main() {
  Client().method();//23行
}

上面代码输出结果:

MySuper
Sub
  • 23行会先查找MyMixin中有没有对应的方法
  • 15行因为MyMixin限定子类必须继承或实现Super,所以会调用MyMixin子类所extend的父类的method
  • 虽然MyMixin继承的是Supert,但是子类的直接父类是MySuper,所以输出是MySuper后跟Sub

0x08 静态变量、枚举

静态变量、方法

静态变量、方法的定义与使用与Java中完全相同,同样使用static关键字,同样不用通过实例调用。

如果是一个广泛使用的静态方法,建议使用top-level方法替代静态方法,我们在Kotlin的编码规范中往往也是这样要求的。

一个疑问

Dart2官方有以下一段文字

You can use static methods as compile-time constants. For example, you
can pass a static method as a parameter to a constant constructor.

可以将静态方法当做编译期常量对待,甚至可以作为参数传给常量构造函数

其实我不太理解这段文字,如果可以当做编译期常量的话,下面代码应该work,但实际上会error

class Math {
    static int plus(x, y) => x + y;
}

const sum = Math.plus(1,2); //Error: Method invocation is not a constant expression.

哪位大佬能答疑解惑的欢迎留言~

枚举类

与Java的enum基本一样。

  • enum中的item会被分配index,index从0开始按照被声明的顺序递增
enum Color { red, green, blue }

assert(Color.green.index == 1);
  • enum可以用在switch-case中
  • enum不能被extends或者mixin、枚举不能创建实例,这与Java类似
  • enum不允许被是实现,这与Java不同

0x09 泛型

整体上与Java的泛型相似,有少许区别

运行时持有类型信息

Java的泛型的类型信息在运行时会丢弃,Dart则一直保持到运行时,可以动态check泛型信息

final strList = <String>[
  "hoge", "fuga", "moge"
];
assert(strList is List<String>);

泛型上界

  • 跟Java一样, <T extends S> 约定泛型T的上界S,但是不能通过super关键字约束下届。
  • Java可以使用 <T extends A & B & C>约定多个上界,但在Dart中不允许。
  • 定义上界时,调用时可以省略泛型书写,如下
class Foo<T extends SomeBaseClass> { }

var foo = Foo(); 
print(foo); // Instance of 'Foo<SomeBaseClass>'

方法泛型

与Java的写法不同

// 泛型的定义在方法名和参数之间
// Java是写在返回值前面
T doHoge<T>(T value){ ... }

Dart2之前只有成员方法才能使用泛型,Dart2中普通的顶级函数也可以使用泛型

0x0A 库与导入(import)

Library的导入

import关键字导入Library

import 'dart:html';

Library的URI

通过URI导入一个Library

根据library的种类不同,URI前缀scheme不同:

  • Dart标准库… dart:xxx
  • 三方库(通过包管理器提供的库,如pub工具)… package:xxx
  • Flutter的Library中也有三方库
import 'package:test'
  • Native实现库… dart-ext: xxx

本地工程的源码还没有package化的,可添加相对路径源码依赖

import 'package:test/text.dart'

别名导入

与Kotlin的import相同

import "package:mylib/awesome_functions.dart" as awesome;

部分导入

当只想导入部分功能时

// Import only foo.
import 'package:lib1/lib1.dart' show foo;

// Import all names EXCEPT foo.
import 'package:lib2/lib2.dart' hide foo;

懒加载库

库的懒加载(或者延迟加载),Java没有类似功能,可以类比Andoird中so的load。

懒加载适用于以下场景。

  • 减少应用程序的初始启动时间
  • 进行A/B测试时,可以根据需求加载不同的库
  • 加载某些使用频次低的功能,懒加载可以提高性能,例如对话框等

延迟加载需要使用deferred as关键字导入

import "package:rarely_used/lib.dart" deferred as rare;

当真正需要加在时,通过loadLibrary()加载,返回一个Future,所以必须使用await同步。

Future doSomething() async {
  await rare.loadLibrary();
  rare.awesomeFunction();
}

使用懒加载有几个注意事项:

  • 懒加载库中的const定义的常量,不能当常量使用,因为有可能还没有加载
  • 相同的理由,不要使用懒加载库中的Class进行任何操作。可以让懒加载库作为某些公共接口的实现,通过接口的依赖倒置避免对懒加载库中Class的直接依赖
  • deferred as (namespace) 中的namespace会被自动插入 Future loadLibrary()以作为执行加载的载体,所以注意避免在Library中出现同样签名的LoadLibrary()方法

Library的创建

创建一个Library供外部使用。

需要创建pubspec.yaml并进行若干配置。具体可以参考Create Library Packages,包括:

  • 库的代码组织
  • export指令的使用
  • part指令的使用
  • library指令的使用

Flutter工程中代码也是放在lib/文件夹中,所以也可以看作是是一个Dart的Library。

0x0B 异步与并发

异步编程

Dart可以像C#或Javascript那样可以通过 async/await 实现异步逻辑

Future与async/await

与Js的async/awiatPromise的语法糖一样,Dart的async/awiat只不过是Future的语法糖,可以帮助我们的用同步的代码处理Future提高可读性。脱离async/await,Future本身的API仍然可以完成异步逻辑。

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

await必须在async{...} 中使用。
async{...}中使用await可以等待异步完成,但是不会阻塞线程。

await expression的expression通常是一个Future对象,如果不是则被自动包装成Future,这相对于Javascript更智能。

async的返回值会被自动包装成Future,例如在main函数使用async,会返回Future<Void>

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

lambda表达式中也可以出现async

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

使用try...catch处理Future中的异常

try {
    version = await lookUpVersion();
} catch (e) {
    // React to inability to look up the version
}

Stream与async/await

熟悉RxJava的人都知道Stream的概念(流式数据),Dart中的Stream类型也有同样功能:连续多个数据的异步处理。

与Future一样,我们可以通过Stream的API或者通过async/await进行异步处理。
Stream使用类似循环语句的写法await for

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

await for 同样必须出现在 async{...}
像循环语句一样,通过return或者break可以结束stream处理

await for只能处理Cold Stream,即Stream中的数据是有始有终的,对于像Event监听这种没有固定数量的数据(Hot Stream)处理,无法使用,因为没有从循环外部取消订阅的API。
使用StreamAPI中的listene()可以得到StreamSubscription对象用来处理Hot Stream,它具有cancel方法可以取消订阅。

Generators(生成器)

JavaScript中有类似概念:用来生成Iterable 或者 Stream 的函数。

Stream相当于异步版本的Iterable

  • 同步生成器:返回Iterable对象
// body前需要加 sync* 
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  // yield的地方,暂停执行并通过Itertable返回当前值k
  while (k < n) yield k++;
}
  • 异步生成器:返回Stream对象

用法没有区别,只是sync* 换成 async*,比Javascrip异步生成器方便得多

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

可以通过yield*提高尾递归的性能:

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

并发(Isolate)

Dart没有Thread,所有代码运行在Isolate上。
Dart通过启动多Isolate实现并发编程。

Isolate相对于Thread的主要不同在于,Isolate拥有独立的heap,互相之间不共享资源,隔离程度更接近进程。Isolate间通信需要通过开Port,类似socket方式的IPC。

import "dart:isolate";
import "dart:async";

void w(sendPort) {
    for(int i = 0; i< 100000; i ++) {
      print("waiting: $i");
    }
    sendPort.send("finished!");
}

main() async {
  final receivePort = ReceivePort();
  final sendPort = receivePort.sendPort;
  await Isolate.spawn(w, sendPort);
  receivePort.listen((msg){
    print("message from another Isolate: $msg");
  });
}

这种IPC式的通信不像线程间通信那样方便,但是安全性更高。

通过Isolate.spawnUri() 可以将main()中的其他文件在新的Isolate中执行,Flutter的主进程也是以这种方式启动的。

0x0C 注释

Dart的注释用法与Java大体相同。

单行注释

final hoge = 0;    // 到行末都是注释内容

多行注释

与Java一样/**/

void main() {
  /*
   * This is a lot of work. Consider raising chickens.

  Llama larry = Llama();
  larry.feed();
  larry.exercise();
  larry.clean();
   */
}

文档注释

使用dartdoc可以生成HTML的API文档。
dartdoc会在IDE智能提示时发挥作用,所以对外暴露的API建议添加文档注释。

Java的文档注释使用/**开头,Dart中同样支持。另外,///的连续多行注释也可以作为文档注释。

/// 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) {
    // ...
  }
}

像Java一样,文档注释中[]会成为一个链接,例如[Food]可以链接到Food类的定义位置。

0x0D Typedef、Metadata

Typedef

可以理解成Kotlin中typealias的函数限定版,只能用来对函数定义别名。

typedef GestureTapDownCallback = void Function(TapDownDetails details);

定义还可以携带泛型

typedef Compare<T> = int Function(T a, T b);

int sort(int a, int b) => a - b;

void main() {
  assert(sort is Compare<int>); // True!
}

注解 Metadata

注解在Dart中称作元数据(Metadata)。

Dart的注解是小写字母开头,Java中是大写。

Dart内置的注解参考annoations.dart , 其中最常用有@deprecated@override等,例如:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {...}
}

可以通过常量构造函数类自定义注解

import 'dart:mirrors';

class Todo {
  final String comment;
  const Todo(this.comment);
}

@Todo("构造函数的参数")
class Hoge {}

main() {
  final h = Hoge();
  final mirror = reflectClass(Hoge);
  final todoRef = mirror.metadata.first;
  print(todoRef.reflectee.comment);
}

在反射中可以获取注解的变量。

0x0E 空安全NullSafty

从2.7之后,到目前最新的2.10,Dart在语法层面没有什么更新,最重要的变化就是空安全机制了(目前仍处于Beta测试阶段)。未来Dart可以像Kotlin一样实现编译期检查,并提供了?!late等各种关键字,处理空安全的相关case,基本是致敬Kotlin,熟悉Kotlin的同学应该不会陌生。 https://dart.cn/null-safety

目录
相关文章
|
2月前
|
Android开发 Swift iOS开发
iOS和安卓作为主流操作系统,开发者需了解两者差异以提高效率并确保优质用户体验。
【10月更文挑战第1天】随着移动互联网的发展,智能手机成为生活必需品,iOS和安卓作为主流操作系统,各有庞大的用户群。开发者需了解两者差异以提高效率并确保优质用户体验。iOS使用Swift或Objective-C开发,强调简洁直观的设计;安卓则采用Java或Kotlin,注重层次与动画。Swift和Kotlin均有现代编程特性。此外,iOS设备更易优化,而安卓需考虑更多兼容性问题。iOS应用仅能通过App Store发布,审核严格;安卓除Google Play外还可通过第三方市场发布,审核较宽松。开发者应根据需求选择合适平台,提供最佳应用体验。
72 3
|
19天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统架构差异及其对开发者的影响
本文旨在通过对比分析iOS和Android两大移动操作系统的系统架构,探讨它们在设计理念、技术实现及开发者生态方面的差异。不同于常规摘要仅概述内容要点,本摘要将简要触及核心议题,为读者提供对两大平台架构特点的宏观理解,铺垫
|
29天前
|
Android开发 数据安全/隐私保护 虚拟化
安卓手机远程连接登录Windows服务器教程
安卓手机远程连接登录Windows服务器教程
56 4
|
1月前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
27天前
|
安全 Java Linux
深入解析Android系统架构及其对开发者的意义####
【10月更文挑战第21天】 本文旨在为读者揭开Android操作系统架构的神秘面纱,探讨其如何塑造现代移动应用开发格局。通过剖析Linux内核、硬件抽象层、运行时环境及应用程序框架等关键组件,揭示Android平台的强大功能与灵活性。文章强调了理解Android架构对于开发者优化应用性能、提升用户体验的重要性,并展望了未来技术趋势下Android的发展方向。 ####
43 0
|
3月前
|
Java Maven 开发工具
第一个安卓项目 | 中国象棋demo学习
本文是作者关于其第一个安卓项目——中国象棋demo的学习记录,展示了demo的运行结果、爬坑记录以及参考资料,包括解决Android Studio和maven相关问题的方法。
第一个安卓项目 | 中国象棋demo学习
|
2月前
|
IDE Android开发 iOS开发
探索安卓与iOS系统的技术差异:开发者的视角
本文深入分析了安卓(Android)与苹果iOS两大移动操作系统在技术架构、开发环境、用户体验和市场策略方面的主要差异。通过对比这两种系统的不同特点,旨在为移动应用开发者提供有价值的见解,帮助他们在不同平台上做出更明智的开发决策。
|
2月前
|
Web App开发 编解码 视频直播
视频直播技术干货(十二):从入门到放弃,快速学习Android端直播技术
本文详细介绍了Android端直播技术的全貌,涵盖了从实时音视频采集、编码、传输到解码与播放的各个环节。文章还探讨了直播中音视频同步、编解码器选择、传输协议以及直播延迟优化等关键问题。希望本文能为你提供有关Andriod端直播技术的深入理解和实践指导。
56 0
|
3月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
414 3
|
3月前
|
IDE Java Android开发
安卓与iOS开发环境的差异及其对开发者的影响
在数字时代的浪潮中,移动应用成为人们生活的延伸。两大操作系统——安卓与iOS,如同两座技术高峰,各自占据着半壁江山。本文将探索这两个平台的开发环境差异,并讨论这些差异如何塑造开发者的编程习惯与职业选择。我们将从工具和语言、用户界面设计、系统架构、市场定位以及开发社区和资源五个方面进行比较,旨在为开发者提供一份实用的指南,帮助他们在不断变化的技术世界中,找到适合自己的发展路径。
58 3