一文快速了解 Kotlin 和 Dart 的区别

701 阅读8分钟

学习一门新的语言,最好的方法就是从自己熟悉的语言中找不同。这篇文章就从基本语法、逻辑判断和控制、属性、函数、接口、抽象类、类、集合、IO、泛型、异常、多线程、反射角度看 Kotlin 与 Dart 的区别。

基本语法的区别

变量声明

在 Dart 中,变量需要显示声明类型,代码如下所示:

  // 整型 age,表示年龄的数值
  int age = 2;
  // 浮点型 weight,表示体重的数值
  double weight = 4.5;

如果想要主动推动类型,可以使用关键字 var,它和 kotlin 中的 var 的作用是一致的。在 Dart 中还有一个关键字 dynamic,它是一种特殊的类型,使用 dynamic 声明的变量可以在运行时存储任何类型的值,并且可以随时改变其存储的值的类型。代码示例如下:

void main() {
  // 使用 var 声明变量,初始值为字符串,Dart 推断其类型为 String
  var message = 'Hello, Dart!';
  print('message 的类型: ${message.runtimeType}');

  // 使用 dynamic 声明变量
  dynamic value;

  // 赋值为字符串
  value = 'Hello';
  print('value 的类型: ${value.runtimeType}');

  // 赋值为整数
  value = 123;
  print('value 的类型: ${value.runtimeType}');

  // 调用 value 的方法,编译时不会检查,运行时根据实际类型调用
  if (value is int) {
    print(value + 1); 
  }
}

函数声明

在 Dart 中,函数的声明和 Java 类似,代码示例如下:

double bmi(double height, double wight) {
  // 具体算法
  double result = wight / (height * height);
  return result;
}

kotlin 中支持给函数的参数设置默认值,调用时也可以使用形参传入数值。在 Dart 中通过命名参数实现,命名参数需要加上 {},代码示例如下:

double bmi({
  required double height, // required 关键字表示该入参必须传入;
  double weight = 65, // 可以用 `=` 提供参数的默认值
}) {
  // 具体算法
  double result = weight / (height * height);
  return result;
}

void main() {
  // 调用时通过 : 设置参数值
  double toly = bmi(weight: 70, height: 1.8);
}

在 Dart 中还有一种参数是位置参数,它需要加上 [],代码示例如下:

// 位置参数,必须要有默认值
double bmi([double height = 1.79, double weight = 65]) {
  // 具体算法
  double result = weight / (height * height);
  return result;
}

void main() {
  double toly = bmi(1.8,70);
}

逻辑判断和控制

if

Dart 的 if 和 Java 一样,无法获取 if 表达式的返回值。代码示例如下:

void main() {
  double height = 1.18;
  // 布尔值可以通过运算获得
  bool free = height < 1.2;
  if(free){
    print("可免费入园");
  }else{
    print("请购买门票");
  }
}

switch

Dart 的 switch 也和 Java 一样,代码示例如下:

void main() {
  String mark = 'A';
  switch (mark) {
    case 'A':
      print("优秀");
      break;
    case 'B':
      print("良好");
      break;
    case 'C':
      print("普通");
      break;
    case 'D':
      print("较差");
      break;
    case 'E':
      print("极差");
      break;
    default:
      print("未知等级");
  }
}

for

Dart 中 for 的语法也和 Java 中一样,代码示例如下:

void main() {
  int sum = 0;
  for (int i = 0; i < 5; i = i + 1) {
    sum = sum + i;
    print("第 $i 次执行,sum = $sum");
  }
}

---->[输出结果]----
第 0 次执行,sum = 0
第 1 次执行,sum = 1
第 2 次执行,sum = 3
第 3 次执行,sum = 6
第 4 次执行,sum = 10

while

Dart 中的 while 语法也和 Java 中一样,代码示例如下:

void main() {
  int i = 0;
  while (i < 10) {
    double bmiValue = bmi(1.75, 65); // 示例:身高1.75m,体重65kg
    print("BMI: $bmiValue\n");
    i++;
  }
}

Dart 中也同样支持 breakcontinue,它们的作用和在 Java 中是一样的。

~/

Dart 中的加减乘除的运算符和 Java 一样,不同的是 Dart 增加了一个 ~/ 运算符,用来求商,代码示例如下:

void main() {
  print(10 % 3);//1   余
  print(10 ~/ 3);//3  商
}

逻辑运算符

Dart 的逻辑运算符 &&||! 也和 Java 一样,代码示例如下:

void main() {
  // 公园是否开放
  bool open = true;
  // 是否免费
  bool free = false;

  // 公园是否免费进入
  bool freeEnter = open && free;
}

