google
宣布Kotlin
作為andorid
一級(jí)開發(fā)語言有一段時(shí)間了。在這段時(shí)間,我也在新的模塊上嘗試使用了kotlin
進(jìn)行開發(fā),經(jīng)過這一段時(shí)間的開發(fā),我覺得在開發(fā)中使用kotlin
是個(gè)很棒的選擇。
使用Kotlin
的很容易,只需要進(jìn)行幾步簡單的設(shè)置
-
android studio
安裝下面兩個(gè)插件,其中Parcelable Code Generator
不是必要的,主要用于序列化Parcelable
,所示最好也安裝一下
image.png - 在
build.grade
文件中添加依賴compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
- 在
build.grade
文件中應(yīng)用插件
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
通過以上的幾步,就可以開始我們的kotlin
之旅了
1. 變量
在kotlin
中變量使用var、val
來表示,var
表示可變變量,val
表示不可變變量(相當(dāng)于java
當(dāng)中的final
)。如下所示
var name: String="ivy"
val age=12
kotlin
定義變量時(shí)可以不指定變量類型,kotlin
會(huì)根據(jù)你的賦值進(jìn)行判斷該變量的類型,如例子所示會(huì)自動(dòng)把age
設(shè)置為Int
類型,這里需要注意的是如果需要把變量賦值為Null
(var name:String?="ivy"
),需要加上?
號(hào)才表示該變量可以為Null
2. 函數(shù)
kotlin
方法的使用和java
的類似,當(dāng)沒有返回值的時(shí)候可以不寫,或者寫Unit
fun add(one: Int , two: Int) : Int{
return one+two
}
同時(shí),函數(shù)還可以為每個(gè)參數(shù)設(shè)置默認(rèn)值,這樣就可以避免多參數(shù)時(shí)定義多個(gè)方法,如下所示。
fun add(one:Int , two: Int ,three: Int=0){
//當(dāng)參時(shí)沒有設(shè)置three的值時(shí),three默認(rèn)為0
}
這里有一個(gè)比較重要的概念。在kotlin
當(dāng)中函數(shù)是一級(jí)公民,即函數(shù)也是可以作為變量、返回值來使用的,如下所示
val add=fun (a: Int,b: Int){ println(a+b) }
add(1,2)
可能有的小伙伴就要說,這有什么卵用,我直接寫個(gè)方法,然后調(diào)用方法不就好了。是,如果只是簡單的調(diào)用是可以,那如果把函數(shù)作為方法的參數(shù),這在java
就無法做到了,而把函數(shù)作為參數(shù)能給我們帶來很大的方便,代碼看起來也更有邏輯性。如下所示
fun judgeHasMoney(doHappyThing: ()->Unit){
if (User.hasMoney()){
println("no money")
}else{
doHappyThing()
}
}
judgeHasMoney(BuyCompany())
judgeHasMoney(BuyPhone())
這就可以非常方便對(duì)能否能購買東西進(jìn)行判斷,代碼少了,看上去也更有邏輯性。還有很多函數(shù)作為一級(jí)公民好處的實(shí)例,這里就不一一演示了,for
循環(huán)、List
、Map
之類的太量操作符也是利用了這個(gè)特性進(jìn)行實(shí)現(xiàn)。
3. 類的定義
class Boy(var name: String) : Person, interface{
init {
}
constructor(name:String,age: Int) : this(name) {
}
}
如例子所示,這就是kotlin
中類的定義。無論是接口還是父類都是寫在冒號(hào)后面,用逗號(hào)分割,不再使用extends
和implement
。在kotlin
中,分主構(gòu)造函數(shù)和次構(gòu)造函數(shù),主構(gòu)造函數(shù)就是類名Boy
括號(hào)后面參數(shù)(如果想在類中直接使用構(gòu)造函數(shù)的參數(shù),需要加上val
或者var
),次構(gòu)造函數(shù)必須以constructor
開頭,并且必須繼承調(diào)用主構(gòu)造函數(shù),所有的構(gòu)造函數(shù)初始化后都會(huì)調(diào)用 Init{}
方法,所以可以在Init()
當(dāng)中進(jìn)行初始化操作。
在kotlin
類中還有一些需要注意的:
- 1 在
Kotlin
中,所有的方法都是不開放的(即子類無法重寫),如果子類要重寫父類的方法,需要在父類方法前面加上open
- 2 如果繼承了兩個(gè)類中含有共同的方法,可以通過
super<類名>.方法名
進(jìn)行調(diào)用
class SuperMan(): Person(), Boy {
override fun run(){
super<Person>.run()
}
}
- 3 生成一個(gè)對(duì)象很簡單,直接
var girl=Girl()
就可以,不再需要new
一個(gè)妹子了
4. 空安全
在android
開發(fā)中,空指針異常是最讓我們頭痛的也是導(dǎo)致應(yīng)用崩潰率最高的問題。我們之前已經(jīng)提到過,在kotlin
中,在定義變量的時(shí)候就需要指定變量是否允許為空,這在一定程度上幫助我們減少空指針異常的可能性。但是還是不能完成避免空指針的出現(xiàn),因?yàn)橛行┳兞烤褪强煽湛刹粸榭眨?dāng)我們調(diào)用時(shí),就有可能因?yàn)闆]有做出判斷而出現(xiàn)空指針異常。kotlin
也考慮到這個(gè)問題,所以在調(diào)用可為空的變量時(shí),kotlin
會(huì)要求我們加上?
號(hào),如下所示:
var school?=Class()
school?.grade?.class?student?.size()
如果我們想查找一個(gè)班的學(xué)生的人數(shù),可以使用以上的方法,就可以獲取到學(xué)生的人數(shù),當(dāng)中的任何一個(gè)變量為空,后面的都不會(huì)繼續(xù)執(zhí)行,這個(gè)鏈?zhǔn)秸{(diào)用就會(huì)返回null
,避免任何一個(gè)階段為空出現(xiàn)NPE
的問題。
5. List、Map
的使用
在開發(fā)中,List
和Map
是我們最經(jīng)常用到的類了。在kotlin
中,List
和Map
也分為可變和不可變
var a = listOf(4)
var b = mutableListOf<String>()
var c = mapOf(Pair("3","4"))
var d = mutableMapOf<String,String>()
不可變的概念即和數(shù)組類似,只能夠改變每個(gè)位置的值,不能減少或者增加size
遍歷List
有三種比較常用的方法
for(position in list.indices){
//每一個(gè)位置遍歷
}
for(str in list){
//每一個(gè)對(duì)象遍歷
}
for((position,str) in list.withIndex()){
//每一個(gè)位置和對(duì)象遍歷
}
遍歷Map
比較常用的方法
for((key,value) in map){
}
kotlin
的遍歷方法相對(duì)于java
來說,還是很方便和實(shí)用的,但是!?。∵@不是最好用的??!最方便的是kotlin
加入了一系列的操作符,使用Rx的小伙伴應(yīng)該知道操作符有多棒。下面我就來演示一個(gè)例子,查找一個(gè)班里面年齡超過15歲的女生名字
java
:看我的
for(student in students){
if(student.getSex()=="girl"&&student.getAge>15){
System.out.println(student.getName())
}
}
kotlin
:小樣,看我的
students.filter{ it.sex=="girl" }
.filter{ it.age>15 }
.forEach{ print(student.name") }
是不是整個(gè)邏輯一目了然。但是,有些喜歡挑事的小伙伴可能就會(huì)說我覺得上面那個(gè)沒有很方便啊。我就喜歡上面那個(gè)。
好,那我就再給你舉兩個(gè)例子,你寫java
代碼對(duì)比一下
- 1 15歲以上的女生是否有單身的!??!
students.filter { it.sex=="girl" }
.filter { it.age >15 }
.any{ !it.hasBoyFriend }
- 2 把15歲以上的女生年齡排序一下,打印出名字
students.filter { it.sex=="girl" }
.filter { it.age >15 }
.sortedBy { it.age }
.forEach { print(it.name) }
還有其它更棒,更好用的操作符,大家可以查看源碼。操作符可以大大提高開發(fā)速度,我在這里就不一一展示了(Map
一樣有各種好用的操作符)
6. 流程控制
- 1
for
語句的使用和java
的類似,也包含了很多的操作符(和List
類似)
for(i in 0 .. 100){
//0到100
}
for(i in 0 until 100){
//0到99
}
for (i in 100 downTo 0){
//100到0
}
- 2 在
java
中,when
語句中的case
判斷只能是int
,后來也支持了String
,但是支持的類型還是很少。而且,只支持相同的類型,這樣就導(dǎo)致我們?cè)陂_發(fā)中遇到復(fù)雜業(yè)務(wù)的時(shí)候,往往還需要在case
中編寫if
語句,這就給我?guī)砹撕艽蟮穆闊掖a也比較混亂。但是kotlin
的when
語句很好的解決了這個(gè)問題。當(dāng)when(x)
有參數(shù)時(shí),可以在參數(shù)中添加參數(shù)所屬類型的表達(dá)式。當(dāng)when
不帶參數(shù)時(shí),可以添加各種表達(dá)式和方法,而不只是常量。
when (x) {
1 -> print("x == 1")
2,3 -> print("x == 2 or 3")
in 10..20 -> print("x is in the range")
else -> { // 注意這個(gè)塊
print("x is shen me gui")
}
}
var x:Int=0
when {
x==1 -> print("x == 1")
x in 10..20 -> print("x is in the range")
isBigNum(x) -> print("big")
else -> { // 注意這個(gè)塊
print("x is shen me gui")
}
}
- 3 在循環(huán)中,
java
經(jīng)常會(huì)用到break
跳出循環(huán),那kotlin
是怎么實(shí)現(xiàn)的呢?請(qǐng)看例子,可以通過直接標(biāo)簽的形式,回到指定位置,和java
類似
tag@ for (i in 0..100) {
for (j in 0..100) {
if (i==j)
break@tag
}
}
7. 數(shù)據(jù)類
相信java
的小伙伴從剛開始學(xué)java
的時(shí)候就知道建立對(duì)象先寫個(gè)javabean
,要寫get、set
方法,比較的時(shí)候要重寫equal
等方法,copy
對(duì)象的時(shí)候要實(shí)現(xiàn)Cloneable
接口。非常的麻煩,而且容易出錯(cuò)。kotlin
給出了一種簡單的方法
data class person(var age: int ,var name: String)
編繹器會(huì)自動(dòng)使該Bean
擁有以下特性:
- 1
get/set
方法,當(dāng)var
改為val
時(shí),則沒有set
方法 - 2
equals()
/hashCode()
- 3
toString()
方法,格式person(age=18, name=ivy)
- 4
copy()
拷貝功能
8. 靜態(tài)變量、靜態(tài)方法
在kotlin
中,沒有靜態(tài)方法的說法。都是通過伴生對(duì)象companion object
來實(shí)現(xiàn)。使用靜態(tài)變量可以通過
const val staticName: String ="123"
來實(shí)現(xiàn)(必須寫在類的頂部),一般用于無法確定歸屬的全局變量。但是最好使用伴生對(duì)象,對(duì)靜態(tài)變量歸類到所屬的類中。使用方法如下
class TestActivity{
companion object{
var username: String="ivy"
fun startActivity(context: Context):Unit{
val intent=Intent(context,KotlinHomeActivity::class.java)
context.startActivity(intent)
}
}
}
//調(diào)用
TestActivity.companion.startActivity(context)
伴生對(duì)象并不是真正意義上的靜態(tài)變量,本質(zhì)也是一個(gè)對(duì)象
kotlin
的單例寫法如下:
companion object{
fun getUser() : String{
return UserManager.user
}
private object UserManager{
val user=User()
}
}
9. 泛型
kotlin
的泛型和java
的類似,有以下幾點(diǎn)不同:
- 1 可以通過
T::class
來獲取到T
的具體類型 - 2
kotlin
不支持通配符,即class person(? extends T)
- 3 聲明處型變,即如下所示,只控制一個(gè)類中只有返回值才有可能需要校驗(yàn)
T
泛型,輸入是不會(huì)出現(xiàn)T
泛型這種情況的(同時(shí)也有<in T>
表示輸入)
abstract class Person<out T> {
abstract fun getSomeThing(): T
}
那么這樣有什么好處呢?看下面的代碼,這在java
中不可行,是禁止這樣操作的。但是實(shí)際上,這樣是極為安全的
fun demo(strs: Person<String>) {
val objects: Person<Any> = strs
// ...
}
10. 代理
在kotlin
當(dāng)中可以對(duì)類和屬性進(jìn)行代理
- 1 代理類(代理模式)
interface Person{
fun run()
}
class SuperMan() : Person{
override fun run() { print("i can fly") }
}
class Delegate(person: Person) : Person by person
fun main(args: Array<String>) {
val superMan = SuperMan()
Delegate(b).run() //打印 i can fly"
}
- 2 代理屬性,這是一個(gè)超級(jí)棒的功能,這里面和很多功能,這里我就介紹兩種
- 代理屬性
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return 'someThing"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("value'")
}
}
這個(gè)這可以在屬性獲取值和設(shè)置值的時(shí)候進(jìn)行代理攔截,這個(gè)功能非常強(qiáng)大,例子如下 (代碼來源《Kotlin for Android Developer》
)
class Preference<T>(val context: Context, val name: String, val default: T, val prefName: String = "default") : ReadWriteProperty<Any?, T> {
val prefs by lazy { context.getSharedPreferences(prefName, Context.MODE_PRIVATE) }
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return findPreference(name, default)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putPreference(name, value)
}
private fun <U> findPreference(name: String, default: U): U = with(prefs) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> getString(name, default)
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}
res as U
}
private fun <U> putPreference(name: String, value: U) = with(prefs.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalArgumentException("This type can be saved into Preferences")
}.apply()
}
}
當(dāng)使用get
方法時(shí),會(huì)自動(dòng)從sharePreference
當(dāng)中取,當(dāng)給變量設(shè)值的時(shí)候會(huì)自動(dòng)給把值存儲(chǔ)更新到sharePreference
當(dāng)中,可以像使用正常變量一樣使用sharePreference
,使用方法如下:
class TestActivity : Activity(){
var version: Int by Preference(this, "version", 1.0.0)
}
- 可觀察屬性(
observable properties
)
這個(gè)的作用,是當(dāng)一個(gè)變量的值改變時(shí),會(huì)進(jìn)行通知,并告知舊值和新值。這個(gè)在開發(fā)中就非常實(shí)用了,比如定位功能,可以判斷定位的值是否發(fā)生變化,設(shè)置了定位信息,從而允許接下來的邏輯。
var locationCity: String by Delegates.observable("<no location>") {
prop, old, new ->
println("$old -> $new")
}
在開發(fā)中我們通常會(huì)通過 一個(gè)helper
工具類來實(shí)現(xiàn)對(duì)某個(gè)對(duì)象的屬性值來進(jìn)行控制。比如,當(dāng)設(shè)置的年齡小于15歲的時(shí)候不允許更改,在這里使用Delegates.observable()
就很方便了,而且邏輯看起來更加清晰明了。
也可以通過Delegates.vetoable()
來控制變量的值是否可以修改(使用方法和 Delegates.observable()
一樣)
- 3 屬性延遲
在開發(fā)中,我們經(jīng)常會(huì)做一件事件,當(dāng)一個(gè)對(duì)象需要使用時(shí)才初始化,不使用不初始化。這在kotlin
中就很方便實(shí)現(xiàn),當(dāng)?shù)谝淮问褂脮r(shí)才初始化,隨后直接使用初始化后的值(該方法默認(rèn)是同步的,如果不需要同步可以加上LazyThreadSafetyMode.NONE
)
val user:Student by lazy {
Student("girl",18,true)
}
11. 擴(kuò)展
kotlin
的擴(kuò)展是我最喜歡的一個(gè)新功能了,之前開發(fā)ios
的時(shí)候就喜歡得不得了,現(xiàn)在在Android
中終于可以使用了。在java
世界中每個(gè)開發(fā)者都有一堆自己的工具類,如StringUtils
、ViewUtils
、ToastUtils
等等。但是這樣會(huì)有一個(gè)問題,比如View
的一個(gè)工具類,誰知道你的工具類名叫什么?就算知道,也很麻煩。比如設(shè)置paddlingLeft
的一般套路
ViewUtils.setPaddingLeft(view,paddingLeft)
但是通過擴(kuò)展,就完成清晰明了,和調(diào)系統(tǒng)方法沒有任務(wù)區(qū)別,首先編寫一個(gè)擴(kuò)展
fun View.setPaddingLeft(paddingLeftNew: Int){
setPadding(paddingLeftNew,paddingTop,paddingRight,paddingBottom)
}
//調(diào)用:
view.paddingLeft=12
是不是很贊?。?!擴(kuò)展功能非常強(qiáng)大,我們可以把各種工具類進(jìn)行簡化。比如Toast
fun Any.toast(text: String){
Toast.makeText(applicationContext,text,Toast.LENGTH_SHORT).show()
}
當(dāng)然擴(kuò)展不止于此,腦洞有多大,擴(kuò)展就有多強(qiáng)!
12. 和ButterKnife say goodbye
每個(gè)Android
的開發(fā)者曾經(jīng)都為findViewById()
而煩惱不已,直到后來ButterKnife
的出現(xiàn)。但是ButterKnife
是不是就完美了,并不是!當(dāng)布局復(fù)雜的時(shí)候,activity
里面前面一堆View
的變量。那有沒有辦法去掉這一堆變量,有!
import kotlinx.android.synthetic.main.activity_kotlin.*
class Activity{
btnCommint.text="xxx"
}
activity_kotlin.xml
<Button
android:id="@+id/btnCommit"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="i am kotlin button"/>
只需要把import
布局文件,就可以直接把Id
當(dāng)成變量來使用。輕輕松松和ButterKnife say goodbye
。
13. 一些小細(xì)節(jié)
- 1
Class
對(duì)象的獲取
View::class.java //在kotlin中獲取class對(duì)象傳遞到j(luò)ava代碼中
View::javaClass //在kotlin中獲取class對(duì)象傳遞到kotlin代碼中
- 2
this
的使用
class KotlinActivity{
fun isBigNum(num: Int){
}
inner class Person{
open fun run(){
this@Person.run()
this@KotlinActivity.isBigNum(3)
}
}
}
- 3
SmartCast
(類型轉(zhuǎn)換)
java:
TextView view=(TextView)findViewById(R.id.tv)
kotlin:
var view=findViewById(R.id.tv) as TextView
- 4 字符串拼接
java:
System.out.println("username:"+user.getUsername()+"--age:"+age)
kotlin:
print("username:${user.username}--age:$age)
- 5 三目運(yùn)算符
在kotlin
當(dāng)中沒有三目運(yùn)算符,實(shí)現(xiàn)三目運(yùn)算符的方法:
var maxValue= if(a>b) a else b