昨天公衆號後臺收到一位小夥伴的留言詢問,他對於 Kotlin 爲什麼沒有 Java 的 final
關鍵字感到困惑,這應該是不少初學者都會遇到的問題,因此我就寫了這篇博文從更底層的角度來解析 Kotlin 聲明變量時用到的三個關鍵字:var
、val
和 const
。html
其實,Java 的 final
就等價於 Kotlin 的 val
, 雖然經過 javap 反編譯能夠看到二者的底層實現不同,可是從語義上講,它們二者的確是等價的。具體緣由,咱們來逐一分析。java
咱們知道,在 Kotlin 的世界中,class 已經再也不是惟一的一等公民,咱們能夠直接在代碼文件的最頂層(top-level)聲明類、函數和變量。git
class Address {
// class properties
var province = "zhejiang"
val city = "hangzhou"
}
fun prettify(address: Address): String {
// local variable
val district = "xihu"
return district + ',' + address.city + ',' + address.province
}
// top-level property
val author = "liangfei"
複製代碼
上例中的 Address
是一個類,prettify
是一個函數,author
是一個變量,它們都是一等公民,也就是說,函數和變量能夠單獨存在,不會像 Java 那樣依附於類。github
首先,var
和 val
可分爲三種類型:函數
var province = "zhejiang"
,它是 Address
類的一個屬性;val author = "liangfei"
,它是文件(module)的一個屬性;val district = "xihu"
,它是函數 prettify
的一個局部變量。類的屬性和頂層屬性都是屬性,因此能夠統一來看待,屬性自己不會存儲值,也就是說它不是一個字段(field),那它的值是哪裏來的呢?咱們先來看一下聲明一個屬性的完整語法:優化
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
複製代碼
能夠看出,一個屬性的聲明能夠分解爲五個部分:屬性名、屬性類型、initializer、getter、setter。spa
val author = "liangfei"
,根據 = "liangfei"
能夠得出它是一個 String
類型;以上只是聲明瞭一個屬性,若是咱們要賦值,它的值會存儲在哪裏呢?其實,編譯器還會自動爲屬性生成一個用於存儲值的字段(field),由於寫代碼時感知不到到它的存在,因此稱爲幕後字段(backing field)。具體能夠參考幕後字段,由於與本文關係不大,因此此處不作介紹。code
var
和 val
所聲明的屬性,其最本質的區別就是:val
不能有 setter,這就達到了 Java 中 final
的效果。htm
例如,上面 Kotlin 代碼中的 Address
類:對象
class Address {
var province = "zhejiang"
val city = "hangzhou"
}
複製代碼
它在 JVM 平臺上的實現是下面這樣的(經過 javap
命令查看):
public final class Address {
public final java.lang.String getProvince();
public final void setProvince(java.lang.String);
public final java.lang.String getCity();
public Address();
}
複製代碼
能夠看出,針對 var province
屬性,生成了 getProvince()
和 setProvince(java.lang.String)
兩個函數。可是 val city
只生成了一個 getCity()
函數。
對於局部變量來講,var
或者 val
都沒法生成 getter 或 setter,因此只會在編譯階段作檢查。
Classes in Kotlin can have properties. These can be declared as mutable, using the var keyword or read-only using the val keyword.
對於類的屬性來講:var
表示可變(mutable),val
表示只讀(read-only)。對於頂層屬性來講也是同樣的。
var
表示可變,val
表示只讀,而不是不可變(immutable)。咱們已經知道了 val
屬性只有 getter,可是這並不能保證它的值是不可變的。例如,下面的代碼:
class Person {
var name = "liangfei"
var age = 30
val nickname: String
get() {
return if (age > 30) "laoliang" else "xiaoliang"
}
fun grow() {
age += 1
}
}
複製代碼
屬性 nickname
的值並不是不可變,當調用 grow()
方法時,它的值會從 "laoliang"
變爲 "xiaoliang"
,可是沒法直接給 nickname
賦值,也就是說,它不能位於賦值運算的左側,只能位於右側,這就說明了爲何它是隻讀(read-only),而不是不可變(immutable)。
其實,Kotlin 有專門的語法來定義可變和不可變的變量,後面會專門寫一篇博問來分析,這裏再也不深刻。
咱們知道,Java 中可使用 static final
來定義常量,這個常量會存放於全局常量區,這樣編譯器會針對這些變量作一些優化,例如,有三個字符串常量,他們的值是同樣的,那麼就可讓這個三個變量指向同一塊空間。咱們還知道,局部變量沒法聲明爲 static final
,由於局部變量會存放在棧區,它會隨着調用的結束而銷燬。
Kotlin 引入一個新的關鍵字 const
來定義常量,可是這個常量跟 Java 的 static final
是有所區別的,若是它的值沒法在編譯時肯定,則編譯不過,所以 const
所定義的常量叫編譯時常量。
首先,const
沒法定義局部變量,除了局部變量位於棧區這個緣由以外,還由於局部變量的值沒法在編譯期間肯定,所以,const
只能修飾屬性(類屬性、頂層屬性)。
由於 const
變量的值必須在編譯期間肯定下來,因此它的類型只能是 String
或基本類型,而且不能有自定義的 getter。
因此,編譯時常量須要知足以下條件:
object
的成員(object
也是 Kotlin 的一個新特性,具體可參考對象聲明)。最後,總結一下:
var
、val
聲明的變量分爲三種類型:頂層屬性、類屬性和局部變量;var
屬性能夠生成 getter 和 setter,是可變的(mutable),val
屬性只有 getter,是隻讀的(read-only,注意不是 immutable);val
等價於 Java 的 final
,在編譯時作檢查。const
只能修飾沒有自定義 getter 的 val
屬性,並且它的值必須在編譯時肯定。