寫在前面
在剛開學的時候,買了一本《第一行代碼Android》,但一直在上課沒有機會看,這幾天剛好寫完了上一個項目,這段時間就對這本書進行了學習。在這本書中,由於谷歌大力推廣kotlin語言,將其作為android開發的首推語言,本書也遵循了谷歌的推廣,因此我就跟着書本學習了基本的Kotlin語法,當作記錄筆記了。
變量和函數
變量
在Koltin中的變量定義方式與java有很大不同,在kotlin中要定義一個變量,只允許在變量前聲明兩種關鍵字:val和var,val是value的簡寫,表示一個不可變的變量,對應java中的final關鍵字定義的變量。var是variable的簡寫,表示一個可變的變量,對應java中的非final關鍵字定義的變量。這時可能學過java的都會冒出一個疑惑,只靠這兩個怎么能知道具體的類型呢?Kotlin里有類型推導機制,如:
val a = 37
println("a = " + a)
這里可以看出,Kotlin不需要行尾分號了,這里我們將10賦值給了a,那么a就會被推導成整型變量。如果你把一個字符串賦值給a變量,那么a就會被自動推導為字符串變量。但需要注意的是,類型推導機制並不總是好用的,因此我們可以對一個變量進行顯式聲明,如:
val a:Int = 10
Kotlin里摒棄了java中的int,double,float等,全部都改用了對象數據類型,即首字母大寫的類型,如Int,Double,Float。此時我們嘗試對a變量進行一個擴大的操作:
a = a * 10
可以看到編譯器報錯了,因為val類型的變量無法被重新賦值,我們把a的類型變成var就可以了:
var a : Int = 10
函數
首先要注意的是,方法和函數其實說的都是一個東西,只是叫法不同而已。接下來我們看看如何在kotlin中定義一個函數:
fun largerNumber(num1:Int,num2:Int):Int{
return max(num1,num2)
}
fun表示function,即聲明函數,后面就是函數名了。函數名后的括號里為變量參數列表,參數的聲明格式為:"參數名:參數類型"。參數后面的冒號內容可以省略,表示返回值類型,這里我們聲明為返回一個Int型的數據。這里使用了一個max函數,表示取二者的最大值。
這里我們提一句語法糖,當返回值只有一句代碼時候,我們可以將大括號省略,用等號連接,且return也可以省略。如下:
fun largerNumber(num1:Int,num2:Int):Int = max(num1,num2)
而我們剛才提到了kotlin的類型推導機制,返回值肯定是一個Int值,因此我們不需要顯式聲明返回值類型了。就可以寫成:
fun largerNumber(num1:Int,num2:Int) = max(num1,num2)
邏輯控制
if條件語句
Kotlin的if和java幾乎沒有任何區別,但需要注意的是,kotlin中的if條件語句是有返回值的,返回值為if語句每個條件中的最后一行代碼的返回值。意味着我們可以這么寫:
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
這里又使用了上面提到的語法糖,因為這里放到大括號里也就是return 一句話,所以符合語法糖的規則。
when條件語句
kotlin中的when有點像java中的switch,但比起switch要強大的多。switch里面只能傳入整型和短於整型的變量作為條件,JDK1.7后還引入了字符串變量的支持。但如果我們傳入的類型不是這些,那么switch語句就無法使用了。同時,switch語句的最后需要加一個break,否則會順序執行每個case。而kotlin不僅解決了以上的痛點,還添加了很多好用的功能。
比如我們先看一個簡單的通過姓名輸出學生分數的功能:
fun getScore(name:String) = if(name =="Tom"){
86
}else if(name == "Jim"){
77
}else if(name == "Jack"){
95
}else if(name == "Lily"){
100
}else{
0
}
這里又再次使用了單行代碼函數的語法糖,但寫了這么多if和else顯得代碼十分冗余,這里我們改用when的寫法:
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
when語句可以傳入一個任意類型的變量,然后在when的結構體定義一系列的條件,格式為: 匹配值 -> {執行邏輯},當執行邏輯只有一行的時候,大括號就可以省略了。
除了精確匹配外,when還支持類型匹配,比如我們定義一個checkNumber()函數:
// when類型匹配
fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
在上面的代碼中,is相當於java中的instance of,而Number是kotlin中的一個抽象類,像Int,Float,Double等與數字有關的類都是他的子類,我們可以通過類型匹配來判斷是什么類型。比如:
val num = 10.0
checkNumber(num)
就會輸出Double類型了。
需要注意的是,kotlin中判斷字符串或者對象是否相等用 == 就可以了,不再需要調用equals方法。
循環語句
kotlin中有兩種循環,while和for循環,而while循環和java基本一模一樣,因此不再提了,而java中常用的for-i循環被kotlin舍棄,java中的另一種for-each循環則被大幅加強,變成了for-in循環。在學習循環前,要先學習區間的概念,這也是java種沒有的。如下代碼:
val range = 0..10
這段代碼的意思其實就是[0,10]的意思,有了區間,我們就可以用for-in來遍歷區間了:
for (i in range){
println(i)
}
但很多時候,我們一般不會用左右閉區間,而會使用左閉右開區間,kotlin也提供了until關鍵字來實現這個功能:
for (i in 0 until 10){
println(i)
}
這時生成的就是[0,10)區間了。
默認情況下,會在區間范圍內遞增1,我們可以用step來遞增更多,如:
for(i in 0 until 10 step 2){
println(i)
}
這時就是遞增2了,相當於i+=2的意思。
但需要注意的是,..和until都只能左邊小右邊大,也就是只能創建升序區間,而創建降序區間可以用downTo,如:
for (i in 10 downTo 1){
println(i)
}
downTo也可以使用step,不再多提了。
類與對象
基本用法
在面向對象語言中,類和對象是很重要的概念。kotlin自然也有這樣的概念。
我們先來通過IDE來定義一個類:
class Person() {
}
可以看到,kotlin也是使用class關鍵字來聲明類的,這點與java一致。我們定義幾個變量和函數:
class Person() {
var name = ""
var age = 0
fun eat(){
println(name + " is eating.He is " + age + " years old")
}
}
這里用var是因為我們要在創建對象后對其進行重新賦值,eat函數就不再解釋了,很簡單。
接下來我們把這個類實例化一下:
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
可以看到,與java不同的是,我們不再需要new關鍵字了,因為你調用一個類的構造方法只可能是為了對類進行實例化。接下來我們執行這段代碼,發現結果和我們想的一致。
繼承與構造函數
面向對象的一個重要特性就是繼承,我們先定義一個Student類:
class Student {
}
要讓Student類繼承Person類,我們要干兩件事:
-
使Person類可以被繼承。這話聽起來似乎很怪,java中所有類默認都是可以被繼承的,但Kotlin卻不是,在Kotlin中任何一個非抽象類默認都是不可以被繼承的,而抽象類如果不繼承就無任何意義,所以必須被繼承。想要讓一個類可以被繼承,其實很簡單,加上一個open關鍵字就可以了:
open class Person() { var name = "" var age = 0 fun eat(){ println(name + " is eating.He is " + age + " years old") } }
-
第二件事,自然就是讓Student類繼承Person類了,如下寫法:
class Student:Person(){ var sno = "" var grade = 0 }
繼承的寫法首先,不再需要extends關鍵字,加上冒號即可。之后如果仔細觀察,會發現多了一對括號。而要理解括號,就要解釋另一個概念——構造函數了。
在Kotlin中,有兩類構造函數,主構造函數與次構造函數,主構造函數只能有一個,且沒有函數體。直接定義在類名的后面。而次構造函數則是可以有任意多個,且有函數體。如下例子:
class Student(var sno:String,var grade:Int)
這種就是典型的主構造函數定義了。如果我們要在主構造函數里寫一些邏輯,就需要用到init結構體了:
init{
println("sno is " + name)
println("grade is " + age)
}
而根據繼承的特性,子類的構造函數必須調用父類的構造函數。這時我們要怎么調用父類的主構造函數呢?自然是在繼承上寫了:
因此,如果我們再看上面的最開始的定義就會更加明了了。其實就是調用了父類的無參構造方法,就算是無參的也不能省略括號。如果我們對Person函數改造一下,將Person函數改成有參的構造方法:
open class Person(val name:String,val age:Int) {
}
此時的Student類要繼承Person類,就必須實現他的主構造函數,即必須傳入name和age兩個值。我們的Student類就可以寫成這樣:
class Student(val sno:String,val grade :Int, name: String , age :Int):Person(name,age){
}
這里定義name和age時不能再聲明成val,因為在主構造函數中聲明的參數都會默認變成該類的字段。這會導致與同名的name和age發生沖突。因此不用再加任何關鍵字。
至於次構造函數,當一個類有主構造函數和次構造函數時,所有的次構造函數必須調用主構造函數。具體的例子就不再寫了。
接下來我們看一個特殊情況,當一個類沒有顯式定義主構造函數而且定義了次構造函數時,它就是沒有主構造函數的。此時繼承Person類就不再需要加上括號了,因為沒有主構造函數。
接口
kotlin是繼承自java的語言,自然很多方面都很像了。在接口方面,與java有很多相似。我們先定義一個接口:
interface Study {
fun readBooks()
fun doHomework(){
println("do homework default")
}
}
這里kotlin和java一樣,支持為接口定義默認函數體,使得實現類不需要實現這個方法。我們讓Student類實現這個接口:
class Student( name:String, age:Int) :Person(name,age),Study {
override fun readBooks() {
println(name + "is reading")
}
}
這里可以看到,kotlin中接口的關鍵字也是冒號,且只需要復寫方法即可。我們在main函數調用該方法即可。
這里還需要提一下,函數的可見性修飾符。見下表:
修飾符 | Java | Kotlin |
---|---|---|
public | 所有類可見 | 所有類可見(默認) |
private | 當前類可見 | 當前類可見 |
protected | 當前類、子類、同一包路徑下的類可見 | 當前類、子類可見 |
default | 同意包的路徑下的類可見(默認) | 無 |
internal | 無 | 同一模塊中的類可見 |
這里的模塊概念暫時不深究。
數據類與單例類
在寫web時候,model是很常用的一種類。我們在kotlin中如何定義一個數據類呢?如下:
data class CellPhone(val brand:String,val price:Double)
只需要加上data關鍵字,所有需要的方法就全部都實現了,如toString(),equals等等方法。
關於單例模式,也是我們很常見的一種設計模式。要使用單例類,如下:
object Singleton {
fun singletonTest(){
println("singletonTest is called")
}
}
只需要定義為object類即可。要調用里面的方法,如下:
Singleton.singletonTest()
即可。這樣看上去是靜態方法的調用,但其實kotlin在背后為我們自動創建了一個Singleton類的實例,並保證全局只會存在一個Singleton實例。
總結
這些只是最基本的知識,kotlin中更重要的特性例如Lambda編程,空指針檢查機制等等就不再這里提了,以我的水平無法總結的很好。日后學習的多了之后再加深吧。