默认情况下,== 可以判断是否指向同一个实例,但是大部分数据类型都会重写该方法,让其判断值是否相同。因此Dart 提供了 identical 方法来判断两个对象是否指向同一个实例。

位运算符

在 Dart 中,左移、右移的运算符 也和 Java 一样,代码示例如下:

属性的区别

访问控制的区别

在 Dart 中,没有 public、private 等访问控制符。在 Dart 中,默认情况下,所有的类、变量、方法、函数等都是公开的,即可以在整个项目的任何地方访问。如果你想要限制访问,则需要在在标识符(类、变量、方法等)前加下划线 _ 来表示该成员是私有的,私有成员只能在定义它们的库(文件)内部访问。代码示例如下:

// 定义一个包含私有成员的类
class PrivateExample {
  // 私有变量
  String _privateVariable = 'This is a private variable';

  // 私有方法
  void _privateMethod() {
    print('This is a private method');
  }

  // 公开方法,用于在类内部访问私有成员
  void accessPrivateMembers() {
    print(_privateVariable);
    _privateMethod();
  }
}

常量的区别

在 Dart 中可以使用 finalconst 定义常量。const与final的共同点是初始化后都无法更改。两者的区别是,const值在编译时会检查值,而final值在运行时才检查值。因此不能给const定义的常量赋值为不确定的值。代码示例如下:

const time='2020-05-03';
const time=DataTime.now();//这行代码在编译器中会报错
final time='2020-05-03';
final time=DataTime.now();//这行代码不会报错

类型的区别

Dart 和 Kotlin 一样,一切都是对象,没有 Java 中的基本数据类型。但是 Dart 的数据类型与 Kotlin 有所不同。

可空类型

Dart 和 Kotlin 类似,也支持可空类型。不同的是,在 Dart 中使用 ! 来强制把可空类型转换为非可空类型;同时使用 ?? 来为可空类型提供一个默认值。代码示例如下:

int? nullableInt = null;
// 使用安全调用操作符,当 nullableInt 为 null 时,不会调用方法,直接返回 null
int? result = nullableInt?.toDouble()?.toInt();
print('result 的值: $result');

nullableInt = nullableInt ?? 0;
print('nullableInt 的值: $nullableInt');

需要注意的是,Dart 的 runtimeType 方法获取的是实际运行时的类型。比如对于 int? a 变量,它只会返回 int 类型或者 Null 类型。

数字类型(Numbers )

在 Dart 语言中,数字有两大类型: 整型(整数) int 和浮点型(小数) double ,没有 float 类型。定义变量的语法是 类型名 变量名 = 值,代码示例如下:

void main(){
  // 整型 age,表示年龄的数值
  int age = 2;
  // 浮点型 weight,表示体重的数值
  double weight = 4.5;
}

虽然 int、double 看上去像是 java 中的基本数据类型,但实际上它们都是继承 num 类。

布尔类型(bool)

Dart 的布尔类型使用 bool 表示,代码示例如下:

void main() {
  // 直接赋值
  bool enable = true;
  double height = 1.18;
  // 布尔值可以通过运算获得
  bool free = height < 1.2;
}

字符串类型

在 Dart 中有3种创建字符串的方式:

  • 使用单引号、双引号创建字符串。
  • 使用3个单引号或者双引号创建多行字符串。
  • 使用r创建原始字符串。

代码示例如下:

void main() {
  // 使用双引号创建字符串
  String doubleQuotedString = "Hello, Dart!";
  print(doubleQuotedString);

  // 使用三个单引号创建多行字符串
  String multiLineSingleQuoted = '''
    这是一个多行字符串示例。
    它可以包含多行文本。
    这里是第三行。
    ''';
  print(multiLineSingleQuoted);

  // 普通字符串,需要对反斜杠进行转义
  String normalString = '这是一个包含路径的字符串:C:\\Users\\Documents';
  print(normalString);

  // 原始字符串,反斜杠无需转义
  String rawString = r'这是一个包含路径的原始字符串:C:\Users\Documents';
  print(rawString);

}

同时 String 还可以通过 + 拼接,也可以使用 $变量名 在字符串内插入变量值,这个和 kotlin 的语法是一样的。代码示例如下:

void main() {
  String str1 = "Hello" + "World";
  String str2 = "$str1 Dart";
}

列表类型(List)

在 Dart 中,列表表示集合,也表示数组,代码示例如下:

void main() {
  String str1 = "Hello" + "World";
  String str2 = "$str1 Dart";
}

列表中的常用方法有:add()、length()、remove()、insert()、indexOf()、sublist()、forEach()、shuffle()等,代码示例如下:

