在任何一種程式語言都有資料型別介紹,而此篇我們將來了解 Kotlin 在資料型別上的特性、操作、轉換等內容。
在 Kotlin 官方文件中有提到:
In Kotlin, everything is an object in the sense that we can call member functions and properties on any variable.
上述內容得知,Kotlin 的任何東西都是一個物件,可以存取任何對象的相關方法與屬性,不像 Java 有區分原始型別(Primitive Type)與參考型態(Reference Types),在開發上有時候甚至需要做轉換才可使用。而 Kotlin 在宣告變數時使用的是靜態類型系統(static type system),即編輯器會按照變數類型辨識程式碼,判斷是否有存在類型與數值不符合的狀況發生,若有出現,編輯器會立即指出,例如下圖提示訊息:

Kotlin 在變數宣告時主要會使用到兩種關鍵字 val 和 var :
val 用於唯讀變數,一旦給值就無法再修改var 用於需要重新修改數值的情況fun main() {
val readOnlyVariable = "鐵人賽第十二屆" // 宣告一個唯讀變數
var playerName = "選手一號" // 宣告一個可重新修改數值的變數
playerName = "選手二號" // 重新賦予新數值
}
Kotlin 官方這邊也有建議開發者在開發上建議優先使用 val,當遇到需要修改數值時再轉為 var 即可,若使用 var 宣告變數,開發者若沒有在程式中修改過,Intellij 編輯器也會提示建議改為 val,如下圖:
還記得嗎?我們在上一章有提到 Kotlin 有一個優勢是可以避免以前 Java 開發中常見的 NullPointerException 情況發生,主要原因是因為 Kotlin 預設宣告都只能是非 null 型態,例如以下範例,當我們想要進行指派 null 值給 String 時會發生編譯錯誤狀況:

這樣的錯誤檢查就能夠避免開發者經常會有出現錯誤的問題,而如果在開發情境上確實有必要使用 null 值,則可以將變數定義為 nullable 狀態,即在變數的型態定義上加上 ? 即可,如下範例:
fun main() {
var test: String? = "鐵人賽"
test = null
println(test) // 印出 null
}
在介紹基本型別前,先介紹 Kotlin 在變數上有個特色是型別判斷處理,可對於已指派預設值的宣告變數自動定義型別,允許開發者省略型別定義,以下我們嘗試宣告一個變數,並輸出該變數的型別來看 Kotlin 是否有自動幫我們進行型別宣告,如下範例:
此範例先宣告變數 name 為「鐵人賽」,再利用「::class.simpleName」印出變數型別結果為 String
fun main() {
val name = "鐵人賽"
println(name::class.simpleName) // 印出 String -> 代表 Kotlin 自動幫我們定義型態
}
Kotlin 在資料型別與 Java 非常相似,只差在變數型態必須使用首字大寫,型別分別如下:
數值型別 Numbers (種類可依長度區分)
數值變數在操作上可直接宣告型態或是透過型別判斷進行操作:
fun main() {
val byte: Byte = 1
val short: Short = 2
val int: Int = 3
val long: Long = 4L
val float: Float = 5f
val double: Double = 6.0
println("Byte => $byte")
println("Short => $short")
println("Int => $int")
println("Long => $long")
println("Float => $float")
println("Double => $double")
}
前面有提到 Kotlin 的一切都是物件,在以前 Java 變數型態有分為基本型別(Primitive type)與參考型別(Reference type),即 int 與 Integer 的差別,而在 J2SE 5.0 時有提供自動裝箱(autoboxing)與拆箱(unboxing)來進行包裹基本型態,但在 Kotlin 中,只存在數值的裝箱,不存在拆箱,因為 Kotlin 是沒有存在基本資料型態的,下面將示範如何進行裝箱操作:
此範例操作須搭配上面提到的概念-空值型態達成裝箱效果,會發現裝箱前與裝箱後的數值都一樣
fun main() {
val number: Int = 913
val numberInBox: Int? = number
println("裝箱前數值: $number , 裝箱後數值: $numberInBox")
// 裝箱前數值: 913 , 裝箱後數值: 913
}
上面範例我們會發現兩個數值印出來雖然是相等的,但其實在 Kotlin 判斷數值是否相等有兩種比較方式(== 與 ===),== 是判斷數值是否相等, === 則是判斷兩個數值在記憶體位置是否相等,而其實 Kotlin 在變數裝箱操作時,記憶體位置會根據其資料型別的數值範圍進行定義,我們可以利用下面範例進行示範:
我們會發現當 a 變數為 127 時,判斷兩個裝箱變數會為 true,因為 Int 型態定義數值範圍為 -128 ~ 127,當 b 變數超過 127 數值時,Kotlin 在記憶體分配上會有不同位置狀況發生。
fun main() {
val a: Int = 127
val boxedA: Int? = a
val anotherBoxedA: Int? = a
val b: Int = 128
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedA === anotherBoxedA) // true
println(boxedB === anotherBoxedB) // false
}
Kotlin 在數值轉換上有分顯性轉換與隱性轉換,隱性轉換即 Kotlin 會自動幫我們進行轉換,但若兩個數值為不同型態時,會自動以定義數值範圍較大的型態為轉換後的最終型態,例如以下範例:
此範例為兩數相加,999為 Long 型態,1為 Int 型態,兩數相加後的結果 number 為 Long 型態
fun main() {
val number = 999L + 1
println(number::class.simpleName) // 印出資料型別為 Long
}
而為了避免隱性轉換時自動選擇型態問題,我們在開發上可使用顯性轉換方式,即下面範例:
fun main() {
val number: Int = 65
println(number.toByte()) // 印出 65
println(number.toShort()) // 印出 65
println(number.toLong()) // 印出 65
println(number.toFloat()) // 印出 65.0
println(number.toDouble()) // 印出 65.0
println(number.toChar()) // 印出 A
println(number.toString()) // 印出 65
}
字元型別 Char
Char 表示字元類型,字元變數必須使用單引號(‘’)表示,在轉換上可利用顯性轉換為數字型態,如以下範例:
fun main() {
val char: Char = 'A'
println(char.toInt()) // 印出 65
}
字串型別 String
String 表示字串類型,在輸出時可使用字串模板表示式處理字串組成,再進行輸出,如下範例:
fun main() {
val username: String = "Devin"
println("第十二屆鐵人賽 參加者 $username") // 印出「第十二屆鐵人賽 參加者 Devin」
}
布林型別 Boolean
Boolean 表示為布林類型,其值有 true 與 false
fun main() {
val isFalse: Boolean = false
val isTrue: Boolean = true
println(isFalse && isTrue) // 印出「false」
}
陣列型別 Array
Kotlin 的 Array 型別在宣告上是以 Array<T> 表示,我們可以到 Kotlin 的 Array 型態定義查看,會發現原始型態已經幫我們定義 get、set、size 與 iterator 方法:

故我們在 Array 操作上可以如下範例進行操作:
fun main() {
val data: Array<Int> = arrayOf(1,2,3,4,5) // 宣告Array並賦予 1-5 數值
data.forEach { println(it) } // 利用 forEach 分別印出數值
}
在前述有提到唯讀變數 val 不允許重新設定數值,但其實 val 是在程式執行階段(Run time)才進行賦值(Assign Value)動作,而我們若要限制程式在編輯階段(Compile time)就進行賦值動作,應使用 const 關鍵字搭配 val 進行變數宣告,我們可用一個範例來說明 const 與 val 的差異:

透過上面範例我們會發現兩件事:
normalVariable 可利用 getRandomValue() 隨機取得 1 - 6 數值,表示程式是先在執行階段利用 getRandomValue() 方法取得數值後,才對 normalVariable 進行賦值constVariableFromGetValue 賦予 getRandomValue 方法時,會出現 const val 只能接受常數(constant value)is 運算子
is運算子可檢查物件或變數是否屬於某資料型別,如Int、String等,類似於Java的 instanceof
fun main() {
val data = "abc"
println(data is String); // 印出 true
println(data is Any); // 印出 true
}
as 運算子進行型別轉換
as運算子用於型別轉換,若要轉換的數值與指定型別相容,轉換就會成功;如果型別不相容,使用 as? 運算子就會返回值null,如下範例:
fun main() {
val x: Int = 2
val y: Int = x as Int
val z: String? = y as? String
println(y) // 印出 2
println(z) // 印出 null
}
除了上述基本型別以外,Kotlin 還有一些特殊型別運用於物件或函數上,這邊會先進行簡單介紹,會在後續章節介紹時會再深入說明:
根據 Kotlin 官方文件所述:
The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
在此篇文章一開始介紹說明,Kotlin的一切都是物件,而每個物件其實都是繼承 Any 這個型別,此型別相當於Java的 Object 型別,而此型別也可再細分為 Any 與 Any?,Any屬於非空型別的根物件,Any?屬於可空型別的根物件。
在 Java 中,當我們所設計的 function 不需回傳值時,我們會使用到 void 型別,而在 Kotlin 可使用 Unit 型別代替,而且若我們不特地為 function 設定回傳型態時,Kotlin 會自動幫我們預設型態為 Unit 型別,會返回 Unit 型別,例如以下範例。
fun main() {
val username = getUserName()
println(username::class.simpleName) // 印出 Unit 型別
}
fun getUserName() {
}
Nothing 型別其實類似於 Unit,Nothing 型別也是不返回任何東西,但差別在於 Nothing 型別意味著此函數不可能成功執行完成,只會拋出異常或是再也回不去函數呼叫的地方。
而 Nothing? 型別則會有一個使用情境,在 Java 中,void不能是變數的型別。也不能被當數值列印輸出。但是,在Java中有個包裝類Void是 void 的自動裝箱型別,如果我們想讓 function 返回型別永遠是 null 的話,可以把返回型別置為這個大寫的V的Void型別,而 Void 即對應 Kotlin 中的 Nothing? 型別。
範例(1) 使用 Nothing 型別
fun main() {
getUserName() // 使用 Nothing 型別
}
fun getUserName(): Nothing {
throw NotImplementedError() // 丟出異常
}
範例(2) 使用 Nothing? 型別
fun main() {
getUserName() // 使用 Nothing? 型別
}
fun getUserName(): Nothing? {
return null // 保持回傳 null
}
有時候我們可能會好奇在 Kotlin 所撰寫的程式,實際轉換為 Java 會是怎麼樣的語法,此時我們可以利用 intellij 內建的工具進行轉換觀察。
在 Intellij 連續按 Shift 鍵兩次,搜尋「show kotlin」關鍵字,選擇「Show Kotlin Bytecode」,會出現Kotlin位元組碼工具視窗,再點擊「Decompile」按鈕即可觀看轉譯的Java 程式碼。
