Dart vs Kotlin vs Swift 语法比较

发布时间 2023-08-02 15:27:41作者: cps666

原文地址 juejin.cn


" 世界上只有两种编程语言:一种经常被人喷,一种没人用。” —— Bjarne Stroustrup

现代原生移动应用主要由 Swift (iOS) 或者 Kotlin (Android) 编程语言开发。另外有两种主要的跨平台应用开发框架 —— React Native (JavaScript) 和 Flutter,后者以 Dart 作为编程语言。React Native 有自己的利基市场,Flutter 看起来更有前途,并常常被拿来同 Swift 和 Kotlin 比较。

这篇文章会聚焦在 Dart,Kotlin, 和 Swift 之间的异同,包括语法,函数和类声明,以及一些其他重要细节。这些语言有很多相似之处,使得在 Dart 和原生 Kotlin/Swift 应用开发间切换上下文相对更容易。

变量和常量

Dart

var myVariable = 42; 
myVariable = 50;
final myConstant = 42;
const myConstant2 = 'John';


Kotlin

var myVariable = 42 
myVariable = 50
val myConstant = 42


Swift

var myVariable = 42 
myVariable = 50
let myConstant = 42


「译者按」在这一项中,留意 Dart 的 const 会比较复杂。可参考这篇文章:

Flutter 知识梳理 (Dart) - Dart 中 static, final, const 区别​juejin.im

总结:Dart 中的 final 和 const 都能用来表明变量在一次赋值后就不想要被改变或者不需要改变。final 用于编译期不可计算的值,const 则用于编译期可计算的值。const 比 final 更 “深度” —— 一个 final 的集合,其集合的元素不是 final 的;而一个 const 的集合,集合内的内容也是 const 的,即所谓的 “the object will be frozen and completely immutable (对象被冻结且完全不可变)”。 const 既可用在左边,也可用在右边,用在右边表明值不可变 (变量本身还可变)。当 const 用在左边,且左边的变量是集合,则隐含了右边的值同时也是 const 的。通过上下文可以得知表达式一定是常量,那么 const 可以被省略,也建议省略。例如:

const primaryColors = [  Color("red", [255, 0, 0]),
  Color("green", [0, 255, 0]),
  Color("blue", [0, 0, 255]),
];


一个类的成员变量,如果是 const ,必须同时声明为 static ,即类 (域) 成员变量。这容易理解,因为如果是实例变量,按照设计是跟实例走,而 const 本身要求编译期可知,这就要求类的 const 变量同时也要是 static 变量。 Dart 2.5 之后,你还可以通过类型检查和转换,以及 collection if, spread 操作符来定义 (const) 常量。 const constructor 是一个难点,可以参考这个 stackoverflow 链接里的回答:其中有一个概率叫 "canonical instance",也就是 a "canonicalized" instance。const constructor 对于还没切换到惰性初始化之前的 Dart 还蛮重要的。

How does the const constructor actually work?​stackoverflow.com

显式类型

Dart

num myDoubleOrInt = 5;
int myInt = 5;
double myDouble = 5.0;


Kotlin

val explicitDouble: Double = 70.0


Swift

let explicitDouble: Double = 70


总结:在几种语言中,没有继承关系的数字类型之间不能混用赋值。也就是说,不能用一个 Int 给 Double 赋值,也不能用一个 Double 给一个 Int 赋值。Kotlin 对于右边的书写格式要求严格,只有 70.0 才代表 Double 类型。而 Swift 在初始化时允许不写 .0 ,因为类型注解显式说明了是 Double 类型。反之,如果是声明的 Int 类型,你写 70.0 Swift 也并不允许。所以显式注解了 Double 类型后可以省略数字后的 .0 大概是 Swift 语言对书写便利的一种支持。毕竟,直觉上有人可能期望不写小数点编译器也能知道自己想要的是浮点数,但大概不会有人故意加了小数点却期望编译器认为自己想要的是整数。
Dart 中 int 和 double 都继承自 num ,num 在 Dart 2.1 之前可以用来规避一些 int 和 double 之间转换带来的错误。但 2.1 之后什么情况下需要用 num ,我查了一些资料,没有找到答案。

动态类型

Dart                       Kotlin                         Swift
// 动态类型                  // 动态类型(not for JVM)        // 动态类型
dynamic name = 'John';     var name: dynamic = ...        N/A    


