Kotlin Tutorial(17)存取修飾子與資料類別 << 前情
泛型(generic)對Kotlin與Java都是一個很重要的特性,它可以節省許多冗長與重複的程式碼,很多API使用泛型的設計,例如經常使用的集合(collection)。在瞭解泛型的設計與應用以後,也可以容易的使用泛型API執行需要的工作。
沒有使用泛型
應用程式在處理各種資料的時候,可能會設計一個像這樣的類別,讓陣列的使用可以方便一些:
/* net.macdidi5.kotlin.tutorial.ch18.StringContainer.kt */
package net.macdidi5.kotlin.tutorial.ch18
class StringContainer(private val size : Int) {
// 建立指定元素個數的陣列
private val arr = arrayOfNulls<String>(size)
// 目前的索引編號
private var index : Int = 0
// 加入一個元素
fun add(element : String) {
if (index < size) {
arr[index] = element
index++
}
}
// 取得指定索引編號的元素
fun get(index : Int) : String? {
var result : String? = null
if ( index in 0 until size ) {
result = arr[index]
}
return result
}
}
下面的程式碼示範使用StringContainer類別處理字串資料:
/* net.macdidi5.kotlin.tutorial.ch18.ContainerDemo01.kt */
package net.macdidi5.kotlin.tutorial.ch18
fun main(args: Array<String>) {
// 建立包裝字串的容器物件
val sc = StringContainer(3)
// 加入字串元素
sc.add("Simon")
sc.add("Mary")
sc.add("John")
for (i in 0..2) {
println("$i : ${sc.get(i)}")
}
...
}
如果應用程式也需要使用上面的作法處理Item物件,就需要另外設計一個這樣的類別:
/* net.macdidi5.kotlin.tutorial.ch18.ItemContainer.kt */
package net.macdidi5.kotlin.tutorial.ch18
class ItemContainer(private val size : Int) {
// 建立指定元素個數的陣列
private val arr = arrayOfNulls<Item>(size)
// 目前的索引編號
private var index : Int = 0
// 加入一個元素
fun add(element : Item) {
if (index < size) {
arr[index] = element
index++
}
}
// 取得指定索引編號的元素
fun get(index : Int) : Item? {
var result : Item? = null
if ( index in 0 until size ) {
result = arr[index]
}
return result
}
}
下面的程式碼示範使用ItemContainer類別處理Item資料:
/* net.macdidi5.kotlin.tutorial.ch18.ContainerDemo01.kt */
package net.macdidi5.kotlin.tutorial.ch18
fun main(args: Array<String>) {
...
// 建立包裝Item的容器物件
val ic = ItemContainer(3)
// 加入Item元素
ic.add(Item(101, "Hello", "Kotlin"))
ic.add(Item(102, "Hi", "Android"))
ic.add(Item(103, "Chao", "Java"))
for (i in 0..2) {
println("$i : ${ic.get(i)?.details}")
}
}
比較StringContainer與ItemContainer,你可以注意到它們幾乎是一樣的,這類的設計方式,就會發生程式碼重複的狀況,對於程式開發與維護來說,都是很不好的。
使用泛型宣告
如果一個類別或介面,它們需要處理的多種不同的資料型態,你可以在類別或介面名稱後面,使用「<泛型代碼>」加入泛型的宣告。下面是一個泛型宣告的範例:
class GenericClass<T> {
private var value : T? = null
fun get() : T? {
return value
}
fun set(value : T?) {
this.value = value
}
}
使用這個類別的時候,可以在類別名稱後面指定泛型的型態:
val stringGC = GenericClass<String>()
在宣告GenericClass的時候,根據你在「<」和「>」之間指定的型態,這個類別會變成下面的樣子:
class GenericClass<String> {
private var value : String? = null
fun get() : String? {
return value
}
fun set(value : String?) {
this.value = value
}
}
如果在宣告GenericClass的時候,指定泛型的型態為Item:
val itemGC = GenericClass<Item>()
對itemGC這個物件來說,GenericClass類別會變成下面的樣子:
class GenericClass<Item> {
private var value : Item? = null
fun get() : Item? {
return value
}
fun set(value : Item?) {
this.value = value
}
}
根據上面說明的範例,使用泛型宣告的GenericClass類別,可以提供非常大的靈活性,而且避免重複的程式碼。泛型代碼可以是任何有效的識別字,一般的作法會使用下面的規則:
- 一個大寫的字母
- 依照用途選擇適合的字母,例如:
- T:型態
- S、U、V…:第二、三、四…種型態
- E:元素
- K:索引鍵型態
- V:資料型態
下面的範例也是一個正確的泛型宣告,不過並不建議使用像「QQQ」這樣的泛型代碼:
class GenericClass<QQQ> {
private var value : QQQ? = null
fun get() : QQQ? {
return value
}
fun set(value : QQQ?) {
this.value = value
}
}
泛型宣告範例
下面的Container類別使用陣列包裝同樣型態的資料,不過類別提供比較簡易的用法,因為使用泛型的設計,Container可以包裝各種型態的資料:
/* net.macdidi5.kotlin.tutorial.ch18.Container.kt */
package net.macdidi5.kotlin.tutorial.ch18
class Container<T>(private val size : Int) {
// 建立指定元素個數的陣列
private val arr = arrayOfNulls<Any?>(size)
// 目前的索引編號
private var index : Int = 0
// 加入一個元素
fun add(element : T) {
if (index < size) {
arr[index] = element
index++
}
}
// 取得指定索引編號的元素
fun get(index : Int) : T? {
var result : T? = null
if ( index in 0 until size ) {
result = arr[index] as T
}
return result
}
}
下面的範例程式使用Container類別包裝字串與Item資料:
/* net.macdidi5.kotlin.tutorial.ch18.ContainerDemo02.kt */
package net.macdidi5.kotlin.tutorial.ch18
fun main(args: Array<String>) {
// 建立包裝元素的容器物件,使用泛型設定元素為字串
val sc = Container<String>(3)
// 加入字串元素
sc.add("Simon")
sc.add("Mary")
sc.add("John")
for (i in 0..2) {
println("$i : ${sc.get(i)}")
}
println()
// 建立包裝元素的容器物件,使用泛型設定元素為Item
val ic = Container<Item>(3)
// 加入Item元素
ic.add(Item(101, "Hello", "Kotlin"))
ic.add(Item(102, "Hi", "Android"))
ic.add(Item(103, "Chao", "Java"))
for (i in 0..2) {
println("$i : ${ic.get(i)?.details}")
}
}
泛型函式
應用程式在提供各種功能的時候,可能會有下面這樣的需求:
/* net.macdidi5.kotlin.tutorial.ch18.GenericFunction01.kt */
package net.macdidi5.kotlin.tutorial.ch18
// 接收兩個字串陣列參數,結合兩個陣列以後傳回新的字串陣列
fun combineArray(a1 : Array<String>, a2 : Array<String>) : Array<String?> {
val newSize = a1.size + a2.size
val result = a1.copyOf(newSize)
for ( (i, v) in a2.withIndex() ) {
result[ i + a1.size ] = v
}
return result
}
// 接收兩個Item陣列參數,結合兩個陣列以後傳回新的Item陣列
fun combineArray(a1 : Array<Item>, a2 : Array<Item>) : Array<Item?> {
val newSize = a1.size + a2.size
val result = a1.copyOf(newSize)
for ( (i, v) in a2.withIndex() ) {
result[ i + a1.size ] = v
}
return result
}
...
fun main(args: Array<String>) {
// 建立兩個字串陣列
val names01 = arrayOf("Simon", "Mary")
val names02 = arrayOf("Sam", "John")
// 結合兩個字串陣列
val namesAll = combineArray(names01, names02)
for ( (i, v) in namesAll.withIndex()) {
println("$i: $v")
}
println()
val item01 = Item(101, "Hello", "Kotlin")
val item02 = Item(102, "Hi", "Android!")
val item03 = Item(103, "Chao", "Java")
// 建立兩個Item陣列
val items01 = arrayOf(item01, item02)
val items02 = arrayOf(item03)
// 結合兩個Item陣列
val itemsAll = combineArray(items01, items02)
for ( (i, v) in itemsAll.withIndex()) {
println("$i: ${v?.details}")
}
...
}
上面範例的combineArray函式在處理不同型態陣列的時候,需要宣告不同的函式,這樣的設計方式看起來應該就會覺得不太好。如果應用程式有這類的需求,可以使用泛型函式的作法。例如下面的泛型函式範例:
fun <T> genericFunction(p : T) : T {
val value : T = p
return value
}
在呼叫上面泛型函式的時候,可以經由參數決定泛型函式的型態,因為傳送給函式的參數為字串,所以泛型函式的型態為String,這個泛型函式的回傳型態也是String:
根據上面的說明與範例,這個函式的宣告會像下面的樣子:
fun <String> genericFunction(p : String) : String {
val value : String = p
return value
}
如果呼叫這個泛型函式的時候,傳送的參數為Item型態的物件:
genericFunction(Item(101, "Hello", "Kotlin"))
這個函式的宣告會像下面的樣子:
fun <Item> genericFunction(p : Item) : Item {
val value : Item = p
return value
}
瞭解泛型函式的基本概念以後,就可以設計執行結合陣列工作的函式:
/* net.macdidi5.kotlin.tutorial.ch18.GenericFunction01.kt */
package net.macdidi5.kotlin.tutorial.ch18
...
// 接收兩個泛型陣列,結合兩個陣列以後傳回新的泛型陣列
fun <T> combineArrayGeneric(a1 : Array<T>, a2 : Array<T>) : Array<T?> {
val newSize = a1.size + a2.size
val result = a1.copyOf(newSize)
for ( (i, v) in a2.withIndex() ) {
result[ i + a1.size ] = v
}
return result
}
fun main(args: Array<String>) {
// 建立兩個字串陣列
val names01 = arrayOf("Simon", "Mary")
val names02 = arrayOf("Sam", "John")
...
val item01 = Item(101, "Hello", "Kotlin")
val item02 = Item(102, "Hi", "Android!")
val item03 = Item(103, "Chao", "Java")
// 建立兩個Item陣列
val items01 = arrayOf(item01, item02)
val items02 = arrayOf(item03)
...
// 呼叫泛型版本的函式,由傳遞的參數決定泛型為String
val names = combineArrayGeneric(names01, names02)
for ( (i, v) in names.withIndex()) {
println("$i: $v")
}
println()
// 呼叫泛型版本的函式,由傳遞的參數決定泛型為Item
val items = combineArrayGeneric(items01, items02)
for ( (i, v) in items.withIndex()) {
println("$i: ${v?.details}")
}
}
下一步
應用程式經常需要處理數量比較多一些的資料,除了固定元素個數的陣列外,接下來準備認識應用程式經常使用的集合架構。
相關的檔案都可以在GitHub瀏覽與下載。
http://github.com/macdidi5/Kotlin-Tutorial
後續 >> Kotlin Tutorial(19)集合與泛型
|