Dart 和 Kotlin 之间的一些差异

Dart

基础数据类型

Dart 主要类型有以下几种:

数值有两个子类型:

  • int : 任意长度的整数
  • double : 双精度浮点数
  • String

    Dart 中的 String 赋值可以使用单双引号,区别在于单引号不可使用 $ 进行值插入。
    除此之外,还可以使用三引号,进行多行字符串赋值。(与 Kotlin 类似,唯一不同即 Kotlin 没有单引号)。
    String 字符串中,不同于 Java 的是 \ 默认具有转义效果,若要避免 \ 的转义作用,需要在字符串声明之前加上 r ,即 String s = r"换行\n"

    bool 值为其对应的值,非 bool 值都为 false

    编译时常量

    使用 [] ,默认值采用 : 赋值,即

    // 声明
    String sayHello(String name, [String greetings : 'Hello']) => println("$greetings, $name")
    // 调用
    sayHello('name', 'hello')
    

    但这种情况下,一旦位置可选参数多了之后很难辨别哪个对象对应哪个可选参数;

    命名可选参数

    使用 {},默认值使用 = 赋值,即

    // 声明
    String sayHello(String name, {String greetings = 'Hello'}) => print("$greetings, $name")
    // 调用
    sayHello('name', greetings = 'hello')
    

    这两种可选参数方式不可同时使用
    PS : 作为一个后来者的语言, Kotlin 可选参数两种方式都支持但不需要 {}[]

    操作符和流程控制语句

    Dart 大多数内容和 C++、 Java 没什么区别,下面是几个有别于前两者的操作符

    取整操作符 ~/

    C++、 Java 之所以没有取整操作符,是因为在整数相除时默认结果为整数,而 Dart 中返回的是真实的结果:

    int a = 3;
    int b = 2;
    print(a / b); // 1.5
    print(a ~/ b); // 1
    

    当需要对单一对象进行一系列操作时,可以使用级联操作符 .. (对应 Kotlin 中的 apply 扩展函数):

    class Person {
        String firstName;
        String lastName;
    Person p = Person(); // new 关键字可以省略
    p .. firstName = 'firstName'
      .. lastName = 'lastName';
    

    if 通常用法与常规一致,在判断非 bool 值时, check 模式下会抛出异常, production 模式下会被当成 false.

    通常 for 循环与常规一致,若迭代对象为容器,则可以使用 forEach((T) -> function) 方法进行迭代。

    Swicth Case

    switch 的参数可以是 String 或者 num;如果分句的内容为空,则可以省略 break 达到落空(执行下一分句内容)效果;如果不为空仍想实现落空效果,需要手动指定标签和使用 continue 返回标签:

    switch(0){
        case 0:
        case 1:
            print('落空');
            break;
        case 2:
            print('非空落空');
            continue closed;
        case 3:
            print('x');
            break;
            closed:
        case 4:
            print('非空落空执行');
            break;
    

    Dart 中,非空对象都可以作为异常抛出,如果 catch 没有指定类型,那么可以处理任何类型的异常(类似于 var)。其余与 Java 类似。

    Dart 与 Java 一致,所有对象都是类的实例,且所有类都是 Object 的子类(Kotlin 为 Any)。

    在 Dart2 中,创建对象可以省略 newconst 关键字。

    构造函数中的值传递及构建常量

    class Person {
        String firstName;
        String lastName;
        // 值传递
        Person(this.firstName, String lastName){
            this.lastName = lastName;
        // final 属性除了默认赋值之外,只能通过下两种方法进行初始化
        // 初始化列表
        Person(String a, String b) : firstName = a, lastName = b;
        // 常量对象
        const Person(this.firstName, this.lastName);
        // 重定向构造函数
        Person.polar(this.lastName, this.firstName)
        Person(String firstName, String lastName) : this.polar(lastName, firstName)
    

    构造函数执行流程:

    class Base {
        int a;
        int b;
        Base(this.a, int c) {
            b = c;
    class Ex : Base {
        int d;
        Ex(a, b , this.d) : super(a, b){
    Ex 对象被创建 -> 调用 Ex 初始化列表 -> 调用 Base 初始化列表 -> 调用 Base 构造方法内容 -> 调用 Ex 构造方法

    工厂构造函数:
    工厂构造函数类似于 static 静态变量,无法访问 this 指针。
    工厂构造函数由 factory 前缀开头,它可能没有初始化列表或者初始化形式参数,相反,他们必须有一个返回一个对象的函数体。

    class A {
        String name;
        static A cache;
        factory A(String name){
            if(cache == null){
                A.cache = new A.newObject(name);
            return A.cache;
    

    Get Set 方法

    类的每个字段都对应一个隐式的 Getter 和 Setter,调用与 Kotlin 一致。
    可以使用 getset 关键字扩展默认调用;如果字段为 finalconst 时只有 Getter 方法。

    class Person {
        String firstName;
        String lastName;
        // get 扩展
        String get firstName => firstName + lastName;
        // set 扩展
        set lastName => lastName = value
    

    Dart 中接口和类时统一的,类就是接口,同 Java 使用 abstract 方法来定义抽象类并且抽象类不能被实例化。

    虽然都是接口但继承和实现仍与 Java 中类似。继承关键字为 extends,实现关键字为 implements

  • 当使用实现时,子类无法访问父类的参数,有点类似于 C++ 中的纯虚函数,但可以实现多个类的多个函数;
  • Dart 与 Java 一致,采用单继承方式,子类可以继承父类非私有变量。
  • Mixins 是用来在不同类中进行代码重用的一种方式。
    声明一个 Mixins 类,需要创建一个继承自 Object 的、没有构造方法(即抽象类)的类:

    abstract class MixinsExample {
        bool isSelected = false;
        void printCurrentState(){
            if(isSelected){
                print('Current state is selected');
            }else{
                print('Current state is unselected');
    

    通过 with 关键字使用:

    class Content with MixinsExample{
    

    StringBuffer

    字符串拼接类:

    StringBuffer sb = new StringBuffer();
    sb.write("first");
    sb.writeAll(['array1', 'array2']);
    print(sb.toString());
    sb.clear();
    

    Dart 中的容器主要包括 List、Set、Map,与 Java 中的用法基本一致。

    // 新建
    List<int> count = new List<int>();
    // 通过数组赋值
    List<int> from = [3, 2, 1];
    // 通过泛型指定
    varfrom = <int>[3, 2, 1];
    count.add(0);
    count.addAll(from);
    count.length; // 4
    count[0]; // 0
    // 排序,方法返回值 < 0 表示小于, = 0 表示等于, > 0 表示大于
    count.sort((a, b) => a - b); // [0, 1, 2, 3]
    count.remove(3); // 3
    count.indexOf(2); // 2
    count.removeAt(1); // 1
    count.removeWhere((i) => i == 0); // 0 
    count.clear(); // count.length 为 0
    
    Set<String> set = new Set<String>();
    set.addAll(['first', 'second', "third"]);
    set.length; // 3
    set.remove('second'); // true
    set.contains('third'); // true
    Set<String> newSet = new Set.from(['third', 'forth']);
    // 交集
    Set<String> intersection = set.inersection(newSet);
    // 子集
    intersection.isSubsetOf(set); // true
    
    Map<String, List<String>> map = {
        'key1': ['array01', 'array02'],
        'key2': ['array11', 'array12']
    // 通过泛型指定
    var map = <String, List<String>>{
        'key1': ['array01', 'array02'],
        'key2': ['array11', 'array12']
    Map<String, String> sMap = new Map<String, String>();
    sMap['key'] = 'value';
    sMap.containsKey('key'); // true
    sMap.keys; // ['key']
    sMap.values; // ['value']
    sMap.remove('key'); // 'value'
    

    容器的迭代

    List、Map 都继承自 EfficientLengthIterable,是可以进行迭代的,而 Set 自身实现了 forEach 方法,所以以上三种容器都可以通过 forEach((T) => function)for(t in collection) 进行迭代。

    导入的唯一必须参数是指定库的 URI。
    对于内置库,导入需要使用特殊的 URI: dart:scheme;
    对于其他库,可以使用文件的系统路径或 package:scheme 这样的格式。

    当导入两个库中有冲突的命名时,可以通过添加前缀的方式来区分:

    // 假设这两个库中都有一个 Element 类
    import 'package:lib1/lib1.dart';
    import 'package:lib2/lib2.dart' as lib2;
    Element element1 = Element();
    Element element2 = lib2.Element();
    
    import 'package:lib1/lib1.dart' show func;
    import 'package:lib2/lib2.dart' hide func;
    

    在以下情况时可能需要延迟导入:

  • 减少启动时间
  • A/B 测试
  • 加载较少使用的功能
  • import 'package:greetings/hello.dart' deferred as greeting;
    Future greet() async{
        // 可多次调用但只会导入一次
        await gretting.loadLibrary();
        gretting.greet();
    

    Dart 库有很多返回值为 Future 和 Stream 对象的方法,这些方法都是异步的,他们通常在设置完耗时操作(例如 I/O,网络请求)后直接返回,而不等待该方法执行完成。

    Future

    当需要使用 Future 执行完成之后的值,有两个选择:

    使用 asyncawait

    使用 asyncawait 是异步的,但从代码上看仿佛是一个同步操作,更便于理解:

    // await 必须在 async 方法中
    Future checkVersion() async{
        // 异步操作只在遇到第一个 await 表达式才会执行,然后返回一个 Future 对象
        // 余下部分只在 await 表达式执行完成后才恢复执行
        var version = await lookUpVersion();
    

    使用 Future 的API
    使用 Future 的 then(function) 来设置在 Future 完成之后需要运行的代码:

    // 返回一个 Future 对象
    HttpRequest.getString(url)
        .then((String result) {
            print(result);
        }).catchError(e){
    

    then(function) 方法也提供了一个有效的按顺序执行异步方法的方式:

    // 以下方法全都为返回 Future 对象的异步方法
    constlyQuery(url)
        .then((value) => expensiveWork(value))
        .then((_) => lengthyComputation())
        .then((_) => print('Done'))
        .catchError(e){
    // 同等的使用 async 和 await 的方法
    request() async {
            final value = await constlyQuery(url);
            await expensiveWork(value);
            await lengthyComputation();
            print('Done');
        }catch(e){
    // 有时不需要关注异步任务的执行顺序,只需要在全部执行完成后继续执行
    Future delete() async => ...
    Future add()    async => ...
    Future select() async => ...
    doJobs() async {
        await Future.wait([
            delete(),
            add(),
            select()
        print('Done');
    

    Stream

    如果想要从 Stream 获取值,也有两个选项:

    使用 async 和一个异步的循环(await for)

    Future main() async {
        await for(varOfType identifier in expression){
    

    异步循环的表达式必须有 Stream 类型,执行流程如下:

  • 等待流读取出一个值;
  • 将值放入循环主体内执行;
  • 循环执行1、2两步直至 Stream 关闭。
  • 如果需要退出监听,使用 break 或者 return,退出循环并且取消 Stream 的监听

    使用 Stream API

    通过 Stream 类的 listen() 方法,传入一个处理每个文件或目录的函数来订阅文件列表:

    void main(List<String> arguments) {
        // ...
        FileSystemEntity.isDirectory(searchPath).then((isDir) {
          if (isDir) {
            final startingDir = Directory(searchPath);
            startingDir
                .list(
                    recursive: argResults[recursive],
                    followLinks: argResults[followLinks])
                .listen((entity) {
              if (entity is File) {
                searchFile(entity, searchTerms);
          } else {
            searchFile(File(searchPath), searchTerms);
    // 同等的使用 async 和 await for 的方法
    Future main(List<String> arguments) async {
      // ...
        if (await FileSystemEntity.isDirectory(searchPath)) {
          final startingDir = Directory(searchPath);
          await for (var entity in startingDir.list(
              recursive: argResults[recursive],
              followLinks: argResults[followLinks])) {
            if (entity is File) {
              searchFile(entity, searchTerms);
        } else {
          searchFile(File(searchPath), searchTerms);
    // 处理成功和失败
    Future readFileAwaitFor() async {
      var config = File('config.txt');
      Stream<List<int>> inputStream = config.openRead();
      var lines = inputStream
          // 对值进行转换
          .transform(utf8.decoder)
          .transform(LineSplitter());
      try {
        await for (var line in lines) {
          print('Got ${line.length} characters from stream');
        print('file is now closed');
      } catch (e) {
        print(e);
    

    如果需要让类像一个方法一样被调用,实现 call 方法:

    class FuctionLike{
        call(String a, String b, String c) => "$a, $b, $c"
    main(){
        var fl = FunctionLike();
        var out = fl('a', 'b', 'c'); // 'a, b, c'
    

    隔离区 Isolates

    为了充分发挥多核 CPU 的优势,通常并发执行一些共享内存的线程,但是共享状态的并发容易出错并很可能导致代码的复杂化。
    Dart 代码都在隔离区内运行,为了确保不会从任何其他隔离区访问隔离区的状态,每个隔离区都有自己的内存堆。

    // 自定义注解
    class Todo {
        final String who;
        final String what;
        const Todo(this.who, this.what);
    // 使用