這次所說的是Kotlin的變數和常量,主要會對以下內容做介紹:
- 1、變數基本定義
- 2、var和val的區別
- 3、智慧型別推斷
- 4、自定義屬性訪問器
- 5、var是可變的而val一定是不可變的嗎
一、Kotlin與Java中變數和常量 使用對比
- 1、在Java中定義一個變數和常量
public String name = "Mikyou";//定義變數
public final int age = 18;//final定義常量
複製程式碼
- 2、在Kotlin中定義一個變數和常量
var name: String = "Mikyou"
val age: Int = 18
複製程式碼
或者
var name = "Mikyou"
val age = 18
複製程式碼
總結: 由以上的對比可得出:
- 1、Kotlin定義一個變數和常量比Java更簡潔和靈活
- 2、Kotlin中擁有型別推斷的機制,當一個變數或者常量初始化的時候可以省略型別的宣告,它會根據初始化值的型別來作為變數的型別。
- 3、Kotlin宣告變數和常量都必須使用var(變數),val(常量)關鍵字開頭,然後再是名稱,最後才是型別(如果有初始化值型別可以直接省略)
- 4、Kotlin相比Java預設的訪問修飾符是public,而Java中是default
- 5、Java中一般都是以型別開頭然後再是名稱,並且型別是不可以省略的;這樣的操作在Kotlin中是不行的。因為在Kotlin中型別是可以省略的,也就是型別相對比較弱化的,所以Kotlin會把型別放在最後,一般含有初始化值就會把在後面的型別宣告省略。
二、Kotlin的變數和常量用法
var name: String = "Mikyou"
var address: String?//如果這個變數沒有初始化,那麼需要顯示宣告型別並且指明型別是否可null
address = "NanJing"
val age: Int = 18
複製程式碼
或者
var name = "Mikyou"
val age = 18
複製程式碼
- 1、變數和常量宣告必須以“var”和“val”關鍵字開頭。
- 2、 變數和常量的結構: (var 或者 val) 名稱 :(分割逗號) 變數型別 = 初始化值。
- 3、 智慧型別轉換(編譯器提示為smart cast),如果變數或常量含有初始值可以省略型別,編譯器會預設分析出將初始化值的型別作為變數和常量型別。
- 4、如果變數沒有初始化,那麼需要顯示宣告型別並且需要指明型別是否可null。
三、Kotlin的自定義屬性訪問器
在Kotlin中屬性是頭等特性,它習慣於用一個屬性去替代Java中的欄位和setter,getter方法。而Kotlin中的set、get訪問器相當於Java中的setter,getter方法。Kotlin有個新的特性就是可以去定義一個類中屬性的訪問器包括setter,getter訪問器。該特性十分適用於需要經過多個屬性邏輯計算得出的一個新的屬性。那麼邏輯計算的過程操作不需要像Java一樣開一個方法來實現。可以直接在屬性訪問器中進行邏輯運算。
- 1、自定義get屬性訪問器
在Java中實現:
public class Rectangle {
private float width;
private float height;
public Rectangle(float width, float height) {
this.width = width;
this.height = height;
}
public boolean isSquare() {//在java中實現一個方法進行相關的邏輯計算
return width == height;
}
}
複製程式碼
在Kotlin中實現:
class Rectangle(width: Float, height: Float) {
val isSquare: Boolean//在Kotlin中只需要一個屬性就能解決,重新定義了isSquare的getter屬性訪問器,訪問器中可以寫入具體邏輯程式碼。
get() {
return width == height
}
}
複製程式碼
- 2、自定義set屬性訪問器
在Java中實現Android中的一個自定義View中的setter方法。
public class RoundImageView extends ImageView {
...
public void setBorderColor(int color) {
mColor = color;
invalidate();
}
...
}
複製程式碼
在Kotlin中實現Android中的一個自定義View中的set屬性訪問器。
class RoundImageView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defAttrStyle: Int = 0)
: ImageView(context, attributeSet, defAttrStyle) {
var mBorderColor: Int
set(value) {//自定義set屬性訪問器
field = value
invalidate()
}
}
複製程式碼
四、Kotlin中的var和val區別
- 1、var(來自於variable)可變引用。並且被它修飾的變數的值是可以改變,具有可讀和可寫許可權,相當於Java中非final的變數。
- 2、val(來自於value)不可變引用。並且被它修飾的變數的值一般情況初始化一遍後期不能再次否則會丟擲編譯異常(這句話有待商榷,這個問題的討論和求證請看Kotlin中val不可變與可讀的討論),相當於Java中的final修飾的常量。
- 3、在Kotlin開發過程中儘可能多的使用val關鍵字宣告所有的Kotlin的變數,僅僅在一些特殊情況使用var,我們都知道在Kotlin中函式是頭等公民,並且加入很多函數語言程式設計內容,而使用不可變的引用的變數使得更加接近函數語言程式設計的風格。
- 4、需要注意的是val引用的本身是不可變的,但是它指向的物件可能是可變的(這個問題的討論和求證請檢視Kotlin中val不可變與可讀的討論)。
- 5、var關鍵字允許改變自己的值,但是它的型別卻是無法改變的。
五、Kotlin中val不可變與可讀的討論
由於Kotlin是一門新的語言,我們在學習的過程中經常習慣性的去記住一些所謂定理,而沒有去真正深究為什麼是這樣。比如拿今天的議題來說,相信很多的人都這樣認為(剛開始包括我自己)var修飾的變數是可變的,而val修飾的變數是不可變的。然後學完Kotlin的自定義屬性訪問器就會覺得是有問題的。然後去看一些國外的部落格,雖然有講述但是看完後更讓我懵逼的是val修飾的變數的值是可以變化的可讀的,並且底層的引用也是變化的。前面那句確實可以理解,後面一句還是保留意見。於是乎就開始寫demo認證。 引用國外部落格的一句原話"But can we say that val guarantees that underlying reference to the object is immutable? No…" 國外部落格源地址
- 1、val不可變與可讀的假設
假設一: 在Kotlin中的val修飾的變數不能說不可變的,只能說val修飾變數的許可權是可讀的。
假設二: 在Koltin中的val修飾的變數的引用是不可變的,但是指向的物件是可變的。
- 2、 val不可變與可讀的論證
論證假設一: 我們在Kotlin的開發過程中,一般是使用了val修飾的變數就不能再次被賦值了,否則就會丟擲編譯時的異常。但是不能再次被賦值不代表它是不可變的。因為Kotlin與Java不一樣的是多了個自定義屬性訪問器的特性。這個特性貌似就和val修飾的變數是不可變的矛盾了。而Java中不存在這個問題,如果使用了final修飾的變數,沒有所謂自定義訪問器概念。
fun main(args: Array<String>) {
val name = "Hello Kotlin"
name = "Hello Java"
}
複製程式碼
print error:
Error:(8, 5) Kotlin: Val cannot be reassigned
複製程式碼
定義get屬性訪問器例子
class RandomNum {
val num: Int
get() = Random().nextInt()
}
fun main(args: Array<String>) {
println("the num is ${RandomNum().num}")
}
複製程式碼
print result:
the num is -1411951962
the num is -1719429461
複製程式碼
總結: 由以上的例子可以說明假設一是成立的,在Kotlin中的val修飾的變數不能說是不可變的,而只能說僅僅具有可讀許可權。
論證假設二: 由論證一,我們知道Kotlin的val修飾的變數是可變的,那它的底層引用是否是可變的呢?國外一篇部落格說引用是可變的,真是這樣嗎?通過一個例子來說明。
User類:
package com.mikyou.kotlin.valtest
open class User() {
var name: String? = "test"
var age: Int = 18
var career: String? = "Student"
}
複製程式碼
Student類:
class Student() : User()
複製程式碼
Teacher類:
class Teacher() : User()
複製程式碼
Customer介面:
interface Customer {
val user: User//注意: 這裡是個val修飾的User例項引用
}
複製程式碼
VipCustomer實現類:
class VipCustomer : Customer {
override val user: User
get() {
// return Student().apply {
// name = "mikyou"
// age = 18
// career = "HighStudent"
// }
return Teacher().apply {
//看到這裡很多人肯定認為,底層引用也會發生改變,畢竟Student, Teacher是不同的物件了。但是事實是這樣的嗎?
name = "youkmi"
age = 28
career = "HighTeacher"
}
}
}
複製程式碼
測試:
fun main(args: Array<String>) = VipCustomer().user.run {
println("my name is $name, I'm $age years old, my career is $career, my unique hash code is ${hashCode()} ")
}
複製程式碼
print result:
my name is mikyou, I'm 18 years old, my career is HighStudent, my unique hash code is 666988784
//切換到Teacher
my name is youkmi, I'm 28 years old, my career is HighTeacher, my unique hash code is 666988784
複製程式碼
總結: 由以上的例子可以說明假設二是成立的,兩個不同的物件hashCode一樣說明,user的引用地址不變的,而變化的是引用指向的物件是可以變化的。
到這裡Kotlin入門基礎語法就結束了,下一篇將會繼續深入Kotlin相關內容
歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~