类型强制 (Type Coercion)

Dart

final label = "The width is ";
final width = 94;
final width2 = 2.0
final widthLabel = label + width.toString();
final widthSum = width + width2;


Kotlin

val label = "The width is "
val width = 94
val width2 = 2.0
val widthLabel = label + width
val widthSum = width + width2


Swift

let label = "The width is "
let width = 94
let width2 = 2.0
let widthLabel = label + String(width)
let widthSum = width + width2 // 编译错误


总结:几种语言中,Swift 对类型强制 (隐式转换) 的要求最为严格(死板),连数字类型之间都不能直接运算。Dart 灵活一些, 数字类型之间直接运算,但它们和字符串之间不能直接运算。Kotlin 在数字和字符串相加时,选择了作为字符串拼接处理,这一点跟 Java 一致。当然,死板和灵活是一种观点。“Explicit is better than implicit.” 又是一种理念。

字符串和字符串插值 (模板)

Dart

// 多行字符串
var s1 = """This is a
multi-line string.""";

// 字符串插值
final apples = 3;
final oranges = 5;
final fruitSummary = "I have ${apples + oranges}  pieces of fruit.";


Kotlin

// 多行字符串
val s1 = """This is a
multi-line string."""

// 字符串插值
val apples = 3
val oranges = 5
val fruitSummary = "I have ${apples + oranges}  pieces of fruit."


Swift

// 多行字符串
let s1 = """This is a
multi-line string."""

// 字符串插值
let apples = 3;
let oranges = 5;
let fruitSummary = "I have \(apples + oranges) pieces of fruit."


总结:Swift 的字符串插值书写方式相对小众,\ 加 () 。Kotlin 和 Dart 是更主流的 $ 和 {}。

范围操作符

Dart

N/A


Kotlin

val names = arrayOf("Anna", "Alex", "Brian", "Jack") 
val count = names.count() 
for (i in 0..count - 1) {     
    println("Person ${i + 1} is called ${names[i]}") 
} 
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack


Swift

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
   print("Person \(i + 1) is called \(names[i])") 
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack


总结:Kotlin 和 Swift 都提供了范围操作符。 Swift 还提供 ... 和 ..< ,即开放和半开放两个版本的范围操作符,前者包含右边的值,后者不包含右边的值。个人更喜欢 Swift 提供的实现,因为在某些情况下,有语义的边界值只有一个,采用有语义的变量标识会比 -1 或者 +1 这种写法更好看一些。

集合

在 Dart,Kotlin 和 Swift 中,数组或者列表都是集合类型。集合中的一个需要注意的点是列表和数组,在很多编程语言中,它们可以被认为是同一个东西。也有些编程语言对两者做了区分。Kotlin 更接近 Java,数组定长内容可变,列表变长内容可变 (mutable) 可不变(immutable),默认不可变。Dart 和 Swift 的做法相同之处在于都没有像 Java 或者 Kotlin 那样把列表和数组细分开,不同之处是 Dart 字面名选择了 List ,而 Swift 字面名选择了 Array。

数组

Dart

var emptyList = <int>[];
List fixedLengthList = new List(3);
List growableList = new List();

fixedLengthList[0] = 0;    // OK
// fixedLengthList[3] = 0; // 编译错误,超出范围
// fixedLengthList.add(0); // 编译错误,定长list不能 add
// growableList[0] = 0; // 编译错误,虽然是可增长的,但初始长度是0,需要通过 add 来添加
growableList.add(0);      // OK

var complexList = [];     // 不论是否有显式类型注释,dart 都可以用 [] 来初始化列表,
                          // 如果不指定类型,则类型是 List<dynamic>
complexList.add(1);       // OK
complexList.add("apple"); // OK,因为类型是 List<dynamic> ,
                          // 所以列表可以混合存放不同子类型 

print(complexList); // 输出 [1, "apple"]


Dart 对于定长和变长的设计,容易理解。但个人对这种用数字参数来区别两种 List 的方式有点不适应,可能是因为 Java 的经验导致?
Dart List 添加元素 API 是 add ,打印输出用 [] 注记。

Kotlin

val shoppingList = mutableListOf("catfish", "water", "tulips", "blue paint") 
shoppingList[1] = "bottle of water"
// shoppingList[3] = "rock" // 编译错误,index 越界
shoppingList.add("rock") // OK

