
前言
從謹慎地在項目中引入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設計理念:

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ù)會輸出

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()
}
可見sortedDescending和joinToString都是對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中使用extends和super來區(qū)分泛型中生產(chǎn)者和消費者,俗稱PEST,在Kotlin中對應的是out和in。同時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)秀的語言,值得大家嘗試。