So sánh Swift với Kotlin

Tại sự kiện Google I/O năm 2017, Google đã ra thông báo Kotlin là ngôn ngữ lập trình được Android chính thức hỗ trợ. Điều này không có gì đáng ngạc nhiên đối với các developer Android và vấn đề chỉ là khi nào thì Java sẽ bị thay thế.

Kotlin là ngôn ngữ đã "học hỏi" rất nhiều từ Swift - một ngôn ngữ lập trình iOS phổ biến được Apple giới thiệu nhiều năm về trước. Cú pháp và một số khái niệm lập trình của Kotlin rất giống với Swift. Điều này giúp cho các mobile developer có thể dễ dàng tiếp cận, phát triển ứng dụng cho cả 2 nền tảng iOS và Android mà không cần tốn quá nhiều thời gian để làm quen với ngôn ngữ mới.

Bài viết này so sánh cú pháp của Swift với Kotlin và một số khái niệm khác nhau giữa 2 ngôn ngữ lập trình mobile "bậc cao" này.

Basic

Hello World

Swift
print("Hello, world!")
Kotlin
println("Hello, world!")

Variables and constants

Trong Swift, khai báo biến sử dụng keyword var, khai báo hằng số sử dụng let.

Swift
var myVariable = 42
myVariable = 50
let myConstant = 42

Còn với Kotlin, khai báo biến cũng sử dụng var, còn hằng số là val.

Kotlin
var myVariable = 42
myVariable = 50
val myConstant = 42

Explicit Types

Khai báo các kiểu dữ liệu tường minh trong Swift và Kotlin giống hệt nhau.

Swift
let explicitDouble: Double = 70.0
Kotlin
val explicitDouble: Double = 70.0

Type Coercion

Ép kiểu dữ liệu trong Swift vẫn phải tường minh dùng các constructor như String(), Int(), Double()...

Swift
let label = "The width is "
let width = 94
let widthLabel = label + String(width)

Còn với Kotlin thì khá đơn giản.

Kotlin
val label = "The width is "
val width = 94
val widthLabel = label + width

String Interpolation

Cú pháp chèn thêm giá trị của các biến, các hằng số, các phép tính vào trong string. Swift dùng \().

Swift
let apples = 3
let oranges = 5
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

Kotlin dùng ${}.

Kotlin
val apples = 3
val oranges = 5
val fruitSummary = "I have ${apples + oranges} pieces of fruit."

Range Operator

Với Swift, operator 0...10 nghĩa là khoảng từ 0 đến 10 - [0, 10]. Còn 0..<10 nghĩa là nửa khoảng từ 0 đến 10 - [0, 10).

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

Trong Kotlin, để biểu diễn khoảng thì dùng 0..10 (2 dấu chấm). Nửa khoảng không gồm phần tử cuối là 0 until 10. Ngoài ra còn có downTo để đếm ngược và step 2 để nhảy cóc.

Kotlin
val names = arrayOf("Anna", "Alex", "Brian", "Jack")
val count = names.count()
for (i in 0 until count) {
    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

Collection

Array

Cách khai báo một mảng với các phần tử xác định trước trong Swift rất đơn giản.

Swift
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

Còn với Kotlin, sử dụng hàm arrayOf(<element...>).

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

Dictionary vs Map

Dictionary trong Swift với kiểu [<Kiểu dữ liệu của key>: <Kiểu dữ liệu của value>].

Swift
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

Dictionary trong Kotlin được gọi là Map, dùng hàm mutableMapOf(<key> to <value>,...) để khởi tạo với các cặp key/value có sẵn.

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

Empty Collection

Cú pháp tạo các array, dictionary/map trong Swift và Kotlin.

Swift
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
Kotlin
val emptyArray = arrayOf<String>()
val emptyMap = mapOf<String, Float>()

Function

Function

Swift dùng keyword func. Kotlin dùng keyword fun 🤣.

Function trong Swift có thể có tên hoặc không có tên các tham số ghi gọi hàm (dùng _).

Swift
func greet(_ name: String,_ day: String) -> String {
    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")

Tuple vs Data Class

Function trong Swift có thể trả về một bộ nhiều giá trị thuộc nhiều kiểu dữ liệu khác nhau thông qua Tuple.

Swift
func getGirlInfo() -> (Int, String, Double) {
    return (20, "Rose", 169.6)
}

Còn Kotlin phải sử dụng Data Class - một dạng class được lược giản.

Kotlin
data class GirlInfo(val age: Int, val name: String, val height: Double)
fun getGirlInfo() = GirlInfo(20, "Rose", 169.6)

Variable Number Of Arguments

Để khai báo fucntion với số lượng tham số truyền vào tùy ý, Swift sử dụng ... sau kiểu dữ liệu.

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

Trong khi đó, Kotlin dùng keyword vararg.

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

Function Type

Swift và Kotlin đều hỗ trợ kiểu trả về của function là một function khác, tuy cú pháp có khác đôi chút.

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

Map

Hàm map() trong Swift có thể viết tắt tham số đầu tiên bằng $0.

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

Kotlin dùng biến đặc trưng it để chỉ các phần tử của mảng đang được map.

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

Sort

Sắp xếp mảng.

Swift
var mutableArray = [1, 5, 3, 12, 2]
mutableArray.sort()
Kotlin
listOf(1, 5, 3, 12, 2).sorted()

Named Arguments

Việc có hay không sử dụng tên của các tham số khi gọi hàm trong Swift phụ thuộc vào ký tự _ trước tham số.

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

Còn Kotlin thì khá thoải mái. Bạn có thể tùy ý, thích thì bỏ tên tham số, thích thì viết cả tên tham số khi gọi hàm. Khi có tên tham số, Kotlin thêm dấu =.

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

// Viết như này đều được.
area(2, height = 2)
area(height = 3, width = 2)

Class

Declaration

Khai báo class trong Swift và Kotlin giống y nhau.

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

Usage

Cách khởi tạo, cách truy cập property, gọi method cũng giống.

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

Subclass

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 " +
	       sideLength + "."
    }
}