println(shoppingList) // 输出 [catfish, bottle of water, tulips, blue paint, rock]

val shoppingArray = arrayOf("catfish", "water", "tulips", "blue paint") 
shoppingArray[1] = "bottle of water"

println(shoppingArray) // 不会直接输出数组元素,需要另外处理


Kotlin 的 集合类型除了 array ,其他都是默认 immutable 的。所以如果你需要修改集合,需要使用 mutableListOf ,mutableSetOf,mutableMapOf 。
Kotlin List 添加元素 API 是 add 。打印输出用 [] 注记。

Swift

var shoppingArray = ["catfish", "water", "tulips", "blue paint"]
shoppingArray[1] = "bottle of water"
// shoppingArray[3] = "rock" // 编译错误,index 越界
shoppingArray.append("rock") // OK

var complexArray:Array<Any> = []   // 如果有显式类型注释,[] 可以用来初始化数组,
                                   // 类型是 Array<Any>
complexArray.append(1)             // OK
complexArray.append("apple")       // OK,因为类型是 Array<Any> ,
                                   // 所以数组可以混合存放不同子类型 

print(complexArray)                // 输出 [1, "apple"]
// 注意,如果两个 array 是 let 声明的,则不能修改 


Swift 的数组没有直接细分出定长或变长以及 mutable 或 immutable 。
Swift Array 添加元素 API 是 append ,打印输出用 [] 注记。

Set

Dart

var s = Set<int>();
// 或
Set<int> s = Set<int>();
Set<int> s = {}; // 如果有显式的类型注释,右边可以直接用 {} 初始化
print("s.isEmpty is ${s.isEmpty}");
s.add(1);
s.add(2);
s.add(1);

// 或
Set<int> s = {1, 2, 1};

print(s);               // 输出只包含1, 2 的形式,如 {1, 2}           


Dart Set 的添加 API 是 add ,注记(打印)形式是用 {} ,显式类型注释时可以直接用花括号初始化集合内容。

Kotlin

val s = mutableSetOf<Int>()
println("s.isEmpty() is ${s.isEmpty()}")

s.add(1)
s.add(2)
s.add(1)

// 或
val s = mutableSetOf<Int>(1, 2, 1)

println(s)              // 输出只包含1, 2 的形式,如 [1, 2]


Kotlin Set 的添加 API 也是 add ,注记(打印)形式也是 [] ,声明时初始化是把元素放在函数的圆括号内。

Swift

var s = Set<Int>()
// 或
var s: Set<Int> = Set<Int>()
var s: Set<Int> = [] // 如果有显式的类型注释,右边可以直接用 [] 初始化
print("s.isEmpty is \(s.isEmpty)") // true
s.insert(1)
s.insert(2)
s.insert(1)

// 或
var s: Set<Int> = [1, 2, 1]

print(s)                // 输出只包含1, 2 的形式,如 [1, 2]
// 注意,如果 s 是 let 声明的,则不能修改 


Swift Set 的添加 API 是 insert ,注记(打印)形式也是 [],显式类型注释时可以直接用中括号初始化集合内容。

Map (字典)

Dart

// 空 Map
var emptyMap = {}; // 不论是否有显式类型注释,dart 都可以用 {} 来初始化 Map,
                  // 如果不指定类型,则类型是 _InternalLinkedHashMap<dynamic, dynamic>

// Mutable Map
var occupations = {"Malcolm": "Captain", "Kaylee": "Mechanic"};
occupations["Jayne"] = "Public Relations";

print(occupations); // 输出 {Malcolm: Captain, Kaylee: Mechanic, Jayne: Public Relations}


Kotlin

val occupations = mutableMapOf("Malcolm" to "Captain", "Kaylee" to "Mechanic")
occupations["Jayne"] = "Public Relations"

println(occupations) // 输出 {Malcolm=Captain, Kaylee=Mechanic, Jayne=Public Relations}


Swift

var occupations = ["Malcolm": "Captain", "Kaylee": "Mechanic",] // 虽然没有类型注释,但是key:value形式可以推断出是字典,及类型参数
occupations["Jayne"] = "Public Relations"

print(occupations) // 输出 ["Jayne": "Public Relations", "Malcolm": "Captain", "Kaylee": "Mechanic"]
// 注意,如果 occupations 是 let 声明的,则不能修改 


