Kotlin精髓

引言

前言

從謹慎地在項目中引入kotlin到全部轉(zhuǎn)為kotlin開發(fā)我們用了大概半年的時間。這中間經(jīng)歷了從在一個小功能中嘗試使用到完全使用kotlin完成了大版本開發(fā)的過程。使用方法也從僅僅地用java風格寫kotlin代碼,慢慢地變成使用kotlin風格去編寫代碼。 轉(zhuǎn)載請注明來源「申國駿」

到目前為止,kotlin的引入至少沒有給我們帶來不必要的麻煩,在慢慢品嘗kotlin語法糖的過程中,我們領略到了能給開發(fā)者真正帶來好處的一些特性。本文就是對這些我們認為是精髓的一些特性的進行總結(jié),希望能給還在猶豫是否要開始學習kotlin或者剛開始編寫kotlin但是不知道該如何利用kotlin的人們先一睹kotlin的優(yōu)雅風采。

Kotlin設計哲學

KotlinConf 2018 - Conference Opening Keynote by Andrey Breslav 上講的Kotlin設計理念:


2018KotlinConference

Kotlin擁有強大的IDE廠商Intellij和Google的支持,保證了其務實、簡潔、安全和與JAVA互操作的良好設計理念。

其中務實表示了Kotlin并沒有獨創(chuàng)一些當前沒有或大眾不太熟悉的設計理念,而是吸收了眾多其他語言的精髓,并且提供強大的IDE支持,能真正方便開發(fā)者運用到實際項目之中。

簡潔主要指的是Kotlin支持隱藏例如getter、setter等Java樣板代碼,并且有大量的標準庫以及靈活的重載和擴展機制,來使代碼變得更加直觀和簡潔。

安全主要是說空值安全的控制以及類型自動檢測,幫助減少NullPointerException以及ClassCastException。

與Java互操作以為這可以與Java相互調(diào)用、混合調(diào)試以及同步重構(gòu),同時支持Java到kotlin代碼的自動轉(zhuǎn)換。

空值安全

Kotlin類型分為可空和非可空,賦值null到非可空類型會編譯出錯

fun main() {
    var a: String = "abc"
    a = null // compilation error
    var b: String? = "abc" 
    b = null // ok
}

對空的操作有以下這些

空值運算符

使用安全調(diào)用運算符 ?: 可以避免Java中大量的空值判斷。以下是一個對比的例子:

// 用Java實現(xiàn)
public void sendMessageToClient(
    @Nullable Client client,
    @Nullable String message,
    @NotNull Mailer mailer
) {
    if (client == null || message == null) return;
    PersonalInfo personalInfo = client.getPersonalInfo();
    if (personalInfo == null) return;
    String email = personalInfo.getEmail();
    if (email == null) return;
    mailer.sendMessage(email, message);
}
// 用Kotlin實現(xiàn)
fun sendMessageToClient(
    client: Client?, 
    message: String?, 
    mailer: Mailer
){
    val email = client?.personalInfo?.email
    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

擴展

擴展函數(shù)

擴展函數(shù)是Kotlin精華特點之一,可以給別人的類添加方法或者屬性,使得方法調(diào)用更加自然和直觀。通過擴展函數(shù)的特性,Kotlin內(nèi)置了大量的輔助擴展方法,非常實用。下面我們通過這個例子看一下

fun main() {
    val list = arrayListOf<Int>(1, 5, 3, 7, 9, 0)
    println(list.sortedDescending())
    println(list.joinToString(
        separator = " | ",
        prefix = "(",
        postfix = ")"
    ) {
        val result = it + 1
        result.toString()
    })
}

其中sortedDescending以及joinToString都是Kotlin內(nèi)置的擴展方法。
上述的函數(shù)會輸出

擴展函數(shù)實例輸出

Kotlin內(nèi)部的實現(xiàn)如下

public fun <T : Comparable<T>> Iterable<T>.sortedDescending(): List<T> {
    return sortedWith(reverseOrder())
}

public fun <T> Iterable<T>.joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
    return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}