let test = Square(sideLength: 5.2, name: "square")
test.area()
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")
test.area()
test.simpleDescription()

Checking Type

Để kiểm tra một instance có phải một class không, cả 2 bên đều dùng keyword is với kết quả trả về true hoặc false.

Swift
var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}
Kotlin
var movieCount = 0
var songCount = 0

for (item in library) {
    if (item is Movie) {
        ++movieCount
    } else if (item is Song) {
        ++songCount
    }
}

Struct vs Data Class

Struct trong Swift và Data Class trong Kotlin đều là phiên bản đơn giản hơn của khái niệm class.

Swift
struct Foo {
    var data: Int
}
Kotlin
data class Foo(var data: Int)

Tuy nhiên, nhìn ngay vào tên chúng ta có thể thấy data class của Kotlin vẫn là một class, reference type. Còn struct là value type. Ví dụ khi khởi tạo instance của data class và struct như sau:

Swift
var foo1 = Foo(data: 2)
var foo2 = foo1
foo1.data = 4
Kotlin
var foo1 = Foo(2)
var foo2 = foo1
foo1.data = 4

Với struct của Swift, giá trị data của foo2 sẽ vẫn bằng 2 sau khi thay đổi giá trị data của foo1 vì phép gán foo2 = foo1 trong Swift sẽ copy một instance mới, độc lập, bằng với instance cũ. Nên khi thay đổi foo1, foo2 sẽ không bị thay đổi.

Còn với Kotlin, instance data class foo2 là reference mới vẫn trỏ đến vùng nhớ của instance foo1. Nên thay đổi giá trị foo1 sẽ làm thay đổi cả foo2.

Pattern Matching

Swift
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")
}
Kotlin
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")
}

Protocol vs Interface

Protocol của Swift và Interface của Kotlin nhìn chung khá giống nhau:

  • Đều yêu cầu các class/struct conform/implement protocol/interface đó phải implement các method của nó.
  • Đều có thể chứa các property (settable, gettable hoặc only gettable).
  • Đều có thể thêm default implement cho các method. Tuy nhiên với Swift chúng ta phải làm việc đó thông qua extension.
Swift
protocol Drawable {
    var myVariable: Int { get set }
    var myReadOnlyProperty: Int { get }
 
    func myMethod()
    func myMethodWithBody()
}

extension MyProtocol {
    func myMethodWithBody() {
        // function's implementation
    }
}

Còn với Kotlin, việc thêm default implementation cho method lại khá đơn giản, có thể viết luôn vào trong interface.

Kotlin
interface MyInterface {
    var myVariable: Int
    val myReadOnlyProperty: Int
    
    fun myMethod()
    
    fun myMethodWithBody() {
        // function's implementation
    }
}

Extension

Swift
extension Double {
    var km: Double { return self * 1000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1000.0 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) minimeters")
// prints "One inch is 0.0254 minimeters"
Kotlin
val Double.km: Double get() = this * 1000
val Double.m: Double get() = this
val Double.cm: Double get() = this / 100
val Double.mm: Double get() = this / 1000

val oneInch = 25.4.mm
println("One inch is $oneInch minimeters")
// prints "One inch is 0.0254 minimeters"