var list=["apple","banana","cherry"];
print(list.length);//输出列表的长度
list.add("bayberry");//末尾添加"bayberry字符串
print(list);
list.remove("apple")//删除apple字符串
print(list);
list.insert(1, 'dates');//在1索引插入dates字符串
print(list);
print(list.indexOf("cherry"));//获取cherry字符串所在位置
print(list.sublist(2));//去除前两个元素后的新的列表
list.forEach(print);//遍历并输出列表
list.shuffle();//打乱列表顺序
print(list);

键值对类型(Map)

示例如下:

Map<int, String> numMap = {
  0: 'zero',
  1: 'one',
  2: 'two',
};
print(numMap);
numMap.remove(1);
print(numMap);

---->[控制台输出]----
{0: zero, 1: one, 2: two}
{0: zero, 2: two}

Set 类型

示例如下:

Set<int> numSet = {1, 9, 9, 4};
print(numSet);

---->[控制台输出]----
{1, 9, 4}

Set 最重要的特征是可以进行集合间的运算,这点 List 列表是无法做到的。两个集合间通过 differenceunion 、intersection 方法可以分别计算差集、并集、交集。计算的结果也是一个集合,代码示例如下:

Set<int> a = {1, 9, 4};
Set<int> b = {1, 9, 3};
print(a.difference(b));// 差集
print(a.union(b)); // 并集
print(a.intersection(b)); // 交集

---->[控制台输出]----
{4}
{1, 9, 4, 3}
{1, 9}

类型判断与转换

在 Dart中,runtimeType 可以获取对象类型;isis! 可以判断类型;而 as 可以强制把类型转换。代码示例如下:

void main() {
  var obj1 = 'Hello, Dart!';
  var obj = 'Hello';
  
  // 判断 obj1 是否为 String 类型
  bool isString = obj1 is String;
  print('obj1 is String: $isString');

  // 判断 obj 是否不是 int 类型
  bool isNotInt = obj is! int;
  print('obj is not int: $isNotInt');
}

静态变量的区别

在 Dart 中,静态变量和静态方法和 Java 一样都被 static 修饰,代码示例如下:

class Cat{
    // 静态变量
    static String TAG = 'Cat';
    
    static String eatFish(){
        print('猫天生喜欢吃鱼!');
    }
}

函数的区别

构造函数的区别

Dart 的构造函数和 Java 类似,代码示例如下:

class Human {
  String name = '';
  double weight = 0;
  double height = 0;

  Human(String name,double weight,double height){
    this.name = name;
    this.weight = weight;
    this.height = height;
  }
  
  // 上面的方法可以简写成下面这样
  Human(this.name, this.weight, this.height)
}

由于 Dart 不支持函数重载,因此推出了命名构造函数来解决这个问题。代码示例如下:

class Human {
  String name = '';
  double weight = 0;
  double height = 0;
  int age = 0;

  Human(String name, double weight, double height) {
    this.name = name;
    this.weight = weight;
    this.height = height;
  }

  Human.withAge(this.name, this.weight, this.height, this.age);
}

在构造函数中还可以加上 factory 关键字,这表示工厂构造函数。工厂构造函数最大的特点是可以手动返回一个对象。代码示例如下:

class Person {
  String name;

  static final Map<String, Person> cache = {};

  Person(this.name);

  factory Person.getSingle(String name) {
    if (cache.containsKey(name)) {
      return cache[name] as Person;
    } else {
      cache[name] = new Person(name);
      return cache[name] as Person;
    }
  }
}

高阶函数

和 Kotlin 类型,Dart 也可以把函数作为变量来传递,代码示例如下:

// 定义一个高阶函数,接受一个函数作为参数
int calculate(int a, int b, int Function(int, int) operation) {
  return operation(a, b);
}

// 定义加法函数
int add(int a, int b) {
  return a + b;
}

// 定义减法函数
int subtract(int a, int b) {
  return a - b;
}

void main() {
  int num1 = 10;
  int num2 = 5;

  // 调用 calculate 函数并传递 add 函数
  int sum = calculate(num1, num2, add);
  print('加法结果: $sum');

  // 调用 calculate 函数并传递 subtract 函数
  int difference = calculate(num1, num2, subtract);
  print('减法结果: $difference');
}

单行函数

在 Dart 中,使用 => 用来表示 {},常用于单行函数中,代码示例如下:

int squareWithArrow(int num) => num * num;

级联运算符

级联运算符 (.., ?..) 可以让你在同一个对象上连续调用多个对象的变量或方法,它类似于 Kotlin 中的 apply{}?.apply{}。代码示例如下:

class Person {
  String name = '';
  int age = 0;