可見sortedDescendingjoinToString都是對Iterable<T>類對象的一個擴展方法。

我們也可以自己實現(xiàn)一個自定義的擴展函數(shù)如下:

fun Int.largerThen(other: Int): Boolean {
    return this > other
}

fun main() {
    println(2.largerThen(1))
}

上述代碼輸出為true
通過擴展函數(shù)我們以非常直觀的方式,將某個類對象的工具類直接使用該類通過"."方式調(diào)用。
當然擴展函數(shù)是一種靜態(tài)的實現(xiàn)方式,不會對原來類對象的方法進行覆蓋,也不會有正常函數(shù)的子類方法覆蓋父類方法現(xiàn)象。

擴展屬性

擴展屬性與擴展函數(shù)類似,也是可以直接給類對象增加一個屬性。例如:

var StringBuilder.lastChar: Char
        get() = get(length -1)
        set(value: Char) {
            this.setCharAt(length -1, value)
        }
        
fun main() {
    val sb = StringBuilder("kotlin")
    println(sb.lastChar)
    sb.lastChar = '!'
    println(sb.lastChar)
}

無論是擴展函數(shù)還是擴展屬性,都是對Java代碼中utils方法很好的改變,可以避免多處相似功能的util定義以及使得調(diào)用更為直觀。

集合

通過擴展的方式,Kotlin對集合類提供了非常豐富且實用的諸多工具,只有你想不到,沒有你做不到。下面我們通過 Kotlin Koans 上的一個例子來說明一下:

data class Shop(val name: String, val customers: List<Customer>)

data class Customer(val name: String, val city: City, val orders: List<Order>) {
    override fun toString() = "$name from ${city.name}"
}

data class Order(val products: List<Product>, val isDelivered: Boolean)

data class Product(val name: String, val price: Double) {
    override fun toString() = "'$name' for $price"
}

data class City(val name: String) {
    override fun toString() = name
}

以上是數(shù)據(jù)結(jié)構(gòu)的定義,我們有一個超市,超市有很多顧客,每個顧客有很多筆訂單,訂單對應著一定數(shù)量的產(chǎn)品。下面我們來通過集合的操作來完成以下任務。

操作符 作用
filter 將集合里的元素過濾,并返回過濾后的元素
map 將集合里的元素一一對應轉(zhuǎn)換為另一個元素
// 返回商店中顧客來自的城市列表
fun Shop.getCitiesCustomersAreFrom(): Set<City> = customers.map { it.city }.toSet()

// 返回住在給定城市的所有顧客
fun Shop.getCustomersFrom(city: City): List<Customer> = customers.filter { it.city == city }

操作符 作用
all 判斷集合中的所有元素是否滿足某個條件,都滿足返回true
any 判斷集合中是否有元素滿足某個條件,有則返回true
count 返回集合中滿足某個條件的元素數(shù)量
find 查找集合中滿足某個條件的一個元素,不存在則返回null
// 如果超市中所有顧客都來自于給定城市,則返回true
fun Shop.checkAllCustomersAreFrom(city: City): Boolean = customers.all { it.city == city }

// 如果超市中有某個顧客來自于給定城市,則返回true
fun Shop.hasCustomerFrom(city: City): Boolean = customers.any{ it.city == city}

// 返回來自于某個城市的所有顧客數(shù)量
fun Shop.countCustomersFrom(city: City): Int = customers.count { it.city == city }

// 返回一個住在給定城市的顧客,若無返回null
fun Shop.findAnyCustomerFrom(city: City): Customer? = customers.find { it.city == city }

操作符 作用
flatMap 將集合的元素轉(zhuǎn)換為另外的元素(非一一對應)
// 返回所有該顧客購買過的商品集合
fun Customer.getOrderedProducts(): Set<Product> = orders.flatMap { it.products }.toSet()

