Chinese (Traditional) (中文(繁體)) translation by Zhang Xiang Liang (you can also view the original English article)
Kotlin 是壹種編譯為Java字節碼的現代編程語言。它是免費的, 開源,並有望使Android開發更有趣。
在 前面的文章中,您了解了更多關於Kotlin屬性的內容,例如後期初始化,擴展和內聯屬性。不僅如此,您還了解了Kotlin中的高級類,如數據,枚舉,嵌套和密封類。
在這篇文章中,您將繼續通過學習抽象類,接口和繼承來了解Kotlin中的面向對象編程。為了獲得獎勵,您還將了解類型別名。
1. 抽象類
Kotlin支持抽象類 - 就像Java壹樣,這些類是妳永遠不打算創建對象的類。如果沒有壹些具體的(非抽象的)子類,抽象類是不完整的或無用的,妳可以從中實例化對象。 抽象類的具體子類實現抽象類中定義的所有方法和屬性 - 否則該子類也是抽象類! 我們用abstract 修飾符創建壹個抽象類 (類似於Java)。
我們用abstract 修飾符創建壹個抽象類 (類似於Java)。
abstract class Employee (val firstName: String, val lastName: String) { abstract fun earnings(): Double }
請註意,並非所有成員都必須是抽象的。換句話說,我們可以在抽象類中實現方法默認實現。
abstract class Employee (val firstName: String, val lastName: String) { // ... fun fullName(): String { return lastName + " " + firstName; } }
這裏我們fullName() 在抽象類中 創建了非抽象函數 Employee。具體類(抽象類的子類)可以重寫抽象方法的默認實現 - 但只有當方法具有open指定的修飾符時(您將在短時間內了解更多內容)。
我們也可以在抽象類中存儲狀態。
abstract class Employee (val firstName: String, val lastName: String) { // ... val propFoo: String = "bla bla" }
即使抽象類沒有定義任何方法,我們也需要在實例化之前創建壹個子類,如下例所示。
class Programmer(firstName: String, lastName: String) : Employee(firstName, lastName) { override fun earnings(): Double { // calculate earnings } }
我們的Programmer課程擴展了Employee抽象類。在Kotlin中,我們使用單個冒號字符(:)而不是Java extends 關鍵字來擴展類或實現接口。
然後,我們可以創建壹個類型對象 Programmer 並調用它的方法 - 無論是在它自己的類還是超類(基類)中。
val programmer = Programmer("Chike", "Mgbemena") println(programmer.fullName()) // "Mgbemena Chike"
有壹件事可能會讓妳大吃壹驚,那就是我們有能力用var (mutable)重寫壹個 val(不可變的)屬性 。
open class BaseA (open val baseProp: String) { } class DerivedA : BaseA("") { private var derivedProp: String = "" override var baseProp: String get() = derivedProp set(value) { derivedProp = value } }
確保妳明智地使用這個功能!請註意,我們無法使用val反向重寫 var 屬性 。
2.接口
接口只是壹個相關方法的集合,它通常使您能夠告訴對象要執行的操作,以及默認情況下如何執行此操作。(接口中的默認方法是添加到Java 8的新特性。)換句話說,接口是實現類必須遵守的合約。
使用interface Kotlin中的關鍵字定義接口 (類似於Java)。
class Result class Student interface StudentRepository { fun getById(id: Long): Student fun getResultsById(id: Long): List<Result> }
在上面的代碼中,我們已經聲明了壹個StudentRepository 接口。這個接口包含兩個抽象方法:getById()和getResultsById()。 請註意,abstract在接口方法中包含關鍵字是多余的,因為它們已經是隱式抽象的。 .
沒有壹個或多個實現者,接口是沒有用的 - 所以我們創建壹個實現這個接口的類。
class StudentLocalDataSource : StudentRepository { override fun getResults(id: Long): List<Result> { // do implementation } override fun getById(id: Long): Student { // do implementation } }
這裏我們創建了壹個StudentLocalDataSource 實現StudentRepository 接口的類 。
我們使用override修飾符來標記要從接口或超類重新定義的方法和屬性 - 這與@OverrideJava中的註釋類似。
請註意Kotlin中以下附加的接口規則:
- 壹個類可以實現盡可能多的接口,但它只能擴展壹個類(類似於Java)。
- 該 override修改是在Java中的必修課科特林-不同。
- 除了方法外,我們還可以在Kotlin界面中聲明屬性。
- Kotlin接口方法可以有壹個默認實現(類似於Java 8)。
我們來看壹個具有默認實現的接口方法的例子。
interface StudentRepository { // ... fun delete(student: Student) { // do implementation } }
在前面的代碼中,我們添加了delete()壹個默認實現的新方法(盡管我沒有為演示目的添加實際的實現代碼)。
如果需要,我們也可以自由重寫默認實現
class StudentLocalDataSource : StudentRepository { // ... override fun delete(student: Student) { // do implementation } }
如上所述,Kotlin接口可以具有屬性 - 但請註意,它不能保持狀態。(但是,記住抽象類可以維護狀態。)所以下面的帶有屬性聲明的接口定義將起作用。
interface StudentRepository { val propFoo: Boolean // will work // ... }
但是,如果我們試圖通過給屬性賦值來向接口添加壹些狀態,它將無法工作。
interface StudentRepository { val propFoo: Boolean = true // Error: Property initializers are not allowed in interfaces // .. }
然而,Kotlin中的接口屬性可以有getter和setter方法(盡管只有後者如果該屬性是可變的)。另請註意,接口中的屬性不能有後臺字段。
interface StudentRepository { var propFoo: Boolean get() = true set(value) { if (value) { // do something } } // ... }
如果需要,我們也可以覆蓋接口屬性,以便重新定義它。
class StudentLocalDataSource : StudentRepository { // ... override var propFoo: Boolean get() = false set(value) { if (value) { } } }
讓我們來看看我們有壹個類實現了多個接口的情況下,使用相同的方法簽名。這個類如何決定調用哪個接口方法?
interface InterfaceA { fun funD() {} } interface InterfaceB { fun funD() {} }
這裏我們有兩個接口具有相同簽名的方法funD()。讓我們創建壹個實現這兩個接口並覆蓋該funD()方法的類。
class classA : InterfaceA, InterfaceB { override fun funD() { super.funD() // Error: Many supertypes available, please specify the one you mean in angle brackets, e.g. 'super<Foo>' } }
編譯器對調用super.funD()方法感到困惑,因為類實現的兩個接口具有相同的方法簽名。
為了解決這個問題,我們用尖括號將我們想要調用方法的接口名稱包裝起來。(IntelliJ IDEA或Android Studio會在您遇到問題時向您提供有關解決此問題的提示。)
class classA : InterfaceA, InterfaceB { override fun funD() { super<InterfaceA>.funD() } }
在這裏,我們將要調用InterfaceA的 funD() 方法。問題解決了!
3.繼承
通過獲取現有類的(超類)成員並可能重新定義其默認實現來創建新類(子類)。 這種機制在面向對象編程(OOP)中被稱為繼承。使Kotlin如此精彩的壹件事是,它包含了面向對象和函數式編程範例 - 所有這些都在壹種語言中。
Kotlin中所有類的基類都是Any。
class Person : Any { }
該Any類型與Object我們在Java中使用的類型相同。
public open class Any { public open operator fun equals(other: Any?): Boolean public open fun hashCode(): Int public open fun toString(): String }
該Any
類型包含以下成員: equals
(),hashcode
()以及toString
()方法(類似於Java)。
我們的類不需要明確地擴展這種類型。如果您沒有明確指定新類繼承的類,則該類將Any隱式擴展。 出於這個原因,您通常不需要包含: Any 在代碼中,我們在上面的代碼中進行演示。 .
現在讓我們考慮在Kotlin中創建類,並考慮繼承。
class Student { } class GraduateStudent : Student() { }
在上面的代碼中,GraduateStudent
類擴展了超類Student
。但是這段代碼不會編譯。為什麽?因為類和方法final
默認在Kotlin中。 換句話說,默認情況下它們不能被擴展 - 不像默認情況下打開類和方法的Java。
軟件工程最佳實踐建議您final 默認開始制作類和方法 - 即,如果它們不是專門用於在子類中重新定義或重寫的。Kotlin團隊(JetBrains)在開發這種現代語言時運用了這種編碼理念和更多開發最佳實踐。
對於我們允許從超類創建子類,我們必須用open修飾符明確地標記超類 。此修飾符也適用於應由子類覆蓋的任何超類屬性或方法。
open class Student { }
我們只需open
在class
關鍵字前加上修飾符。我們現在已經指示編譯器允許我們的Student
類可以被擴展。
如前所述,Kotlin課程的成員默認也是最終的。
open class Student { open fun schoolFees(): BigDecimal { // do implementation } }
在前面的代碼中,我們將該schoolFees函數標記為 - open子類可以覆蓋它。
open class GraduateStudent : Student() { override fun schoolFees(): BigDecimal { return super.schoolFees() + calculateSchoolFees() } private fun calculateSchoolFees(): BigDecimal { // calculate and return school fees } }
在這裏,Student
超類的開放函數schoolFees
被GraduateStudent
類重寫- 通過override
在fun
關鍵字之前添加修飾符。請註意,如果您重寫超類或接口的成員,則覆蓋成員也將為open
默認值,如下例所示:
class ComputerScienceStudent : GraduateStudent() { override fun schoolFees(): BigDecimal { return super.schoolFees() + calculateSchoolFess() } private fun calculateSchoolFess(): BigDecimal { // calculate and return school fees } }
盡管我們沒有用修飾符open
在GraduateStudent
類中標記方法schoolFees
(),但我們仍然可以重寫它 - 就像我們在ComputerScienceStudent
類中所做的那樣。為了防止這種情況發生,我們必須將最重要的成員標記為final
。
請記住,我們可以通過在Kotlin中使用擴展函數向類添加新功能,即使它是最終的。有關擴展功能的更新,請查看我在Kotlin post中的高級功能。 另外,如果您需要了解如何在不繼承最終類新屬性的情況下進行回顧,請參閱高級屬性和類 帖子中關於擴展屬性的部分 。
如果我們的超類具有這樣的主構造函數:
open class Student(val firstName: String, val lastName: String) { // ... }
然後任何子類都必須調用超類的主構造函數。
open class GraduateStudent(firstName: String, lastName: String) : Student(firstName, lastName) { // ... }
我們可以GraduateStudent照常創建壹個類的對象:
val graduateStudent = GraduateStudent("Jon", "Snow") println(graduateStudent.firstName) // Jon
如果子類想從其次級構造函數調用超類構造函數,那麽我們使用super關鍵字(類似於如何在Java中調用超類構造函數)。
open class GraduateStudent : Student { // ... private var thesis: String = "" constructor(firstName: String, lastName: String, thesis: String) : super(firstName, lastName) { this.thesis = thesis } }
如果妳需要復習Kotlin中的類構造函數,請訪問我的 類和對象 文章。
4.獎金:輸入別名
我們可以在Kotlin中做的另壹件令人敬畏的事情是給壹個類型壹個別名。
我們來看壹個例子。
data class Person(val firstName: String, val lastName: String, val age: Int)
在上面的類中,我們可以使用Kotlin中的修飾符為屬性別名分配String
和Int
類型 。該修飾符用於在Kotlin中創建任何類型的別名 - 包括您創建的別名。
typealias Name = String typealias Age = Int data class Person(val firstName: Name, val lastName: Name, val age: Age)
正如妳所看到的,我們已經創建了壹個別名Name
和Age
在,類型分別為String
和Int
類型。 現在我們已更換firstName 和lastName 屬性類型,以我們的別名Name-和Int類型Age別名。
當您想要為您的Kotlin代碼庫中的類型提供更好的含義或語義時,這些可以很方便。所以明智地使用它們!
結論
在本教程中,您了解了Kotlin中更多關於面向對象編程的知識。我們涵蓋以下內容:
- 抽象類
- 接口
- 遺產
- 類型別名
如果您通過我們的Kotlin From Scratch系列學習了Kotlin,請確保您輸入了您看到的代碼並在IDE上運行它。真正掌握壹門新的編程語言(或任何編程概念)的壹個很好的技巧就是要確保妳不只是閱讀學習資源或指南,而且還要輸入實際的代碼並運行它!
在Kotlin From Scratch 系列的下壹個教程中 ,您將介紹Kotlin中的異常處理。再見!
要了解有關Kotlin語言的更多信息,我建議訪問 Kotlin文檔。或者在Envato Tuts上查看我們的其他壹些Android應用程序開發帖子!
- ANDROID SDKJava vs. Kotlin:妳應該使用Kotlin進行Android開發嗎?Jessica Thornsby
- ANDROID SDKAndroid體系結構組件介紹Tin Megali
- Android SDK開始使用適用於Android的RxJava 2Jessica Thornsby
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post