Kotlin學習筆記9——類和對象
前言
上一篇,我們學習了Kotlin中的內聯函數,今天繼續來學習Kotlin中的類和對象。
類定義
Kotlin 類可以包含:構造函數和初始化代碼塊、函數、屬性、內部類、對象聲明,Kotlin 中使用關鍵字 class 聲明類,後面緊跟類名。接下來看一個簡單的類定義:
class Runoob { // 類名爲 Runoob
// 大括號內是類體構成
fun foo() { print("Foo") } // 成員函數
}
類的屬性
屬性定義
類的屬性可以用關鍵字 var 聲明爲可變的,否則使用只讀關鍵字 val 聲明爲不可變。
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
要使用一個屬性,只要用名稱引用它即可:
val result = Address() // Kotlin 中沒有“new”關鍵字
result.name = "Jim"
Getters 與 Setters
屬性聲明的完整語法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
getter 和 setter 都是可選
如果屬性類型可以從初始化語句或者類的成員函數中推斷出來,那就可以省去類型,val不允許設置setter函數,因爲它是隻讀的。
var allByDefault: Int? // 錯誤: 需要一個初始化語句, 默認實現了 getter 和 setter 方法
var initialized = 1 // 類型爲 Int, 默認實現了 getter 和 setter
val simple: Int? // 類型爲 Int ,默認實現 getter ,但必須在構造函數中初始化
val inferredType = 1 // 類型爲 Int 類型,默認實現 getter
實例
以下實例定義了一個 Person 類,包含兩個可變變量 lastName 和 no,lastName 修改了 getter 方法,no 修改了 setter 方法。
class Person {
var lastName: String = "zhang"
get() = field.toUpperCase() // 將變量賦值後轉換爲大寫
set
var no: Int = 100
get() = field // 後端變量
set(value) {
if (value < 10) { // 如果傳入的值小於 10 返回該值
field = value
} else {
field = -1 // 如果傳入的值大於等於 10 返回 -1
}
}
var heiht: Float = 145.4f
private set
}
// 測試
fun main(args: Array<String>) {
var person: Person = Person()
person.lastName = "wang"
println("lastName:${person.lastName}")
person.no = 9
println("no:${person.no}")
person.no = 20
println("no:${person.no}")
}
輸出結果爲:
lastName:WANG
no:9
no:-1
幕後字段
Kotlin 中類不能有字段。提供了 Backing Fields(幕後字段) 機制,備用字段使用field關鍵字聲明,field 關鍵詞只能用於屬性的訪問器,如以上實例:
//在這裏的get和set方法中field字段代表了no自己
var no: Int = 100
get() = field // 幕後字段
set(value) {
if (value < 10) { // 如果傳入的值小於 10 返回該值
field = value
} else {
field = -1 // 如果傳入的值大於等於 10 返回 -1
}
}
如果屬性至少一個訪問器使用默認實現,或者自定義訪問器通過 field 引用幕後字段,將會爲該屬性生成一個幕後字段。例如,下面的情況下, 就沒有幕後字段:
val isEmpty: Boolean
get() = this.size == 0
幕後屬性
幕後屬性是我們自己定義的,一般用於這種情況:我們希望定義一個這樣的變量,對外表現爲只能讀,不能寫,對內表現可以任意讀寫。這在Java中的應該只能通過函數來做,Kotlin中定義變量val只能讀,不能寫,var任意讀寫,那麼同一個變量,怎麼實現兩種情況呢?
class Demo{
private var _size:Int = 0
val size get() = _size
}
這裏size是提供給對外的變量,_size是內部使用的,其實最後還是通過函數來控制的,我們可以看看字節碼:
public final class Demo {
private int _size;
public final int getSize() {
return this._size;
}
}
這裏_size就叫做幕後屬性
構造函數
在 Kotlin 中的一個類可以有一個主構造函數以及一個或多個次構造函數。
主構造器
主構造器中不能包含任何代碼,初始化代碼可以放在初始化代碼段中,初始化代碼段使用 init 關鍵字作爲前綴。主構造函數是類頭的一部分:它跟在類名(與可選的類型參數)後。
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
如果主構造函數沒有任何註解或者可見性修飾符,可以省略這個 constructor 關鍵字。
class Person(firstName: String) { /*……*/ }
注意:主構造器的參數可以在初始化代碼段中使用,也可以在類主體n定義的屬性初始化代碼中使用。 一種簡潔語法,可以通過主構造器來定義屬性並初始化屬性值(可以是var或val):
class People(val firstName: String, val lastName: String) {
//...
}
上面的代碼等同下面的代碼:
class People() {
val firstName: String = ""
val lastName: String = ""
//...
}
如果構造器有註解,或者有可見度修飾符,這時constructor關鍵字是必須的,註解和修飾符要放在它之前。
創建一個 Runoob類,並通過構造函數傳入網站名:
class Runoob constructor(name: String) { // 類名爲 Runoob
// 大括號內是類體構成
var url: String = "http://www.runoob.com"
var country: String = "CN"
var siteName = name
init {
println("初始化網站名: ${name}")
}
fun printTest() {
println("我是類的函數")
}
}
fun main(args: Array<String>) {
val runoob = Runoob("菜鳥教程")
println(runoob.siteName)
println(runoob.url)
println(runoob.country)
runoob.printTest()
}
輸出結果爲:
初始化網站名: 菜鳥教程
菜鳥教程
http://www.runoob.com
CN
我是類的函數
次構造函數
類也可以有二級構造函數,需要加前綴 constructor:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果類有主構造函數,每個次構造函數都要,或直接或間接通過另一個次構造函數代理主構造函數。在同一個類中代理另一個構造函數使用 this 關鍵字:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}
如果一個非抽象類沒有聲明構造函數(主構造函數或次構造函數),它會產生一個沒有參數的構造函數。構造函數是 public 。如果你不想你的類有公共的構造函數,你就得聲明一個空的主構造函數:
class DontCreateMe private constructor () {
}
示例:
class Runoob constructor(name: String) { // 類名爲 Runoob
// 大括號內是類體構成
var url: String = "http://www.runoob.com"
var country: String = "CN"
var siteName = name
init {
println("初始化網站名: ${name}")
}
// 次構造函數
constructor (name: String, alexa: Int) : this(name) {
println("Alexa 排名 $alexa")
}
fun printTest() {
println("我是類的函數")
}
}
fun main(args: Array<String>) {
val runoob = Runoob("菜鳥教程", 10000)
println(runoob.siteName)
println(runoob.url)
println(runoob.country)
runoob.printTest()
}
輸出結果爲:
初始化網站名: 菜鳥教程
Alexa 排名 10000
菜鳥教程
http://www.runoob.com
CN
我是類的函數
抽象類
抽象是面向對象編程的特徵之一,類本身,或類中的部分成員,都可以聲明爲abstract的。抽象成員在類中不存在具體的實現。
注意:無需對抽象類或抽象成員標註open註解。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
嵌套類
我們可以把類嵌套在其他類中,看以下實例:
class Outer { // 外部類
private val bar: Int = 1
class Nested { // 嵌套類
fun foo() = 2
}
}
fun main(args: Array<String>) {
val demo = Outer.Nested().foo() // 調用格式:外部類.嵌套類.嵌套類方法/屬性
println(demo) // == 2
}
內部類
內部類使用 inner 關鍵字來表示。
內部類會帶有一個對外部類的對象的引用,所以內部類可以訪問外部類成員屬性和成員函數。
class Outer {
private val bar: Int = 1
var v = "成員屬性"
/**嵌套內部類**/
inner class Inner {
fun foo() = bar // 訪問外部類成員
fun innerTest() {
var o = this@Outer //獲取外部類的成員變量
println("內部類可以引用外部類的成員,例如:" + o.v)
}
}
}
fun main(args: Array<String>) {
val demo = Outer().Inner().foo()
println(demo) // 1
val demo2 = Outer().Inner().innerTest()
println(demo2) // 內部類可以引用外部類的成員,例如:成員屬性
}
爲了消除歧義,要訪問來自外部作用域的 this,我們使用this@label,其中 @label 是一個 代指 this 來源的標籤。
嵌套類和內部類在使用時的區別
創建對象的區別
var demo = Outter.Nested()// 嵌套類,Outter後邊沒有括號
var demo = Outter().Inner();// 內部類,Outter後邊有括號
也就是說,要想構造內部類的對象,必須先構造外部類的對象,而嵌套類則不需要;
引用外部類的成員變量的方式不同
先來看嵌套類:
class Outer { // 外部類
private val bar: Int = 1
class Nested { // 嵌套類
var ot: Outer = Outer()
println(ot.bar) // 嵌套類可以引用外部類私有變量,但要先創建外部類的實例,不能直接引用
fun foo() = 2
}
}
再來看一下內部類:
class Outer {
private val bar: Int = 1
var v = "成員屬性"
/**嵌套內部類**/
inner class Inner {
fun foo() = bar // 訪問外部類成員
fun innerTest() {
var o = this@Outer //獲取外部類的成員變量
println("內部類可以引用外部類的成員,例如:" + o.v)
}
}
}
可以看來內部類可以直接通過 this@ 外部類名 的形式引用外部類的成員變量,不需要創建外部類對象;
匿名內部類
使用對象表達式來創建匿名內部類,使用object關鍵字:
class Test {
var v = "成員屬性"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/**
* 定義接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array<String>) {
var test = Test()
/**
* 採用對象表達式來創建接口對象,即匿名內部類的實例。
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("對象表達式創建匿名內部類的實例")
}
})
}
枚舉類
枚舉類需要在類名前添加enum關鍵字,默認名稱爲枚舉字符名,值從0開始
enum class Color {
RED,BLACK,BLUE,GREEN,WHITE
}
使用:
//枚舉
var color:Color=Color.BLUE
println(Color.values())
println(Color.valueOf("RED"))
println(color.name)
println(color.ordinal)
結果:
[Lcom.example.kotlindemo.Color;@30dae81
RED
BLUE
2
接口
Kotlin 的接口可以既包含抽象方法的聲明也包含實現。與抽象類不同的是,接口無法保存狀態。它可以有屬性但必須聲明爲抽象或提供訪問器實現。
使用關鍵字 interface 來定義接口
interface MyInterface {
fun bar()
fun foo() {
// 可選的方法體
}
}
實現接口
一個類或者對象可以實現一個或多個接口,多個接口用逗號隔開
class Child : MyInterface {
override fun bar() {
// 方法體
}
}
接口中的屬性
你可以在接口中定義屬性。在接口中聲明的屬性要麼是抽象的,要麼提供訪問器的實現。在接口中聲明的屬性不能有幕後字段(backing field),因此接口中聲明的訪問器不能引用它們。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
接口繼承
一個接口可以從其他接口派生,從而既提供基類型成員的實現也聲明新的函數與屬性。很自然地,實現這樣接口的類只需定義所缺少的實現:
interface Named {
val name: String
}
interface Person : Named {
val firstName: String
val lastName: String
override val name: String get() = "$firstName $lastName"
}
data class Employee(
// 不必實現“name”
override val firstName: String,
override val lastName: String,
val position: Position
) : Person
函數重寫
實現多個接口時,可能會遇到同一方法繼承多個實現的問題。例如
interface A {
fun foo() { print("A") } // 已實現
fun bar() // 未實現,沒有方法體,是抽象的
}
interface B {
fun foo() { print("B") } // 已實現
fun bar() { print("bar") } // 已實現
}
class C : A {
override fun bar() { print("bar") } // 重寫
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
fun main(args: Array<String>) {
val d = D()
d.foo();
d.bar();
}
//輸出結果:ABbar
實例中接口 A 和 B 都定義了方法 foo() 和 bar(), 兩者都實現了 foo(), B 實現了 bar()。因爲 C 是一個實現了 A 的具體類,所以必須要重寫 bar() 並實現這個抽象方法。
然而,如果我們從 A 和 B 派生 D,我們需要實現多個接口繼承的所有方法,並指明 D 應該如何實現它們。這一規則 既適用於繼承單個實現(bar())的方法也適用於繼承多個實現(foo())的方法。
類的修飾符
類的修飾符包括 classModifier 和_accessModifier_:
- classModifier: 類屬性修飾符,標示類本身特性。
abstract // 抽象類
final // 類不可繼承,默認屬性
enum // 枚舉類
open // 類可繼承,類默認是final的
annotation // 註解類
- accessModifier: 訪問權限修飾符
private // 僅在同一個文件中可見
protected // 同一個文件中或子類可見
public // 所有調用的地方都可見
internal // 同一個模塊中可見
示例:
// 文件名:example.kt
package foo
private fun foo() {} // 在 example.kt 內可見
public var bar: Int = 5 // 該屬性隨處可見
internal val baz = 6 // 相同模塊內可見
尾巴
今天的學習筆記就先到這裏了,下一篇我們將學習Kotlin中的繼承。
老規矩,喜歡我的文章,歡迎素質三連:點贊,評論,關注,謝謝大家!