// 返回超市中至少有一名顧客購買過的商品列表
fun Shop.getAllOrderedProducts(): Set<Product> = customers.flatMap { it.getOrderedProducts() }.toSet()
操作符 作用
max 返回集合中以某個條件排序的最大的元素
min 返回集合中以某個條件排序的最小的元素
// 返回商店中購買訂單次數(shù)最多的用戶
fun Shop.getCustomerWithMaximumNumberOfOrders(): Customer? = customers.maxBy { it.orders.size }

// 返回顧所購買過的最貴的商品
fun Customer.getMostExpensiveOrderedProduct(): Product? = orders.flatMap { it.products }.maxBy { it.price }
操作符 作用
sort 根據(jù)某個條件對集合元素進行排序
sum 對集合中的元素按照某種規(guī)則進行相加
groupBy 對集合中的元素按照某種規(guī)則進行組合
// 按照購買訂單數(shù)量升序返回商店的顧客
fun Shop.getCustomersSortedByNumberOfOrders(): List<Customer> = customers.sortedBy { it.orders.size }

// 返回顧客在商店中購買的所有訂單價格總和
fun Customer.getTotalOrderPrice(): Double = orders.flatMap { it.products }.sumByDouble { it.price }

// 返回商店中居住城市與顧客的映射
fun Shop.groupCustomersByCity(): Map<City, List<Customer>> = customers.groupBy { it.city }
操作符 作用
partition 根據(jù)某種規(guī)則將集合中的元素分為兩組
fold 對集合的元素按照某個邏輯進行一一累計
// 返回商店中未送到訂單比送達訂單要多的顧客列表
fun Shop.getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set<Customer> = customers.filter {
    val (delivered, undelivered) = it.orders.partition { it.isDelivered }
    undelivered.size > delivered.size
}.toSet()

// 對所有顧客購買過的商品取交集,返回所有顧客都購買過的商品列表
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    val allProduct = customers.flatMap { it.orders }.flatMap { it.products }.toSet()

    return customers.fold(allProduct) { orderedByAll, customer ->
        orderedByAll.intersect(customer.orders.flatMap { it.products })
    }
}

綜合使用:

// 返回顧客所有送達商品中最貴的商品
fun Customer.getMostExpensiveDeliveredProduct(): Product? {
    return orders.filter { it.isDelivered }.flatMap { it.products }.maxBy { it.price }
}

// 返回商店中某件商品的購買次數(shù)
fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int {
    return customers.flatMap { it.orders }.flatMap { it.products }.count{it == product}
}

Kotlin對集合提供了幾乎你能想到的所有操作,通過對這些操作的組合減少集合操作的復雜度,提高可讀性。以下是Java和Kotln對集合操作的對比

// 用Java實現(xiàn)
public Collection<String> doSomethingStrangeWithCollection(
        Collection<String> collection
) {
    Map<Integer, List<String>> groupsByLength = Maps.newHashMap();
    for (String s : collection) {
        List<String> strings = groupsByLength.get(s.length());
        if (strings == null) {
            strings = Lists.newArrayList();
            groupsByLength.put(s.length(), strings);
        }
        strings.add(s);
    }
    int maximumSizeOfGroup = 0;
    for (List<String> group : groupsByLength.values()) {
        if (group.size() > maximumSizeOfGroup) {
            maximumSizeOfGroup = group.size();
        }
    }
    for (List<String> group : groupsByLength.values()) {
        if (group.size() == maximumSizeOfGroup) {
            return group;
        }
    }
    return null;
}

// 用Kotlin實現(xiàn)
fun doSomethingStrangeWithCollection(collection: Collection<String>): Collection<String>? {

    val groupsByLength = collection.groupBy { s -> s.length }

    val maximumSizeOfGroup = groupsByLength.values.map { group -> group.size }.max()

    return groupsByLength.values.firstOrNull { group -> group.size == maximumSizeOfGroup }
}