  void introduce() {
    print('我叫 $name,今年 $age 岁。');
  }
}

void main() {
  Person person = Person()
    ..name = '张三'
    ..age = 25
    ..introduce();
}

接口和抽象类的区别

在 Dart 中,没有 interface 关键字来定义接口。但是每一个类(除了 int、String 这些类型类不行)外都可以通过 implements 来作为接口来实现。

在 Dart 中,抽象类也是用 abstract 来声明的,抽象类可以用 implements 作为接口来实现;或者使用 extends 来作为父类来继承。

代码示例如下:

// 定义一个抽象类作为接口
abstract class Shape {
  // 没有实现的是抽象方法,不需要加 abstract 修饰
  double area();
  
  void draw() {
    print('绘制一个形状');
  }
}

// implements 表示子类是作为接口来实现的
// 因此不能使用 Shape 中的任何属性和方法,同时必须重写所有的属性和方法
class Circle implements Shape {
  double radius;

  Circle(this.radius);

  @override
  double area() {
    return 3.14 * radius * radius;
  }

  // 必须实现
  @override
  void draw() {
    print('绘制一个半径为 $radius 的圆');
  }
}

// extends 表示子类是作为父类来实现的
// 因此只需要实现抽象方法,同时可以使用Shape 中的属性和方法
class Rectangle extends Shape {
  double width;
  double height;

  Rectangle(this.width, this.height);

  @override
  double area() {
    return width * height;
  }
  
}

类的区别

new 的区别

在 Kotlin 中,创建对象不需要 new 关键字,而在 Dart 中,new 是可选的。

继承的区别

在 Dart 中,一般的继承和 Java 一样。它们都是使用 extends 继承父类,使用 super 调用父类的方法。代码示例如下:

// 定义父类
class Animal {
  String name;

  // 父类构造函数
  Animal(this.name);

  // 父类方法
  void eat() {
    print('$name 正在进食');
  }
}

// 定义子类,继承自 Animal 类
class Dog extends Animal {
  // 子类构造函数,调用父类构造函数
  Dog(String name) : super(name);

  // 子类特有的方法
  void bark() {
    print('$name 正在汪汪叫');
  }
}

void main() {
  // 创建 Dog 类的实例
  Dog dog = Dog('旺财');
  // 调用从父类继承的方法
  dog.eat();
  // 调用子类特有的方法
  dog.bark();
}

在 Kotlin 中,我们使用继承是为了复用父类的代码。而在 Dart 中,除了继承这种方式外,还提供了 mixin(混合)来让你在不使用继承的情况下,将一组方法和属性添加到多个类中。mixin 主要用于实现代码的复用和组合,避免了多重继承带来的复杂性和问题。代码示例如下:

// 定义一个 mixin
mixin CanFly {
  void fly() {
    print('正在飞行');
  }
}

// 定义另一个 mixin
mixin CanSwim {
  void swim() {
    print('正在游泳');
  }
}

// 定义一个类,使用 with 关键字应用 mixin
class Bird with CanFly {
  void chirp() {
    print('鸟儿在叽叽喳喳叫');
  }
}

// 定义另一个类,应用多个 mixin
class Duck with CanFly, CanSwim {
  void quack() {
    print('鸭子在嘎嘎叫');
  }
}

void main() {
  Bird bird = Bird();
  bird.fly();
  bird.chirp();

  Duck duck = Duck();
  duck.fly();
  duck.swim();
  duck.quack();
}

需要注意,被 mixins 的类有如下限制:

  • 只能继承自 Object,不能继承其他类
  • 不能有构造函数

内部类的区别

和 Kotlin 不同,Dart 不支持内部类。

单例

在 Dart 中没有 Kotlin 中的 object 关键字来直接声明单例。Dart 中的单例声明如下:

class Singleton {
  // 静态私有变量,用于存储单例实例
  static final Singleton _instance = Singleton._internal();

  // 私有构造函数,防止外部直接创建实例
  Singleton._internal();

  // 静态方法,用于获取单例实例
  static Singleton get instance => _instance;

  void doSomething() {
    print('单例对象正在执行操作...');
  }
}

void main() {
  Singleton singleton1 = Singleton.instance;
  Singleton singleton2 = Singleton.instance;

  // 验证两个实例是否相同
  print('singleton1 和 singleton2 是否为同一个实例: ${identical(singleton1, singleton2)}');

  singleton1.doSomething();
}

枚举的区别

Dart 中的枚举的定义和 Kotlin 类似,代码示例如下:

// 定义一个枚举类型表示一周的天数
enum Weekday {
  monday(name: "星期一"),
  tuesday(name: "星期二"),
  wednesday(name: "星期三"),
  thursday(name: "星期四"),
  friday(name: "星期五"),
  saturday(name: "星期六"),
  sunday(name: "星期天");

  const Weekday({required this.name});
  final String name;
}

更详细的用法可以看 Dart 的枚举类型的高阶用法Dart 的枚举你知道多少?

导入库的区别

不同于 Kotlin 通过import导入各种类型的开发包,Dart 将这些导入的开发包称为库,每段Dart程序都是由被称为库的模块化单元组成的。在 Dart 中也是通过 import 关键字来导入的,代码示例如下:

import 'dart:math'; // 引入内置 math 库,对于 Dart 内置的库,使用 `dart:xxxxxx` 的形式
import 'package:test/test.dart'; // 引入包管理器中的库
import 'lib/test.dart'; // 引入自己写的库

IO的区别

在 Dart 中的 IO 操作是使用的 dart:io 库,代码示例如下:

import 'dart:io';

void main() async {
  try {
    // 创建一个 File 对象,指定要读取的文件路径
    File file = File('example.txt');
    // 以 UTF-8 编码读取文件的全部内容
    String content = await file.readAsString();
    print('文件内容:$content');
  } catch (e) {
    print('读取文件时出错:$e');
  }
}

更多IO操作可以看 Flutter系列之Dart文件IO操作_dart.io delete file-CSDN博客

泛型的区别

Dart 中泛型也是使用 <T> 来表示,也可以通过 extends 来限制。但是不同于 Kotlin,没有星投影、 逆变等操作。代码示例如下:

// 定义一个抽象类
abstract class Shape {
  void draw();
}

// 定义一个矩形类,继承自 Shape
class Rectangle extends Shape {
  @override
  void draw() {
    print('绘制矩形');
  }
}

// 定义一个圆形类,继承自 Shape
class Circle extends Shape {
  @override
  void draw() {
    print('绘制圆形');
  }
}

// 定义一个泛型类,限制类型参数必须是 Shape 或其子类
class ShapeContainer<T extends Shape> {
  T shape;

  ShapeContainer(this.shape);

  void drawShape() {
    shape.draw();
  }
}

void main() {
  Rectangle rectangle = Rectangle();
  ShapeContainer<Rectangle> rectangleContainer = ShapeContainer(rectangle);
  rectangleContainer.drawShape();

  Circle circle = Circle();
  ShapeContainer<Circle> circleContainer = ShapeContainer(circle);
  circleContainer.drawShape();

  // 下面这行代码会报错,因为 int 不是 Shape 或其子类
  // ShapeContainer<int> intContainer = ShapeContainer(123);
}

多线程的区别

Dart是一门单线程的语言,不支持多线程。具体可以看 Flutter-Dart中的异步和多线程讲解

异常的区别

和 Java 异常处理类似,Dart 使用 throw 抛出异常、try-catch-finally 来处理异常。不同的是,在 Dart 中,我们需要使用 on 关键字来指定异常的类型;也可以使用 rethrow 关键字在我们捕获异常时,再把这个异常抛出。代码示例如下:

  try {
    // throw Error();
    throw Exception('this is exception error');
  } on Exception catch (e) {
    print('this is Unknown exception $e');
  } catch (e,s) { // catch 方法有两个参数,第一个参数是抛出的异常对象,第二个参数是栈信息
    print('No specified type, handles all error $e');
    print('Stack trace:\n $s');
  }

注解的区别

在 Dart 中,注解的样式和 Kotlin 一样都是 @注解名。不同的是,Dart 中自定义注解需要创建一个类。类名通常以Annotation为后缀。自定义注解类可以包含任意数量的参数,这些参数可以在使用注解时传入。要定义自定义注解,需要创建一个带有const构造函数的类。这个类可以有任意数量的属性,但它们必须是编译时常量。代码示例如下:

class MyAnnotation {
  final String description;
  const MyAnnotation(this.description);
}

// 使用自定义注解注解类
@MyAnnotation('这是一个注解类的元数据')
class MyClass {
  // ...
}
// 使用自定义注解注解函数
@MyAnnotation('这是一个注解函数的元数据')
void myFunction() {
  // ...
}
// 使用自定义元素据注解变量
@MyAnnotation('这是一个注解变量的元数据')
int myVariable;

在这个示例中,我们定义了一个名为MyAnnotation的自定义注解。这个注解有一个属性description,用于存储与注解关联的文本描述。我们还为这个类提供了一个const构造函数,以便在使用注解时可以创建编译时常量。

反射的区别

Dart 的反射具体可以看 Dart基础4-反射 - 简书

参考