kotlin中的數據類及對象
使用泛型創建可重復使用的類
我們將常在線答題考試,有的考試題型包括判斷,或者填空,以及數學題,此外試題內容還包括難易程度:"easy”,"medium","hard",下面我們來定義三種不同類型的問題。
填空題: 答案是由string表示的字詞
class FillInTheBlankQuestion(val questionText: String,val answer: String,val difficulty: String
) {}
判斷題:答案有boolean表示
class TrueOrFalseQuestion(val questionText: String,val answer: Boolean,val difficulty: String
) {}
數學題:答案是數值Int表示
class NumericQuestion(val questionText: String,val answer: Int,val difficulty: String
) {}
以上的三個類基本都是重復代碼,不一樣的就是answer的類型,如果沒想簡化寫法怎么辦?
kotlin提供了一種稱為“泛型類型”的元素,可以讓單個屬性根據特定用例具有不同的數據類型。
那么啥是泛型數據類型?
為屬性創建占位符,在實例化類的時候才指定實際的數據類型。
為類定義通用類型的語法如下:
實例化的時候這樣寫:
注意:這里的泛型屬性傳入的值一定要和尖括號的數據類型一致哦
重新修改代碼:
class Question<T>(val questionText: String,val answer: T,val difficulty: String
) {}fun main() {val q1 = Question<String>("look ___ my eyes","into","medium")val q2 = Question<Boolean>("Hu Ge looks very cute. True or False", true, "easy")val q3 = Question<Int>("1+1=_",2,"hard")
}
枚舉類
枚舉類定義:
枚舉類常量訪問:
enum class Difficulty {EASY, MEDIUM, HARD
}
class Question<T>(val questionText: String,val answer: T,val difficulty: Difficulty
) {}fun main() {val q1 = Question<String>("look ___ my eyes","into",Difficulty.MEDIUM)val q2 = Question<Boolean>("Hu Ge looks very cute. True or False", true, Difficulty.EASY)val q3 = Question<Int>("1+1=_", 2, Difficulty.HARD)
}
數據類
像Question 這樣的類,只包含數據,沒有用于操作的各種什么方法,像這種類就可以稱作數據類,數據類的對象可以使用toString,如何將Question變成數據類呢?那就是在class前加上data關鍵字。
data class Question<T>(val questionText: String,val answer: T,val difficulty: Difficulty
)
fun main() {val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)println(question1.toString()) //輸出為:Question(questionText=Quoth the raven ___, answer=nevermore, difficulty=MEDIUM)
}
將某個類定義為數據類后,系統會實現以下方法。
equals()
hashCode()
:在使用某些集合類型時,您會看到此方法。toString()
- componentN():
component1()
、component2()
等 copy()
單例對象
給硬件設備做測試的時候,因為要修改配置,這種配置不能多人修改,硬件設備只能一個人使用的情況等。那么如何創建單例對象?
單例對象和創建class十分相似,就是把class 變成object,注意單例對象沒有構造函數,因為我們沒法直接創建實例,所有屬性都要在大括號內定義并被賦予初始值。
object StudentProgress {var total: Int = 10var answered: Int = 3
}
訪問單例對象:
fun main() {...println("${StudentProgress.answered} of ${StudentProgress.total} answered.")
}
伴生對象
您可以使用“伴生對象”在另一個類中定義單例對象。伴生對象允許您從類內部訪問其屬性和方法(如果對象的屬性和方法屬于相應類的話),從而讓語法變得更簡潔。
如需聲明伴生對象,只需在 object
關鍵字前面添加 companion
關鍵字即可。
class Quiz {val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)companion object StudentProgress {var total: Int = 10var answered: Int = 3}
}fun main() {println("${Quiz.answered} of ${Quiz.total} answered.")
}
擴展屬性
如果現在要給Quiz添加一個顯示完成情況的屬性,我們應該怎么做呢?那就是使用擴展屬性,語法如下:
val Quiz.StudentProgress.progressText: Stringget() = "${answered} of ${total} answered"fun main() {println(Quiz.progressText)
}
擴展函數
fun Quiz.StudentProgress.printProgressBar() {repeat(Quiz.answered) { print("▓") }repeat(Quiz.total - Quiz.answered) { print("?") }println()println(Quiz.progressText)
}
fun main() {Quiz.printProgressBar()
}
使用重寫的擴展函數
如果您需要多個類具有相同的額外屬性和方法(可能是行為方式不同),就可以使用接口定義這些屬性和方法。
接口使用 interface
關鍵字定義,后跟大駝峰式命名法 (UpperCamelCase) 名稱,再跟左大括號和右大括號。在大括號內,您可以定義任何符合接口標準的類必須實現的方法簽名或 get-only 屬性。
接口是一種協定。系統會聲明符合接口規范的類以擴展接口。類可以聲明它想用“冒號 (:
) 后跟一個空格,再跟接口名稱”的形式來擴展接口。
interface ProgressPrintable {val progressText: Stringfun printProgressBar()
}
在 Quiz
類中,使用 override
關鍵字添加 printProgressBar()
方法
class Quiz : ProgressPrintable {override val progressText: Stringget() = "${answered} of ${total} answered"override fun printProgressBar() {repeat(Quiz.answered) { print("▓") }repeat(Quiz.total - Quiz.answered) { print("?") }println()println(progressText)}
}
作用域函數訪問類的屬性和方法
使用 let()
替換過長的對象名稱
向 Quiz
類添加一個名為 printQuiz()
的函數。
fun printQuiz() {println(question1.questionText)println(question1.answer)println(question1.difficulty)println()println(question2.questionText)println(question2.answer)println(question2.difficulty)println()println(question3.questionText)println(question3.answer)println(question3.difficulty)println()
}
使用let以后可以簡化為:
?fun printQuiz() {println(question1.questionText)println(question1.answer)println(question1.difficulty)println()println(question2.questionText)println(question2.answer)println(question2.difficulty)println()println(question3.questionText)println(question3.answer)println(question3.difficulty)println()
}?
使用 apply() 在沒有變量的情況下調用對象方法
作用域函數有一項非常棒的功能,那就是即使尚未將某個對象分配到變量,您也可以對此對象調用作用域函數。例如,apply()
函數是一個擴展函數,可通過點表示法調用對象。apply()
函數還會返回對相應對象的引用,以便將其存儲在變量中。
- 在創建
Quiz
類的實例時,請在右圓括號后面調用apply()
。您可以在調用apply()
時省略圓括號,并使用尾隨 lambda 語法。
val quiz = Quiz().apply {
}
- 將對
printQuiz()
的調用移至 lambda 表達式內。您無需再引用quiz
變量或使用點表示法。
val quiz = Quiz().apply {printQuiz()
}
apply()
函數會返回Quiz
類的實例,但由于您在任何位置都不再使用此實例了,因此請移除quiz
變量。借助apply()
函數,您甚至無需變量即可對Quiz
實例調用方法。
Quiz().apply {printQuiz()
}
集合
集合可以創建Android的常見功能,例如滾動列表,以及解決任意數量數據的實際編程問題。
數組
同一數據類型的一系列值,有序,可以通過索引訪問。什么是索引?索引是與數組中的某個元素對應的整數。索引可以指示某個項與數組起始元素之間的距離。這稱為“零索引”。數組的第一個元素位于索引 0 處,第二個元素位于索引 1 處,因為它與第一個元素相距一個位置,以此類推。
在設備的內存中,數組中的元素彼此相鄰地存儲在一起。雖然底層細節不在本 Codelab 的討論范圍之內,但這有兩個重要影響:
- 通過索引可以快速地訪問數組元素。您可以通過索引訪問數組的任何隨機元素,并且訪問任何其他隨機元素預計需要大約相同的時間。因此,有人說數組具有隨機訪問特性。
- 數組具有固定的大小。這意味著您向數組添加元素時不能超過該數組的大小。如果嘗試訪問某個數組(包含 100 個元素)中位于索引 100 處的元素,則會引發異常,因為最高索引為 99(請注意,第一個索引為 0,而不是 1)。但是,您可以修改數組中位于相關索引處的值。
- 如果想增加數組的大小只能重新創建這個數組
如需在代碼中聲明數組,請使用 arrayOf()
函數。
fun main() {val rockPlanets = arrayOf<String>("Mercury", "Venus", "Earth", "Mars")val gasPlanets = arrayOf<String>("Jupiter","Saturn","Uranus","Neptune")val solarSystem = rockPlanets + gasPlanetsprintln(solarSystem[0])
}
列表
列表是有序且可調整大小的集合,通常作為可調整大小的數組實現。當數組達到容量上限時,如果您嘗試插入新元素,需要將該數組復制到一個新的較大數組。
使用列表,您還可以在特定索引處(介于其他元素之間)插入新元素。
上圖顯示了在列表中添加和移除元素的方式。在大多數情況下,無論列表中包含多少個元素,向列表中添加任何元素所需的時間都是相同的。每隔一段時間,如果添加新元素會使數組超出其定義的大小,那么數組元素可能必須移動,以便為新元素騰出空間。列表會為您執行所有這些操作,但在后臺,它只是一個在需要時換成新數組的數組。
List
和 MutableList
您在 Kotlin 中遇到的集合類型會實現一個或多個接口。正如您在本單元前面的泛型、對象和擴展 Codelab 中所學到的,接口提供了一組供類實現的標準屬性和方法。實現 List
接口的類可為 List
接口的所有屬性和方法提供實現。MutableList
也是如此。
List
和 MutableList
有什么用?
List
是一個接口,用于定義與只讀有序項集合相關的屬性和方法。MutableList
通過定義修改列表的方法(例如添加和移除元素)來擴展List
接口。
這些接口僅指定 List
和/或 MutableList
的屬性和方法。每個屬性和方法的實現方式均由擴展這些接口的類決定。上述基于數組的實現將是您最常使用(如果不是始終使用)的實現,但 Kotlin 允許其他類擴展 List
和 MutableList
。
listOf()
函數
與 arrayOf()
類似,listOf()
函數將相關項作為形參,但返回 List
,而不是數組。
- 從
main()
中移除現有代碼。 - 在
main()
中,通過調用listOf()
創建名為solarSystem
的行星List
。
fun main() {val solarSystem = listOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
}
List
具有size
屬性,用于獲取列表中的元素數量。輸出solarSystem
列表的size
。
println(solarSystem.size)
- 運行代碼。列表的大小應為 8。
8
訪問列表中的元素
與數組一樣,您可以使用下標語法從 List
訪問特定索引處的元素。您可以使用 get()
方法執行相同的操作。下標語法和 get()
方法接受 Int
作為參數,并在該索引處返回相應元素。與 Array
一樣,ArrayList
也為零索引,因此第四個元素位于索引 3
處。
- 使用下標語法輸出索引
2
處的行星。
println(solarSystem[2])
- 通過對
solarSystem
列表調用get()
,輸出索引3
處的元素。
println(solarSystem.get(3))
- 運行代碼。索引
2
處的元素為"Earth"
,索引3
處的元素為"Mars"
。
... Earth Mars
除了按索引獲取元素之外,您還可以使用 indexOf()
方法搜索特定元素的索引。indexOf()
方法在列表中搜索指定元素(作為實參傳入),并返回該元素在第一次出現時的索引。如果該元素未出現在列表中,則返回 -1
。
- 輸出對
solarSystem
列表調用indexOf()
并傳入"Earth"
的結果。
println(solarSystem.indexOf("Earth"))
- 調用
indexOf()
,傳入"Pluto"
,并輸出結果。
println(solarSystem.indexOf("Pluto"))
- 運行代碼。某個元素與
"Earth"
匹配,因此輸出索引2
。沒有與"Pluto"
匹配的元素,因此輸出-1
。
... 2 -1
使用 for
循環遍歷列表元素
在學習函數類型和 lambda 表達式時,您已經了解如何使用 repeat()
函數多次執行代碼。
編程中的一項常見任務是對列表中的每個元素執行一次某個任務。Kotlin 包含一個名叫 for
循環的功能,可利用簡潔易懂的語法來實現此目的。這通常稱為“循環遍歷”列表或“遍歷”列表。
向列表中添加元素
只有實現 MutableList
接口的類具有在集合中添加、移除和更新元素的功能。如果您一直在跟蹤新發現的行星,則可能需要能夠經常向列表中添加元素。在創建要向其中添加元素和從中移除元素的列表時,您需要專門調用 mutableListOf()
函數,而不是 listOf()
。
add()
函數有兩個版本:
- 第一個
add()
函數具有一個屬于列表中元素類型的參數,并將其添加到列表末尾。 add()
的另一個版本有兩個參數。第一個參數對應于應該插入新元素的索引。第二個參數是要添加到列表中的元素。
我們來看看實際用例。
- 將
solarSystem
的初始化更改為調用mutableListOf()
,而不是listOf()
。您現在可以調用MutableList
中定義的方法。
val solarSystem = mutableListOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
- 同樣,我們也可能需要將 Pluto 歸類為行星。對
solarSystem
調用add()
方法,傳入"Pluto"
作為單個參數。
solarSystem.add("Pluto")
- 有些科學家提出一種理論:過去曾有一顆名為 Theia 的行星,該行星后來與地球發生碰撞并形成月球。在索引
3
(介于"Earth"
和"Mars"
之間)處插入"Theia"
。
solarSystem.add(3, "Theia")
更新特定索引處的元素
您可以使用下標語法更新現有元素:
- 將索引
3
處的值更新為"Future Moon"
。
solarSystem[3] = "Future Moon"
- 使用下標語法輸出位于索引
3
和9
處的值。
println(solarSystem[3])
println(solarSystem[9])
- 運行代碼,驗證輸出。
Future Moon Pluto
從列表中移除元素
使用 remove()
或 removeAt()
方法可移除元素。您可以通過兩種方法移除元素:將該元素傳遞到 remove()
方法中,或者使用 removeAt()
按索引移除該元素。
我們來看下這兩種元素移除方法的實際操作效果。
- 對
solarSystem
調用removeAt()
,并傳入索引9
。這應該會從列表中移除"Pluto"
。
solarSystem.removeAt(9)
- 對
solarSystem
調用remove()
,并傳入"Future Moon"
作為要移除的元素。此操作應該會搜索此列表,如果找到匹配元素,則會將其移除。
solarSystem.remove("Future Moon")
List
可提供contains()
方法,該方法可在列表中存在某個元素時返回Boolean
。輸出為"Pluto"
調用contains()
的結果。
println(solarSystem.contains("Pluto"))
- 更簡潔的語法是使用
in
運算符。您可以使用元素、in
運算符和集合來檢查該元素是否在列表中。使用in
運算符檢查solarSystem
是否包含"Future Moon"
。
println("Future Moon" in solarSystem)
- 運行代碼。兩個語句都應輸出
false
。
... false false
集
集是指沒有特定順序且不允許出現重復值的集合。
在 Kotlin 中使用 MutableSet
在本示例中,我們將使用 MutableSet
來演示如何添加和移除元素。
- 從
main()
中移除現有代碼。 - 使用
mutableSetOf()
創建名為solarSystem
的行星Set
。這將返回MutableSet
,其默認實現為LinkedHashSet()
。
val solarSystem = mutableSetOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
- 使用
size
屬性輸出該集的大小。
println(solarSystem.size)
- 與
MutableList
類似,MutableSet
具有add()
方法。使用add()
方法將"Pluto"
添加到solarSystem
集。它只需要為所添加的元素使用一個參數。集中的元素未必是有序的,因此沒有索引!
solarSystem.add("Pluto")
- 添加相應元素后,輸出該集的
size
。
println(solarSystem.size)
contains()
函數接受一個參數,并檢查該集中是否包含指定的元素。如果是,則返回 true。否則,它將返回 false。調用contains()
以檢查"Pluto"
是否在solarSystem
中。
println(solarSystem.contains("Pluto"))
- 運行代碼。大小已增大,
contains()
現在會返回true
。
8 9 true
注意:您也可以使用 in
運算符檢查某個元素是否在集合中,例如:"Pluto" in solarSystem
相當于 solarSystem.contains("Pluto")
。
- 如前所述,集不能包含重復項。請嘗試重新添加
"Pluto"
。
solarSystem.add("Pluto")
- 再次輸出集的大小。
println(solarSystem.size)
- 再次運行您的代碼。未添加
"Pluto"
,因為它已包含在集中。這次,大小應該不會增加。
... 9
remove()
函數接受一個參數,并從集中移除指定的元素。
- 使用
remove()
函數移除"Pluto"
。
solarSystem.remove("Pluto")
注意:請記住,集是無序的集合。您無法按索引從集中移除某個值,因為集沒有索引。
- 輸出集合的大小,并再次調用
contains()
以檢查"Pluto"
是否仍在集中。
println(solarSystem.size)
println(solarSystem.contains("Pluto"))
- 運行代碼。
"Pluto"
不在集中,大小現在為 8。
... 8 false
映射集合
Map
是由鍵和值組成的集合。之所以稱之為映射,是因為唯一鍵會映射到其他值。鍵及其附帶的值通常稱為 key-value pair
。
映射的鍵具有唯一性,但映射的值不具有唯一性。兩個不同的鍵可以映射到同一個值。例如,"Mercury"
有 0
顆衛星,"Venus"
也有 0
顆衛星。
通過相應的鍵訪問映射的值通常比在大型列表中(例如使用 indexOf()
)搜索值更快。
您可以使用 mapOf()
或 mutableMapOf()
函數聲明映射。映射需要兩個泛型類型(以英文逗號隔開),一個用于鍵,另一個用于值。
如果映射具有初始值,則還可以使用類型推斷。要使用初始值填充映射,每個鍵值對都由以下部分組成:首先是鍵,后跟 to
運算符,而后是值。每個鍵值對均以英文逗號隔開。
接下來,我們詳細了解一下如何使用映射,以及一些有用的屬性和方法。
- 從
main()
中移除現有代碼。 - 使用
mutableMapOf()
和如下初始值創建一個名為solarSystem
的映射。
val solarSystem = mutableMapOf("Mercury" to 0,"Venus" to 0,"Earth" to 1,"Mars" to 2,"Jupiter" to 79,"Saturn" to 82,"Uranus" to 27,"Neptune" to 14
)
- 與列表和集一樣,
Map
提供包含鍵值對數量的size
屬性。輸出solarSystem
映射的大小。
println(solarSystem.size)
- 您可以使用下標語法設置其他鍵值對。將
"Pluto"
鍵的值設置為5
。
solarSystem["Pluto"] = 5
- 插入元素后,再次輸出大小。
println(solarSystem.size)
- 您可以使用下標語法來獲取值。輸出鍵
"Pluto"
的衛星數量。
println(solarSystem["Pluto"])
- 您還可以使用
get()
方法訪問值。無論您使用下標語法還是調用get()
,您傳入的鍵都可能不存在于映射中。如果沒有鍵值對,它將返回 null。輸出"Theia"
的衛星數量。
println(solarSystem.get("Theia"))
- 運行代碼。系統應該會輸出 Pluto 的衛星數量。不過,由于 Theia 不在映射中,因此調用
get()
會返回 null。
8 9 5 null
remove()
方法可移除具有指定鍵的鍵值對。它也會返回已移除的值,或者如果指定的鍵不在映射中,則返回 null
。
- 輸出調用
remove()
并傳入"Pluto"
的結果。
solarSystem.remove("Pluto")
- 若要驗證相應項是否已移除,請再次輸出大小。
println(solarSystem.size)
- 運行代碼。移除該條目后,映射的大小將為 8。
... 8
- 下標語法或
put()
方法也可以修改已存在的鍵的值。使用下標語法將 Jupiter 的衛星數量更新為 78,并輸出新值。
solarSystem["Jupiter"] = 78
println(solarSystem["Jupiter"])
- 運行代碼。現有鍵
"Jupiter"
的值已更新。
... 78