Dart 和 Kotlin 之间的一些差异

Dart

基础数据类型

Dart 主要类型有以下几种:

  • num
    数值有两个子类型:

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

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

  • bool

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

  • List

  • Map

变量声明

语言 变量 编译时常量 常量
Dart 具体类型、var const (变量) final (变量)
Kotlin var const val val

函数

Dart 中,函数也是对象,没有指定返回值时返回 null,即 函数可以作为函数的参数

函数声明有以下几种方式:

// 正常声明方式
String sayHello(String name){
    println("Hello, $name");
}

// 类型可选,即可声明:
sayHello(name){
    println("Hello, $name");
}// 此种方式不提倡,应明确函数的输入类型和输出类型,方便调用

// 在函数只返回一个简单表达式时,可以简略为一个 '=>' (Kotlin 中为 '=')
sayHello(name) => println("Hello, $name");

// 函数也是对象,也存在匿名函数
var sayHello = (name) => println("Hello, $name");

函数别名

// 声明一个函数别名
// typedef 返回值类型 别名名称(参数列表)
typedef void sayHello(String hellow)

// 它可以接收一个参数和返回值与别名相同的函数作为具体执行内容
// Kotlin 中为 (参数列表) -> 返回值类型

可选参数

Dart 支持两种可选参数:

  • 位置可选参数(已弃用)

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

    // 声明
    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

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

// 新建
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

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

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 {
        try{
            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. 将值放入循环主体内执行;
    3. 循环执行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);
      }
    }
    
    

Generators

如果需要延迟发送一系列值,可以选择使用 generator function

  • 同步生成 => 返回一个 Iterable 对象:

    Iterable<int> naturalsTo(int n) sync* {
        int k = 0;
        while(k < n)
            // 使用 yield 提交数据 
            yield k++;
    }
    
  • 异步生成 => 返回一个 Stream 对象:

    Stream<int> naturalsTo(int n) async* {
        int k = 0;
        while(k < n)
            // 使用 yield 提交数据 
            yield k++;
    }
    
  • 如果生成器是递归调用的,可以使用 yield* 来提升效率:

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

可调用的类

如果需要让类像一个方法一样被调用,实现 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);
}

// 使用
@Todo('name', 'job')
void doSomething {
    print('do something');
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 此文章是v1.0+时编写,年代久远,小心有毒,谨慎食用!!! 一些重要概念 所有的东西都是对象,所有的对象都是类的...
    soojade阅读 10,047评论 2 27
  • Dart API 学习记录(一) //www.greatytc.com/p/388d986c0f48 D...
    you的日常阅读 9,821评论 1 14
  • 关键词:好的婚姻 题主:女 问: 您好,冷爱!我想咨询好的婚姻是怎样的?我和他是大学同学,大一就在一起了(2010...
    冷爱阅读 324评论 0 0
  • “这话要怎么说呢?”“用嘴说啊!”你真的会说话吗?简单的答案,但或许一个不经意间,你已经得罪了别人而自己却不知。 ...
    篱陌冰筱阅读 284评论 0 2