在2017的Google I/O大會上绸吸,Google宣布倦淀,這門誕生于俄羅斯的年輕語言侈贷,即日起成為最新的一級安卓編程語言毕泌,并在Android Studio 3.0 已加入對其的支持环鲤。Kotlin是JetBrains設(shè)計(jì)并開源(最新開源版本為1.1.4)的一門靜態(tài)編程語言纯趋,由于設(shè)計(jì)者是IDE著名開發(fā)商JetBrains公司,Kotlin從一開始就自帶IDE 支持冷离。在Intellij IDEA 15和Android Studio3.0之前的版本需要安裝Kotlin插件吵冒,之后的版本自帶Kotlin插件。
Kotlin想給我們展現(xiàn)什么西剥?
1. 互操作與互兼容
談起Kotlin與Java痹栖,很多人估計(jì)會聯(lián)想起Swift與OC,Swift是蘋果于2014年WWDC(蘋果開發(fā)者大會)發(fā)布的新開發(fā)語言瞭空,可與Objective-C共同運(yùn)行于Mac OS和iOS平臺揪阿。根據(jù)了解,現(xiàn)在國內(nèi)一些大公司依然使用OC或者Swift與OC混編的方式開發(fā)iOS咆畏。這其中涉及很多原因:
- 早期版本的Swift編譯速度和運(yùn)行速度慢图甜,導(dǎo)致用戶覺得應(yīng)用卡;
- 用Swift開發(fā)打包后的安裝包比用OC的大;
- 再比如Swift的版本更新太快鳖眼,不太穩(wěn)定黑毅,開發(fā)者不得不花時(shí)間去適配到最新的Swift。
- Swift與OC并不能完全互操作钦讳,存在兼容性問題矿瘦,除此以外雖然Swift調(diào)用OC比較簡單枕面,但OC里用Swift比較麻煩。(源自簡書作者LingoGuo)
但是Kotlin缚去,卻與Java有著100%的互操作和互兼容性潮秘,并在編譯速度和運(yùn)行速度上,與Java相比并未有劣勢可言:
- 兼容性(Compatibility)—— Kotlin能兼容JDK 6易结,確保Kotlin的應(yīng)用程序在老版本的Android設(shè)備上運(yùn)行
- 運(yùn)行速度(Performance)—— Kotlin 應(yīng)用程序的運(yùn)行速度與 Java 差不多枕荞,但是隨著Kotlin對內(nèi)聯(lián)函數(shù)的支持,使用 lambda 表達(dá)式的代碼通常比用 Java 寫的代碼運(yùn)行得更快
- 互操作性(Interoperability)—— 用Java寫的類庫和代碼可以繼續(xù)在Kotlin的代碼中繼續(xù)沿用搞动,并支持Kotlin和Java兩種語言的混合編程
- 占用空間(Footprint)—— Kotlin擁有一個(gè)緊密的runtime library躏精,在ProGuard的作用下減小了更多內(nèi)存的占用,在實(shí)際應(yīng)用程序中鹦肿,Kotlin 開發(fā)的apk比Java開發(fā)的apk增加不到 100K 的大小矗烛。
- 編譯時(shí)間(Compilation Time)—— Kotlin 支持高效的增量編譯,所以對于清理構(gòu)建會有額外的開銷箩溃,增量構(gòu)建通常與 Java 一樣快或者更快(增量編譯只重新編譯代碼中更改的部分)
2. 易表現(xiàn)(簡潔)
- 常量與變量
在Kotlin中瞭吃,變量用var聲明,常量用val聲明涣旨,val聲明的對象意味著它在實(shí)例化之后就不能再去改變它的狀態(tài)了歪架。如果你需要一個(gè)這個(gè)對象修改之后的版本,那就會再創(chuàng)建一個(gè)新的對象霹陡。這個(gè)讓編程更加具有健壯性和預(yù)估性:
val s = "Example" // A String
val actionBar = supportActionBar // An ActionBar in an Activity context
var i = 23 // An Int
var m = 23.4 // An Double
而在Java中和蚪,聲明一個(gè)對象不可變需要加final屬性,間接性一目明了:
private final String s = "Example"
- 基本類型
在Kotlin中穆律,基本類型自帶轉(zhuǎn)化方法:
val i:Int = 7
val d:Double = i.toDouble()
val c:Char = 'c'
val i:Int = c.toInt()
- 函數(shù)
Kotlin的函數(shù)可以給參數(shù)指定一個(gè)默認(rèn)值使得它們變得可選,如下导俘,第二個(gè)參數(shù)( length) 指定了一個(gè)默認(rèn)值峦耘,意味著調(diào)用的時(shí)候可以傳入第二個(gè)值或者不傳,這樣可以避免你需要的重載函數(shù):
fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, length).show()
}
toast("Hello")
toast("Hello", Toast.LENGTH_LONG)
這個(gè)與下面的Java代碼是一樣的旅薄,明顯Kotlin更加易于表現(xiàn):
void toast(String message){
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
void toast(String message, int length){
Toast.makeText(this, message, length).show();
}
- 類
在Java中辅髓,如果我們要典型的數(shù)據(jù)類,我們需要去編寫(至少生成) 這些代碼:
public class Artist {
private long id;
private String name;
private String url;
private String mbid;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "Artist{" + "id=" + id + ", name='" + name + '\'' + ", url='" + url + '\'' + '}';
}
}
使用Kotlin少梁,我們只需要通過數(shù)據(jù)類洛口,這個(gè)數(shù)據(jù)類,它會自動生成所有屬性和它們的訪問器凯沪,以及一些有用的方法第焰,比如toString():
data class Artist(
var id: Long,
var name: String,
var url: String,
var mbid: String)
如果我們使用不可修改的對象,假如我們需要修改這個(gè)對象狀態(tài)妨马,必須要創(chuàng)建一個(gè)新的一個(gè)或者多個(gè)屬性被修改的實(shí)例挺举。這個(gè)任務(wù)在java里是非常重復(fù)且不簡潔的杀赢,然后Kotlin可以這樣實(shí)現(xiàn):
val f1 = Forecast(Date(), 27.5f, "Shiny day")
val f2 = f1.copy(temperature = 30f)
而且Kotlin還支持映射對象的每一個(gè)屬性到一個(gè)變量中:
val f1 = Forecast(Date(), 27.5f, "Shiny day")
val (date, temperature, details) = f1
在Java中我們需要這樣去實(shí)現(xiàn):
Forecast f1 = new Forecast(Date(), 27.5f, "Shiny day");
Date date = f1.getDate();
float temperature = f1.getTemperature();
String details = f1.getDetails();
- 操作符重載
在Kotlin中,每個(gè)操作符都有對應(yīng)的操作方法湘纵,如:
操作符 | 對應(yīng)方法 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
正如這兩個(gè)表達(dá)式是一樣的意思:
var num:Int = 1
num = num.plus(1) // 與num = num + 1效果一樣
這也就提供了重載操作符(擴(kuò)展操作符)的方法:
fun main(args: Array<String>){
var num1 = Number(1, 1)
var num2 = Number(1, 1)
println((num1 + num2).toString())
}
operator fun Number.plus(other: Number):Number{
this.one = this.one + other.one
this.two = this.two + other.two
return this
}
data class Number(
var one: Int,
var two: Int
)
輸出結(jié)果為
Number(one=2, two=2)
- 強(qiáng)大的list
val list = listOf(1, 2, 3, 4, 5, 6)
list.any { it % 2 == 0 } // 如果至少有一個(gè)元素符合給出的判斷條件脂崔,則返回true
list.all { it % 2 == 0 } // 如果全部的元素符合給出的判斷條件,則返回true
list.count { it % 2 == 0 } // 返回符合給出判斷條件的元素總數(shù)
list.forEach { println(it) } // 遍歷所有元素梧喷,并執(zhí)行給定的操作
list.forEachIndexed { index, value -> println("position $index contains a $value") }
// 與 forEach 砌左,但是我們同時(shí)可以得到元素的index
list.max() // 返回最大的一項(xiàng),如果沒有則返回null
list.maxBy { -it } // 根據(jù)給定的函數(shù)返回最大的一項(xiàng)铺敌,如果沒有則返回null
list.min() // 返回最小的一項(xiàng)汇歹,如果沒有則返回null
list.minBy { -it } // 根據(jù)給定的函數(shù)返回最小的一項(xiàng),如果沒有則返回null
list.sumBy { it % 2 } // 返回所有每一項(xiàng)通過函數(shù)轉(zhuǎn)換之后的數(shù)據(jù)的總和
list.drop(4) // 返回包含去掉前n個(gè)元素的所有元素的列表
list.filter { it % 2 == 0 } // 過濾所有符合給定函數(shù)條件的元素
list.contains(2) // 如果指定元素可以在集合中找到适刀,則返回true
list.elementAt(1) // 返回給定index對應(yīng)的元素
list.first { it % 2 == 0 } // 返回符合給定函數(shù)條件的第一個(gè)元素
list.indexOf(4) // 返回指定元素的第一個(gè)index秤朗,如果不存在,則返回 -1
list.indexOfFirst { it % 2 == 0 }
// 返回第一個(gè)符合給定函數(shù)條件的元素的index笔喉,如果沒有符合則返回 -1
list.indexOfLast{ it % 2 == 0 } // 返回最后一個(gè)符合給定函數(shù)條件的元素的index取视,如果沒有符合則返回 -1
list.reverse() // 返回一個(gè)與指定list相反順序的list
list.sort() // 返回一個(gè)自然排序后的list
list..sortBy { it % 3 } // 返回一個(gè)根據(jù)指定函數(shù)排序后的list
......
- 流程控制
if表達(dá)式可實(shí)現(xiàn)賦值操作:
val z = if (condition) x else y
when表達(dá)式代替switch/case
when (x){
1 -> print("x == 1")
2 -> print("x == 2")
else -> {
print("I'm a block")
print("x is neither 1 nor 2")
}
}
val result = when (x) {
0, 1 -> "binary"
else -> "error"
}
when(view) {
is TextView -> view.setText("I'm a TextView")
is EditText -> toast("EditText value: ${view.getText()}")
is ViewGroup -> toast("Number of children: ${view.getChildCount()} ")
else -> view.visibility = View.GONE
}
val cost = when(x) {
in 1..10 -> "cheap"
in 10..100 -> "regular"
in 100..1000 -> "expensive"
in specialValues -> "special value!"
else -> "not rated"
}
val res = when{
x in 1..10 -> "cheap"
s.contains("hello") -> "it's a welcome!"
v is ViewGroup -> "child count: ${v.getChildCount()}"
else -> ""
}
Range 表達(dá)式使用一個(gè) .. 操作符,它是被定義實(shí)現(xiàn)了一個(gè) RangTo 方法常挚。Ranges 幫助我們使用很多富有創(chuàng)造性的方式去簡化我們的代碼作谭。比如我們可以把它:
if(i >= 0 && i <= 10) println(i)
轉(zhuǎn)化成:
if (i in 0..10) println(i)
3. 空安全
我們有時(shí)候的確需要去定義一個(gè)變量包不包含一個(gè)值。在Java中盡管注解和IDE在這方面幫了我們很多奄毡,但是我們?nèi)匀豢梢赃@么做:
Forecast forecast = null;
forecast.toString();
這個(gè)代碼可以被完美地編譯( 你可能會從IDE上得到一個(gè)警告) 折欠,然后正常地執(zhí)行,但是顯然它會拋一個(gè)NullPointerException 吼过。這個(gè)相當(dāng)不安全的锐秦。當(dāng)然,在Kotlin中盗忱,也可以有一個(gè)可null的對象(用酱床?標(biāo)記):
val a: Int? = null
然而一個(gè)可null類型,你在沒有進(jìn)行檢查之前你是不能直接使用它趟佃,這個(gè)代碼不能被編譯:
val a: Int? = null
a.toString() // 編譯失敗
a?.toString() // 編譯成功
if(a!=null){ // 編譯成功
a.toString()
}
4. 可擴(kuò)展方法
Kotlin允許我們給任何類添加函數(shù)扇谣。它比那些我們項(xiàng)目中典型的工具類更加具有可讀性。舉個(gè)例子闲昭,我們可以給fragment增加一個(gè)顯示toast的函數(shù):
fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(getActivity(), message, duration).show()
}
我們現(xiàn)在可以這么做:
fragment.toast("Hello world!")
注:擴(kuò)展函數(shù)并不是真正地修改了原來的類罐寨,它是以靜態(tài)導(dǎo)入的方式來實(shí)現(xiàn)的。擴(kuò)展函數(shù)可以被聲明在任何文件中序矩,因此有個(gè)通用的實(shí)踐是把一系列有關(guān)的函數(shù)放在一個(gè)新建的文件里鸯绿。
5. 函數(shù)式支持(lambda)
一個(gè)lambda表達(dá)式通過參數(shù)的形式被定義在箭頭的左邊( 被圓括號包圍) ,然后在箭頭的右邊返回結(jié)果值。在這個(gè)例子中楞慈,我們接收一個(gè)View幔烛,然后返回一個(gè)Unit( 沒有東西) 。所以根據(jù)這種思想囊蓝,我們可以把前面的代碼簡化成這樣:
view.setOnClickListener({ view -> toast("Click")})
這是非常棒的簡化饿悬!當(dāng)我們定義了一個(gè)方法,我們必須使用大括號包圍聚霜,然后在箭頭的左邊指定參數(shù)狡恬,在箭頭的右邊返回函數(shù)執(zhí)行的結(jié)果。如果左邊的參數(shù)沒有使用到蝎宇,我們甚至可以省略左邊的參數(shù):
view.setOnClickListener({ toast("Click") })
如果這個(gè)函數(shù)的最后一個(gè)參數(shù)是一個(gè)函數(shù)弟劲,我們可以把這個(gè)函數(shù)移動到圓括號外面:
view.setOnClickListener() { toast("Click") }
并且,最后姥芥,如果這個(gè)函數(shù)只有一個(gè)參數(shù)兔乞,我們可以省略這個(gè)圓括號:
view.setOnClickListener { toast("Click") }
比原始的Java代碼簡短了5倍多,并且更加容易理解它所做的事情凉唐,非常讓人影響深刻庸追。
Kotlin Android Extensions有什么用?
Kotlin Android Extensions是Kotlin團(tuán)隊(duì)研發(fā)的可以讓開發(fā)更簡單的插件台囱,Kotlin Android Extensions自動創(chuàng)建了屬性讓開發(fā)者直接訪問XML中的view淡溯,而不需要在開始使用之前明確地從布局中去找到這些views。
注:屬性的名字就是來自對應(yīng)view的id簿训,所以取id的時(shí)候要十分小心咱娶,因?yàn)樗鼈儗俏覀冾愔蟹浅V匾囊徊糠帧_@些屬性的類型也是來自XML中的强品,所以不需要去進(jìn)行額外的類型轉(zhuǎn)換膘侮。
在使用的時(shí)候需要我們需要使用import 語句,以 kotlin.android.synthetic 開頭的榛,然后加上需要綁定到Activity的布局XML的名字:
import kotlinx.android.synthetic.activity_main.*
然后就可以直接使用View對象了:
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView.setText("Hello, world!")
// Instead of findViewById(R.id.textView) as TextView
}
}
那它背后是怎么工作的琼了?其實(shí)正如Kotlin支持?jǐn)U展方法一樣,Kotlin也支持擴(kuò)展屬性困曙,通過獲取布局文件的控件id表伦,以其為名添加相應(yīng)控件作為該Activity的擴(kuò)展屬性谦去,并與布局中的控件通過findViewById等方式獲取控件實(shí)例(映射)慷丽。
該插件會代替任何屬性調(diào)用函數(shù),比如獲取到view鳄哭,并具有緩存功能要糊,以免每次屬性被調(diào)用都會去重新獲取這個(gè)view。需要注意的是這個(gè)緩存裝置只會在 Activity 或者 Fragment 中才有效妆丘。如果它是在一個(gè)擴(kuò)展函數(shù)中增加的锄俄,這個(gè)緩存就會被跳過局劲,因?yàn)樗梢员挥迷?Activity 中但是插件不能被修改,所以不需要再去增加一個(gè)緩存功能奶赠。
Anko鱼填!強(qiáng)大?方便毅戈?
Anko是JetBrains開發(fā)的一個(gè)強(qiáng)大的庫苹丸。它主要的目的是用來替代以前XML的方式來使用代碼生成UI布局。如:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
linearLayout {
button("Login") {
textSize = dip(16)
onclick{
clickButton()
}
}.lparams(width = wrapContent) {
horizontalMargin = dip(5)
topMargin = dip(10)
}
}
}
以上代碼相當(dāng)于:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="10dp"/>
</LinearLayout>
這種通過代碼生成UI布局的方式雖然方便苇经,但是赘理,這種由代碼生成布局的方式,不利于控制器(Controller)與視圖(View)的分離扇单,處理不當(dāng)可能會造成Activity代碼臃腫商模;而且這種方式不支持視圖的預(yù)覽,必須運(yùn)行之后才能看清楚效果蜘澜。從個(gè)人的UI繪制經(jīng)驗(yàn)出發(fā)施流,使用XML會更容易一些。
但是兼都,Anko還是有其優(yōu)勢的嫂沉,最重要的一點(diǎn)就是上方提及的擴(kuò)展函數(shù)(方法),擴(kuò)展函數(shù)數(shù)是指在一個(gè)類上增加一種新的行為扮碧,甚至我們沒有這個(gè)類代碼的訪問權(quán)限趟章。另外,Anko也支持?jǐn)U展屬性:
public var TextView.text: CharSequence
get() = getText()
set(v) = setText(v)
所以慎王,當(dāng)你在Fragment中看見activity的引用時(shí)不要驚訝蚓土,那其實(shí)就是使用getActivity()的擴(kuò)展屬性,再如當(dāng)給ListView設(shè)置適配器時(shí):
listview.adapter = mAdapter; // .adapter等同于set/getAdapter
還有赖淤,當(dāng)一個(gè)Listener有多個(gè)方法時(shí)蜀漆,Anko就顯得很方便了, 看下面的代碼(沒有使用Anko):
seekBar.setOnSeekBarChangeListener(object: OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
// Something
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
// Just an empty method
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
// Another empty method
}
})
使用了Anko之后:
seekBar {
onSeekBarChangeListener {
onProgressChanged { seekBar, progress, fromUser ->
// Something
}
}
}
我相信咱旱,當(dāng)越來越多的Android開發(fā)者認(rèn)識到Kotlin的魅力后确丢,Kotlin會真正成為Android開發(fā)的主流語言,以上的分享也只是簡單的認(rèn)識吐限,如果讀者有發(fā)現(xiàn)Kotlin更加誘人的魅力鲜侥,希望能夠多多分享,小牧在此先謝過啦(真誠臉)诸典。