总结:Dart,Kotlin 的类型参数都是写在 <> 里 (Java 也是),在 Swift 中,对于数组和字典,则直接把类型参数 (类型注释) 写在 [] 里。
在 Collection 场景下,Dart 对中括号和花括号的使用可以说比较 “豪放” —— 不指定类型的话,[] 默认表示 List ,{} 默认表示 _InternalLinkedHashMap<dynamic, dynamic> 。在有类型注释的时候,{} 还可以用来注记 Set 。
Kotlin 用 [] 注记 List 和 Set ,用 {} 注记 Map 。
Swift “克制” 一些,三种 Collection 的打印输出都是 [] ,有类型注释或者能够推断的时候可以分别用来初始化 Array,Set,Dictionary 。
值得注意的是,对于 Swift ,值的 mutability 也是交给绑定的类型决定的,如果是变量,则集合是 mutable 的;如果是常量,则集合是 immutable 的。也是说,var 或者 let 同时决定了绑定和值是否可变;对于 Dart ,final 用于声明,确定绑定是否可变。const 用于声明,隐含了 final ,同时要求值是编译器可计算的。const 用于修饰值,则相当于确定值 immutable ;对于 Kotlin ,绑定的 mutability 和值的 mutability 是分离的,前者由 var 或者 val 确定,后者通过不同的 API 直接区分,如 mapOf / mutableMapOf 。

// Dart 的 immutable binding 和 immutable object
final array = const [1, 2, 3];
const array2 = [1, 2, 3]; // 当类型为集合时,右边隐含 const,Effective Dart 建议不写。


可空性 & 可选型(可空类型)

 Dart (tested <= v2.7)       Kotlin                        Swift

 // 声明                     // 声明                         // 声明
 int id = null;             var id: Int? = null            var id: Int? = nil
 id.abs(); //异常            id.inc() // 编译错误           id.signum() // 编译错误
 id?.abs(); // 安全的调用     id!!.inc() // 异常             id?.signum() // 安全的调用

 // Null aware 操作符        // Elvis 操作符                 // 空合操作符
 int id = null;             val id: Int? = null            let id: Int? = nil
 var userId = id ?? -1;     var userId = id ?: -1          var userId = id ?? -1

 // 可选型(还在实现中)         // 可选型                        // 可选型
 int? id; // 需要启用实验特性  val id: Int?                    var id: Int? // 可选型
 var userId = id ?? -1;     var userId = id ?: -1           var userId = id ?? -1
                            // 编译错误:id 必须先被初始化      // 输出 -1


总结:Dart 目前还不是严格区分可空和非空两种类型的 “严格” 的类型系统,但 NNBD non-nullable (by default) 一直都是呼声很高的特性,到 Dart 2.7 ,NNBD 已经部分实现,不过需要通过启用实验特性的方式体验。当然,还有许多迁移工作要完成。
Kotlin,Swift,包括 C# ,都已经支持可空和非空的严格类型。Kotlin 的变量在使用前需要显式赋值,包括可空类型的 null 初值 (编译期的静态检查),而 Swift 如果是可空类型,不显式赋值则默认 nil 。

控制流

关于 Kotlin 和 Swift 的控制流比较,这篇文章写的不错,推荐阅读:

hujiangtech.github.io/tech/androi…

Dart

// Switch case 
var id = 5;
switch (id) {
  case 1:
    print("id == 1");
    break;
  case 2:
    print("id == 2");
    break;
  default:
    print("id is undefined");
}

// Inclusive for loop
for (var i = 1; i <= 5; i++) {}

// range match
// N/A

// Cascade notation (..)
querySelector('#confirm')
  ..text = 'Confirm'
  ..classes.add('important')
  ..onClick.listen((e) =>
window.alert('Confirmed!'));


Kotlin

// When
val id = 5
when (id) {
  1 -> print("id == 1")
  2 -> print("id == 2")
else -> {
  print("id is undefined")
  }
}

// Inclusive for loop
for (index in 1..5) {}

// range match
val nb = 42 
when (nb) {     
    in 0..7, 8, 9 -> println("single digit")
    10 -> println("double digits")
    in 11..99 -> println("double digits")
    in 100..999 -> println("triple digits")
    else -> println("four or more digits")
}

// Cascade notation (..)
// N/A,可以用 Builder 模式或者 apply, also 函数 


Swift