運算符

運算符重載

還是舉 Kotlin Koans 上的運算符重載例子。假設我們需要實現(xiàn)以下功能:

enum class TimeInterval { DAY, WEEK, YEAR }

data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
    override fun compareTo(other: MyDate): Int {
        if (year != other.year) return year - other.year
        if (month != other.month) return month - other.month
        return dayOfMonth - other.dayOfMonth
    }

    override fun toString(): String {
        return "$year/$month/$dayOfMonth"
    }
}

fun main() {
    val first = MyDate(2018, 10, 30)
    val last = MyDate(2018, 11, 1)
    for (date in first..last) {
        println(date)
    }
    println()
    println(first + DAY)
    println()
    println(first + DAY * 2 + YEAR * 2)
}

輸出為以下:

運算符重載例子輸出

只要實現(xiàn)以下運算符重載既可:

operator fun MyDate.rangeTo(other: MyDate): DateRange = DateRange(this, other)

operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = this.addTimeIntervals(timeInterval, 1)

operator fun TimeInterval.times(num: Int): RepeatTimeInterval = RepeatTimeInterval(this, num)

operator fun MyDate.plus(repeatTimeInterval: RepeatTimeInterval): MyDate =
    this.addTimeIntervals(repeatTimeInterval.timeInterval, repeatTimeInterval.num)

class RepeatTimeInterval(val timeInterval: TimeInterval, val num: Int)

class DateRange(override val start: MyDate, override val endInclusive: MyDate) : ClosedRange<MyDate>, Iterable<MyDate> {
    override fun iterator(): Iterator<MyDate> = DateIterator(start, endInclusive)
}

class DateIterator(first: MyDate, private val last: MyDate) : Iterator<MyDate> {
    private var current = first
    override fun hasNext(): Boolean {
        return current <= last
    }

    override fun next(): MyDate {
        val result = current
        current = current.nextDay()
        return result
    }
}

fun MyDate.nextDay(): MyDate = this.addTimeIntervals(DAY, 1)

fun MyDate.addTimeIntervals(timeInterval: TimeInterval, number: Int): MyDate {
    val c = Calendar.getInstance()
    c.set(year + if (timeInterval == TimeInterval.YEAR) number else 0, month - 1, dayOfMonth)
    var timeInMillis = c.timeInMillis
    val millisecondsInADay = 24 * 60 * 60 * 1000L
    timeInMillis += number * when (timeInterval) {
        TimeInterval.DAY -> millisecondsInADay
        TimeInterval.WEEK -> 7 * millisecondsInADay
        TimeInterval.YEAR -> 0L
    }
    val result = Calendar.getInstance()
    result.timeInMillis = timeInMillis
    return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH) + 1, result.get(Calendar.DATE))
}

Kotlin支持使用指定的擴展函數(shù)來實現(xiàn)運算符的重載,運算符對應的方法名具體參見官方文檔 Operator overloading

infix

標記為infix的方法,可以類似于二元運算符使用,舉個例子

infix fun Int.plus(other: Int): Int {
    return this + other
}

fun main() {
    // 結(jié)果會輸出7
    println(3 plus 4)
}

infix方法執(zhí)行的優(yōu)先級低于算數(shù)運算符、類型轉(zhuǎn)換type case以及rangTo運算符,但是高于布爾、is、in check等其他運算符。
使用infix的擴展函數(shù)可以實現(xiàn)自定義的二元運算標記。

整潔Kotlin風格

在《Kotlin in Action》一書中有歸納了一些Kotlin對比Java的整潔語法如下:

