美文网首页前端一统
Dart 和 Kotlin 之间的一些差异

Dart 和 Kotlin 之间的一些差异

作者: 佐以言 | 来源:发表于2018-07-12 18:22 被阅读2631次

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');
}

相关文章

网友评论

    本文标题:Dart 和 Kotlin 之间的一些差异

    本文链接:https://www.haomeiwen.com/subject/qfwrpftx.html