// Switch case
let id = 5
switch id {
  case 1:
    print("id == 1")
  case 2:
    print("id == 2")
  default:
    print("id is undefined")
}

// Inclusive for loop
for index in 1...5 {}

// range match
let nb = 42
switch nb {     
    case 0...7, 8, 9: print("single digit")
    case 10: print("double digits")
    case 11...99: print("double digits")
    case 100...999: print("triple digits")
    default: print("four or more digits")
}

// Cascade notation (..)
// N/A,用 Builder 或者 apply{} 扩展


总结:Kotlin,Swift 的控制流都各有许多减少代码量,增加代码清晰度的花样。比如 Kotlin 用增强版的 when 取代了 switch ,if 和 when 不仅可做控制语句,也可作表达式使用。Swift 的 switch 语句支持元组,where 语句添加额外判断等。
Dart 的 switch 跟 Java 一样,需要主动 break,Swift 则相反,匹配到则自动退出 switch ,想要显示继续执行用 fallthrough 。
Dart 的级联注记 (Cascade notation) 也算它的一个小特色了,不过其他语言也有各自的替代方案。严格说来,它并不算一个操作符,只是 Dart 的一种特殊语法。笔者自己在写 Java 代码时也封装 API 做过类似的操作。这种书写方式最大的好处是:把一堆高度相关的代码逻辑 “强制” 聚在一处,使代码看起来更流畅,整洁。

函数

Dart

String greet(String name, String day) {
    return "Hello $name, today is $day.";
}
greet("Bob", "Tuesday");


Kotlin

fun greet(name: String, day: String): String {
    return "Hello $name, today is $day."
}
greet("Bob", "Tuesday")


Swift

func greet(_ name: String, _ day: String) -> String {
    return "Hello \(name), today is \(day)."
}
greet("Bob", "Tuesday")


总结:Swift 和 Kotlin 都是类型后置的风格,注意 Swift 返回值用 -> , Kotlin 仍是用冒号。
下划线在 Swift 中有许多用法,其中有一类主要是 “忽略”,包括忽略元组元素,循环的局部变量,以及函数中的外部参数名。Standard Library 中有大量 API 的第一个参数都是可以忽略外部参数名的。
Kotlin 用 fun,Swift 用 func,Dart 和 Java ,C 等语言一样,没有为函数声明引入额外的关键字。

可变数量的参数

Dart

N/A


Kotlin

fun sumOf(vararg numbers: Int): Int {     
    var sum = 0
    for (number in numbers) {         
        sum += number     
    }     
    return sum
}
sumOf(42, 597, 12)


一个 Kotlin 函数只能有一个可变数量的参数。对于顺序,没有强制约束,可以放在普通参数之前或者之后。当同时包含可变参数和普通参数时,调用时普通参数需要使用命名参数的调用语法。

fun sumOfNumbers(vararg numbers: Double, initialSum: Double): Double {
    var sum = initialSum
    for(number in numbers) {
        sum += number
    }
    return sum
}
sumOfNumbers(1.5, 2.5, initialSum=100.0) // Result = 104.0


Swift

func sumOf(_ numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}
sumOf(42, 597, 12)


可变数量的参数在 Swift 官方文档中称为 variadic parameter 。同样,一个函数只允许至多一个 variadic parameter 。

函数类型

Dart

// 写法1
int Function(int) makeIncrementer(int a) {
    return (a) {
        return 1 + a;
    };
}

// 写法2
int Function(int) makeIncrementer2(int a) {
    int myInnerFunction(a) {
        return 1 + a;
    }

    final myFunction = myInnerFunction;
    return myFunction;
}

// 写法3:类型是 Closure: (int) => int
int Function(int) increment = (int a) {
    return 1 + a;
};

// 写法4:类型是 Closure: (dynamic) => num
var increment2 = (a) {
    return 1 + a;
};

print(makeIncrementer);
print(makeIncrementer(1));
print(makeIncrementer(1)());

print(increment);
print(increment(1));


Kotlin

fun makeIncrementer(): (Int) -> Int {     
    val addOne = fun(number: Int): Int {         
        return 1 + number     
    }     
    return addOne 
} 
val increment = makeIncrementer() 
increment(7)

 // makeIncrementer 可以写成更短的形式
fun makeIncrementer2() = fun(number: Int) = 1 + number


Swift