常規(guī)語法 整潔語法 用到的功能
StringUtil.capitalize(s) s.capitalize() 擴展函數(shù)
1.to("one") 1 to "one" 中綴函數(shù) infix
set.add(2) set += 1 運算符重載
map.get("key") map["key"] get方法約定
file.use({f -> f.read}) file.use { it.read() } 括號內(nèi)lambda外移
sb.append("a") sb.append("b") with(sb) { append(“a") append(“b")} 帶接收者的lambda

整潔語法換句話來說也是Kotlin的一種編程風格,其他約定俗成的整潔Kotlin編程風格可見官方文檔 Idioms。非常建議大家看看Idioms這個文檔,里面涵蓋了非常Kotlin的使用方式,包括:

  • 使用默認參數(shù)代替方法重載
  • String模板(在Android中是否推薦仍值得商榷)
  • lambda使用it代替?zhèn)魅胫?/li>
  • 使用下標方式訪問map
  • 懶初始化屬性
  • 使用rangs范圍遍歷
  • if when表達式返回值
  • 等等

方法參數(shù)

Kotlin中的function是一等公民,擁有和變量一樣的定義以及傳參方式,如以下例子:

fun SQLiteDatabase.inTransaction(func: (SQLiteDatabase) -> Unit) {
  beginTransaction()
  try {
    func(this)
    setTransactionSuccessful()
  } finally {
    endTransaction()
  }
}
// 調(diào)用的時候就可以如下方法進行調(diào)用
db.inTransaction {
  it.db.delete("users", "first_name = ?", arrayOf("Jake"))
}

帶接收者的lambda表達式

lambda表達式可以聲明擁有接收者,例如:

val isEven: Int.() -> Boolean = {
    this % 2 == 0
}

fun main() {
    print(2.isEven())
}

這種帶接收者的lambda實際上也是一種方法定義,不過其優(yōu)先級比擴展方法要低,如果同時有擴展函數(shù)(如下)擁有相同名字,則會優(yōu)先調(diào)用擴展方法。

fun Int.isEven(): Boolean {
    return this % 2 != 0
}

let run with apply also

這幾個關鍵字其實都是Kotlin的特殊方法,他們可以讓lambda里面的代碼在相同的接收者中運行,避免冗余代碼,他們的聲明如下:

public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}
public inline fun <R> run(block: () -> R): R {
    return block()
}
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}
public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}

從聲明中可以看出他們有以下區(qū)別,假設在以下代碼中運行

class MyClass {
    fun test() {
        val str: String = "..."
        val result = str.xxx {
            print(this) // 接收者this
            print(it) // lambda參數(shù)it
            42 // 返回結(jié)果
        }
    }
}
方法 接收者this lambda參數(shù)it 返回結(jié)果
let this@MyClass String("...") Int(42)
run String("...") N\A Int(42)
with(*) String("...") N\A Int(42)
apply String("...") N\A String("...")
also this@MyClass String("...") String("...")

DSL構(gòu)建

以下是DSL和API調(diào)用方式的區(qū)別

// DSL
dependencies {
    compile("junit")
    compile("guice")
}
// API
project.dependencies.add("compile", "junit")
project.dependencies.add("compile", "guice")

對比下DSL方式更為簡潔且易讀。通過上述對lambda的介紹可以發(fā)現(xiàn)Kotlin可以完美地支持DSL方式編程,只要少量的擴展方法以及l(fā)ambda定義既可實現(xiàn)以下方式來構(gòu)建一段html表格

html {
    table {
        tr (color = getTitleColor()){
            this.td {
                text("Product")
            }
            td {
                text("Price")
            }
            td {
                text("Popularity")
            }
        }
        val products = getProducts()
        for ((index, product) in products.withIndex()) {
            tr {
                td(color = getCellColor(index, 0)) {
                    text(product.description)
                }
                td(color = getCellColor(index, 1)) {
                    text(product.price)
                }
                td(color = getCellColor(index, 2)) {
                        text(product.popularity)
                }
            }
        }
    }
}

具體定義如下:

import java.util.ArrayList

open class Tag(val name: String) {
    val children: MutableList<Tag> = ArrayList()
    val attributes: MutableList<Attribute> = ArrayList()

    override fun toString(): String {
        return "<$name" +
            (if (attributes.isEmpty()) "" else attributes.joinToString(separator = "", prefix = " ")) + ">" +
            (if (children.isEmpty()) "" else children.joinToString(separator = "")) +
            "</$name>"
    }
}

class Attribute(val name : String, val value : String) {
    override fun toString() = """$name="$value" """
}

fun <T: Tag> T.set(name: String, value: String?): T {
    if (value != null) {
        attributes.add(Attribute(name, value))
    }
    return this
}

fun <T: Tag> Tag.doInit(tag: T, init: T.() -> Unit): T {
    tag.init()
    children.add(tag)
    return tag
}

class Html: Tag("html")
class Table: Tag("table")
class Center: Tag("center")
class TR: Tag("tr")
class TD: Tag("td")
class Text(val text: String): Tag("b") {
    override fun toString() = text
}

fun html(init: Html.() -> Unit): Html = Html().apply(init)

fun Html.table(init : Table.() -> Unit) = doInit(Table(), init)
fun Html.center(init : Center.() -> Unit) = doInit(Center(), init)

fun Table.tr(color: String? = null, init : TR.() -> Unit) = doInit(TR(), init).set("bgcolor", color)

fun TR.td(color: String? = null, align : String = "left", init : TD.() -> Unit) = doInit(TD(), init).set("align", align).set("bgcolor", color)

fun Tag.text(s : Any?) = doInit(Text(s.toString()), {})

屬性代理

Kotlin提供對屬性代理的支持,可以將屬性的get set操作代理到外部執(zhí)行。代理的好處有三個:

  • 懶初始化,只在第一次調(diào)用進行初始化操作
  • 實現(xiàn)對屬性的觀察者模式
  • 方便對屬性進行保存等管理

下面來看比較常用的懶初始化例子:

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}

以上代碼會輸出

computed!
Hello
Hello

證明懶加載模塊只在第一次調(diào)用被執(zhí)行,然后會將得到的值保存起來,后面訪問屬性將不會繼續(xù)計算。這也是在Kotlin中實現(xiàn)單例模式的方式。這種懶初始化的過程也是線程同步的,線程同步方式有以下幾種:

public enum class LazyThreadSafetyMode {
    /**
     * 加鎖單一線程初始化Lazy實例
     */
    SYNCHRONIZED,

    /**
     * 初始化代碼塊會被多次調(diào)用,但只有首次初始化的值會賦值給Lazy實例
     */
    PUBLICATION,

    /**
     * 沒有線程安全,不保證同步,只能在確保單線程環(huán)境中使用
     */
    NONE,
}

解構(gòu)

解構(gòu)是非常實用的Kotlin提供的將一個對象屬性分離出來的特性。
內(nèi)部實現(xiàn)原理是通過聲明為componentN()的操作符重載實現(xiàn)。對Kotlin中的data類會自動生成component函數(shù),默認支持解構(gòu)操作。以下是解構(gòu)比較實用的一個例子:

for ((key, value) in map) {
   // 使用該 key、value 做些事情
}

協(xié)程Coroutine

先占個位,等我看懂了再來補充 :)
先po一個協(xié)程和Rxjava的對比吸引下大家

// RxJava
interface RemoteService {
    @GET("/trendingshows")
    fun trendingShows(): Single<List<Show>>
}
service.trendingShows()
    .scheduleOn(schedulers.io)
    .subscribe(::onTrendingLoaded, ::onError)
// Coroutine
interface RemoteService {
    @GET("/trendingshows")
    suspend fun trendingShows(): List<Show>
}
val show = withContext(dispatchers.io) {
    service.trendingShows()
}

在Android中使用

findViewById

通過引入import kotlinx.android.synthetic.main.實現(xiàn)直接獲取xml中ui組件。