func makeIncrementer() -> (Int -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
let increment = makeIncrementer()
increment(7)


总结:在这几种语言中,函数都是一等公民。Swift 是中规中矩的写法,Kotlin 提供了简短的版本。Dart 也有几种写法。当然,写法 1、2 和写法 3 、4 并不对等,推荐 3、4 的写法。留意结尾的分号,这里有没有分号结尾的差别类似于 C++ 中的声明和定义。写法 1、2 仅声明,写法 3、4 声明的同时也定义了,所以必须加分号。

映射

Dart

final numbers = [20, 19, 7, 12];

// 写法1
numbers.map((number) => 3 * number).toList();

// 写法2:类似 Python 的语法
[for (var number in numbers) 3 * number];


Kotlin

val numbers = listOf(20, 19, 7, 12)
numbers.map { 3 * it }


Swift

let numbers = [20, 19, 7, 12]
numbers.map { 3 * $0 }


排序

Dart

var list = [1, 5, 3, 12, 2];
list.sort();


Kotlin

listOf(1, 5, 3, 12, 2).sorted()


Swift

var mutableArray = [1, 5, 3, 12, 2].sorted()


命名参数

Dart

// Dart 的命名参数同时是可选参数,用 {} 包裹
int area({int width, int height}) {
    return width * height;
}

area(width: 2, height: 3)
// 命名参数可以按任意顺序书写
area(height: 3, width: 2)

// Dart 的可选顺序参数,用 [] 包裹
int area([int width, int height]) {
    return width * height;
}

area(); // 可编译,运行时错误
area(2); // 可编译,运行时错误
area(2, 3); // OK


Kotlin

fun area(width: Int, height: Int) = width * height
area(width = 2, height = 3)

// 混合顺序参数和命名参数的写法
area(2, height = 3)

// 全部采用命名参数时可以按任意顺序书写
area(height = 3, width = 2)


Swift

func area(width: Int, height: Int) -> Int {
    return width * height
}
area(width: 2, height: 3)


总结:Swift 的参数列表设计最为严格,参数需要同时匹配顺序和名称 (命名的作用是为了使代码含义更清晰) 。Kotlin 类似 Python,纯命名参数方式可以随意调整顺序。 Dart 则把命名参数同时也是可选参数。

声明和定义

Dart

class Shape {
    var numberOfSides = 0;
    String simpleDescription() {
        return "A shape with $numberOfSides sides.";
    }
}

var shape = Shape();
shape.numberOfSides = 7;
var shapeDescription = shape.simpleDescription();


Kotlin

class Shape {
    var numberOfSides = 0
    fun simpleDescription() =
        "A shape with $numberOfSides sides."
}

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()


Swift

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()


继承

Dart

class NamedShape {
    int numberOfSides = 0;
    String name;

    NamedShape(String name) {
        this.name = name;
    }
    
    // 或者采用语法糖写法
    NamedShape(this.name);

    String simpleDescription() {
        return "A shape with $numberOfSides sides.";
    }
}

class Square extends NamedShape {
    double sideLength;

    Square(this.sideLength, String name) : super(name) {
        numberOfSides = 4;
    }

    double area() {
        return sideLength * sideLength;
    }

    // Dart 官方建议审慎使用 @override 注解,当超类不在自身控制下时使用
    // 这个注解主要是为了超类更改了代码,子类不知情仍正常工作时可以提醒程序员
    @override
    String simpleDescription() {
        return "A square with sides of length $sideLength.";
    }
}

final test = Square(;5.2, "square");
print(test.area());
print(test.simpleDescription());


Kotlin

open class NamedShape(val name: String) {
    var numberOfSides = 0

    open fun simpleDescription() =
        "A shape with $numberOfSides sides."
}

class Square(var sideLength: BigDecimal, name: String) :
        NamedShape(name) {
    init {
        numberOfSides = 4
    }

    fun area() = sideLength.pow(2)

    override fun simpleDescription() =
        "A square with sides of length $sideLength."
}

val test = Square(BigDecimal("5.2"), "square")
println(test.area())
println(test.simpleDescription())


Swift

class NamedShape {
    var numberOfSides: Int = 0
    let name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

class Square: NamedShape {
    var sideLength: Double

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        self.numberOfSides = 4
    }

    func area() -> Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -> String {
        return "A square with sides of length " +
	       String(sideLength) + "."
    }
}

let test = Square(sideLength: 5.2, name: "square")
print(test.area())
print(test.simpleDescription())


总结:继承的书写方式在编程语言中可谓五花八门。像 Python 是将父类放在 () 里紧跟在子类名后,Ruby 是用 < 。典型的 C 家族是用 :。现代编程语言主要在构造器上有一些花样。其中 Kotlin 的花样特别多,分主从构造器,主构造器必有 (没声明的话会默认生成无参的 public 主构造器) ,主构造器隐含初始化块等。写法上主构造器是直接跟在类头。

// Kotlin
class NamedShape constructor(val name: String) { /*……*/ }
// 如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。


Swift 的 init 其实也挺复杂。可以参考下面这篇文章

猫克杯:[五十斩 ·Swift 设计模式] init 模式​zhuanlan.zhihu.com

关于 override ,个人更喜欢 Kotlin 和 Swift 把它们作为关键字的做法。这样在编译期就能够明确编程者的意图。其一避免本不想重写但不小心重写的情况,其二显式书写确保重写的方法存在,即正确重写了。

类型检查

Dart

final test = Square(5.2, "square");
if (test is Square) {
    print("test is square.");
}

if (test is! Square) {
    print("test is not square.");
}

// 获取类型
print(test.runtimeType);


Kotlin

val test = Square(BigDecimal("5.2"), "square")
if (test is Square) {
    println("test is square.")
}
if (test !is Double) {
    println("test is not square.")
}

// 获取类型
println(test.javaClass.kotlin);


Swift

let test = Square(sideLength: 5.2, name: "square")
if (test is Square) {
    print("test is square.")
}
if !(test is Double) {
    print("test is not square.")
}

// 获取类型
print(type(of: test));


接口(协议)编程

Dart

// 被声明为 abstract 因此不能被实例化
abstract class AbstractContainer {
  // 定义构造器, 字段, 方法...

  void updateChildren(); // Abstract method.
}

// Dart 移除了显式接口声明,
// 每个类都隐式的定义个了一个包含该类和该类实现的接口的所有实例成员 
// (说人话:类就是接口)。
// 一个类通过 implements 语句声明它们实现一个或者多个接口,
// 然后提供接口要求的 API,例如:

// Person. 包含了 greet() 方法的隐式接口
class Person {
    // 仅库可见,属于接口的一部分
    final _name;

    // 不属于接口的一部分,因为这是个构造器
    Person(this._name);

    // 属于接口的一部分
    String greet(String who) => 'Hello, $who. I am $_name.';
}

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

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

// 如果想要像 Java 那样的接口,建议用抽象类作为接口,相当于是把关心的部分提炼出来


Kotlin

interface Nameable {
    fun name(): String
}

fun <T: Nameable> f(x: T) {
    println("Name is " + x.name())
}

class NameX : Nameable {
    override fun name() : String {
        return "NameX";
    }
}

f(x = NameX())


Swift

protocol Nameable {
    func name() -> String
}

func f<T: Nameable>(x: T) {
    print("Name is " + x.name())
}

class NameX: Nameable {
    func name() -> String {
        return "NameX";
    }
}

f(x: NameX())


Lambda 表达式、闭包

lambda 表达式,简单理解为匿名函数,它是 “函数字面值”,即未声明的函数, 但立即做为表达式传递。闭包,简单理解为持有外部环境变量的函数。两者都是函数,并非所有的匿名函数都是闭包,也并非所有的闭包都是匿名的。

Dart

// 匿名函数标准形式
// ([[Type] param1[, …]]) { 
//  codeBlock; 
// }; 
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});


Kotlin

// Lambda 表达式标准形式
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
println(sum(1, 2))

// 拖尾 lambda 表达式
// 如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外:
val product = items.fold(1) { acc, e -> acc * e }

// 唯一参数 lambda 表达式
// 如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:
run { println("...") }


Swift

// 闭包表达式标准形式
// { (parameters) -> return type in
//    statements
// }
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// 拖尾闭包
// 如果函数的最后一个参数是函数,那么作为相应参数传入的闭包表达式可以放在圆括号之外:
reversedNames = names.sorted() { $0 > $1 }

// 唯一参数闭包
// 如果该闭包是调用时唯一的参数,那么圆括号可以完全省略
reversedNames = names.sorted { $0 > $1 }