anko

anko提供了很多工具類,幫助開發(fā)者在Android中更好地使用Kotlin。anko提供了以下實用工具:

  • 快捷Intent:startActivity(intentFor<SomeOtherActivity>("id" to 5).singleTop())
  • 快捷toast、dialog:toast("Hi there!")
  • 快捷log:info("London is the capital of Great Britain")
  • 快捷協(xié)程:bg()
  • layout DSL構(gòu)建
  • 等等

ktx

android-ktx 提供了一系列Andrdoid方法的簡潔實現(xiàn)。

與Java不太一樣的地方

  • static 與 伴生對象
    在Kotlin中并沒有static這個關鍵字,如果想要實現(xiàn)類似于Java中static的用法,需要聲明伴生對象companion object。使用object聲明的類實際上是一個單例,可以支持直接調(diào)用其中的屬性與方法。使用了companion修飾的object實際上是可以放在其他類內(nèi)部的單例,因此可以實現(xiàn)類似于Java中static的效果。至于為什么Kotlin要這樣設計,原因是Kotlin希望所有屬性都是一個類對象,不做差異化處理,這也是為什么Java中的int、long等基本數(shù)據(jù)類型在Kotlin中也用Int、Long處理的原因。

  • 默認都是final,除非聲明為open
    在Kotlin中所有方法默認都是禁止覆蓋的,這樣的好處是規(guī)范了接口設計的安全性,僅開放那些確實在設計中希望子類覆蓋的方法。

  • 默認是public,多了internal
    在Java中,如果不加可見性修飾的話默認是包內(nèi)可見,Kotlin中默認都是public。同時Kotlin加入了internal關鍵字,代表著是模塊內(nèi)可見。這個可見性彌補了使用Java進行模塊設計的過程中,可見性設計的缺陷。如果要想在Java中實現(xiàn)僅開放某些方法給外部模塊使用,但是這些方法又能在內(nèi)部模塊自由調(diào)用,那只能是把這些方法都放到一個包內(nèi),顯然是一個很不好的包結(jié)構(gòu)設計。Kotlininternal關鍵字可以完美解決這個問題。要想在Java調(diào)用的時候完全隱蔽Kotlin的方法,可以加上@JvmSynthetic。

  • 泛型
    Java中使用extendssuper來區(qū)分泛型中生產(chǎn)者和消費者,俗稱PEST,在Kotlin中對應的是outin。同時Java與Kotlin都會對泛型進行運行時擦除,Kotlin不一樣的是可以對inline方法使用reified關鍵字來提供運行時類型。

  • 本地方法
    由于在Kotlin語言中方法是一等公民,因此可以聲明局部生命周期的本地方法,如下例子:

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

學習資源

Kotlin online try
Kotlin官方文檔
kotlin in action
Android Development with kotlin
Kotlin for Android Developers

問題

在Java項目中引入kotlin在大多數(shù)情況下都是無痛的,且可以馬上帶給我們不一樣的快捷高效體驗。如果硬是要說出一點Kotlin的問題,我覺得會有幾個:

  • Kotlin加入會增加方法數(shù)以及不多的代碼體積,這在大多數(shù)情況下不會產(chǎn)生太大的問題
  • 寫法太靈活,較難統(tǒng)一。由于Kotlin允許程序員選擇傳統(tǒng)的Java風味或者Kotlin風味來編寫代碼,這種靈活性可能導致混合風味的代碼出現(xiàn),且較難統(tǒng)一。
  • 過多的大括號層級嵌套。這是因為lambda以及方法參數(shù)帶來的,其初衷是希望大家可以用DSL的代碼風格,如果沒掌握DSL方式的話可能會寫出比較丑陋的多層級嵌套Java風味代碼,影響代碼可讀性。

最后

Kotlin是一門優(yōu)秀的語言,值得大家嘗試。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容