Birthday Sale! Up to 40% off unlimited courses & creative assets Birthday Sale! Save up to 40%!
Advertisement
  1. Code
  2. Kotlin
Code

Kotlin從頭開始:高級屬性和類

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Classes and Objects
Kotlin From Scratch: Abstract Classes, Interfaces, Inheritance, and Type Alias

Chinese (Traditional) (中文(繁體)) translation by Zhang Xiang Liang (you can also view the original English article)

Kotlin  是壹種編譯為Java字節碼的現代編程語言。它是免費的,  開源,並有望使編碼為Android更有趣。 

在  前面的文章中,您了解了Kotlin類和對象。在本教程中,我們將繼續學習更多關於屬性的內容,並通過探索以下內容來深入了解Kotlin中的高級類型: 

  • 後期初始化的屬性
  • 內聯屬性
  • 擴展屬性
  • 數據,枚舉,嵌套和密封類

1.後期初始化屬性

我們可以在Kotlin中聲明壹個非空屬性作為後期初始化。這意味著非空屬性在聲明時不會被初始化,並且值實際的初始化不會通過任何構造函數發生 - 相反,它將通過方法或依賴註入來延遲初始化。 

我們來看壹個例子來理解這個獨特的屬性修飾符。

在上面的代碼中,我們聲明了壹個可變的nullable repository屬性,它是類型RepositoryPresenter- 在類中- 然後我們在聲明過程中將此屬性初始化為null。 我們 在Presenter類中有壹個initRepository()方法,稍後用實際的Repository實例重新初始化這個屬性。   我們必須執行空檢查或使用安全調用操作符。為什麽?

請註意,該屬性也可以使用像Dagger這樣的依賴註入器來賦值。     因為該repository屬性是可空類型(Repository?)。(如果您需要進壹步了解Kotlin的可空性,請訪問Nullability,Loops和Conditions)。

為了避免每次我們需要調用屬性方法時進行空檢查,我們可以使用lateinit修飾符標記該屬性  - 這意味著我們已經聲明該屬性(這是另壹個類的實例)為後期初始化的(意思是屬性稍後將被初始化)。   

現在,只要我們等待該屬性被賦予壹個值,我們就可以安全地訪問該屬性的方法而不進行任何空的檢查。屬性初始化可以通過setter方法或通過依賴註入來實現。   

請註意,如果我們在初始化之前嘗試訪問屬性的方法,我們將得到壹個  kotlin.UninitializedPropertyAccessException 而不是壹個  NullPointerException。在這種情況下,異常消息將是“lateinit屬性存儲庫尚未初始化”。   

當使用以下命令延遲屬性初始化時,還要註意以下限制lateinit

  • 它必須是可變的(用var聲明)。
  • 屬性類型不能是基本類型,例如,  IntDoubleFloat,等。
  • 該屬性不能有自定義的getter或setter。

2.  內聯屬性

在  高級函數中,我引入了inline高階函數的修飾符 - 這有助於優化任何接受lambda作為參數的高階函數。 

在Kotlin中,我們也可以inline在屬性上使用這個修飾符。使用此修飾符將優化對該屬性的訪問。 我們來看壹個實際的例子。 

我們來看壹個實際的例子。

在上面的代碼中,我們有壹個普通的屬性,  nickName,沒有  inline修飾符。如果我們反編譯代碼片段,使用Show Kotlin Bytecode功能(如果您使用的是IntelliJ IDEA或Android Studio,請使用  Tools  > Kotlin > Show Kotlin Bytecode),我們將看到以下Java代碼: 

在上面生成的Java代碼中(為了簡潔,刪除了生成代碼的某些元素),您可以看到在該main()方法內部,編譯器創建了壹個Student名為getNickName() 方法的對象,然後打印其返回值。   

現在我們來指定該屬性  inline ,並比較生成的字節碼。

我們只需在變量varval修飾符之前插入inline  修飾符:  。 以下是為這個內聯屬性生成的字節碼:

再次刪除了壹些代碼,但要註意的關鍵是該main()方法。編譯器復制屬性get()函數體,並將其粘貼到調用點(這種機制類似於內聯函數)。 

我們的代碼已經過優化,因為不需要創建對象並調用屬性getter方法。但是,正如在內聯函數中所討論的那樣,我們會有比以前更大的字節碼,所以謹慎使用。 

還要註意,這種機制可以用於沒有後臺字段的屬性(記住,當妳想修改或使用字段數據時,後臺字段只是屬性使用的字段)。 

3.  擴展屬性

在  高級函數中,  我還討論了擴展函數 - 這些擴展函數使我們能夠擴展具有新功能的類,而不必從該類繼承。Kotlin還為屬性提供了壹種類似的機制,稱為  擴展屬性。 

在  高級函數後,  我們 用接收器String類型定義了壹個擴展函數uppercaseFirstLetter() 。  在這裏,我們已將其轉換為頂級擴展屬性。請註意,您必須在您的屬性上定義壹個getter方法才能使其工作。 

因此,借助關於擴展屬性的新知識,您將知道如果您希望某個類應該有壹個不可用的屬性,則可以自由創建該類的擴展屬性。 

4.  數據類

讓我們從壹個典型的Java類或POJO(Plain Old Java Object)開始。

正如妳所看到的,我們需要明確的代碼的類屬性訪問器:getter和setter,以及  hashcode,  equalstoString 方法(雖然IntelliJ IDEA的,Android的工作室,或AutoValue庫可以幫助我們來生成)。   我們在壹個典型的Java項目的數據層中主要看到這種樣板代碼。(為了簡潔起見,我刪除了字段訪問器和構造器)。

很酷的是,Kotlin團隊為我們提供了data修改器,以避免編寫這些樣板文件。 

現在讓我們在Kotlin中寫上前面的代碼。 

真棒!我們只是dataclass 關鍵字之前指定  修飾符  來創建數據類,就像我們在BlogPost 上面的Kotlin類中所做的壹樣  。 現在  equals,  hashcode,  toString,  copy,和多組分方法將引擎蓋為我們下創建的。請註意,數據類可以擴展其他類(這是Kotlin 1.1的新功能)。

equals 方法

該方法比較兩個對象是否相等,如果相等則返回true,否則返回false。換句話說,它比較兩個類實例是否包含相同的數據。 

在Kotlin中,使用相等運算符==將在幕後調用equals方法。

hashCode 方法

此方法返回在用於快速存儲,並存儲在基於散列的集合數據結構的數據的檢索,例如壹個整數值  HashMap 和  HashSet 集合類型。 

toString 方法

這個方法返回壹個String 對象的  表示。

通過調用類實例,我們得到壹個返回給我們的字符串對象 - Kotlin為我們調用toString() 對象  。但是如果我們不把data 關鍵字放進去  ,看看我們的對象字符串表示是什麽: 

更少的信息!

copy 方法

這個方法允許我們創建壹個具有所有相同屬性值的對象的新實例。換句話說,它創建了對象的副本。

Kotlin中關於copy方法的壹件很酷的事情就是在復制期間改變屬性的能力。

如果您是Java編碼人员,則此方法與clone() 您已熟悉的方法類似  。但是Kotlin  copy 方法具有更強大的功能。 

破壞性聲明

在  Person 類中,由於data 關鍵字放置在類中,因此編譯器會自動為我們生成兩個方法  。這兩個方法以“component”為前綴,後跟壹個數字後綴:  component1(),  component2()。 每種方法都表示該類型的各個屬性。請註意,後綴對應於在主構造函數中聲明的屬性的順序。

因此,在我們的示例中,調用  component1() 將返回名字,並且調用  component2() 將返回最後的名字。 

盡管使用這種風格調用屬性很難理解和閱讀,所以明確調用屬性名稱要好得多。但是,這些隱式創建的屬性確實有壹個非常有用的用途:它們讓我們執行壹個解構聲明,其中我們可以將每個組件分配給壹個局部變量。 

我們在這裏所做的是直接將Person類型的第壹個和第二個屬性(firstName 和  lastName)   分別賦值給變量firstName 和  變量  lastName。我還在Packages和Basic Functions  的最後壹部分  討論了稱為解構聲明的機制   。   

5.  嵌套類

在 文章 更有趣的函數,我告訴妳,Kotlin支持本地函數或嵌套函數 - 這是在另壹個函數內聲明的函數。那麽,Kotlin也同樣支持嵌套類 - 在另壹個類中創建的類。 

我們甚至可以調用嵌套類的公共函數,如下所示 - Kotlin中的static 嵌套類相當於Java中的  嵌套類。請註意,嵌套類不能存儲對其外部類的引用。 

我們也可以自由地將嵌套類設置為私有 - 這意味著我們只能NestedClass 在該範圍內  創建壹個實例  OuterClass。 

內部類

內部類,在另壹方面,可以參考它在聲明的外部類。要創建壹個內部類,我們把該  inner 關鍵字前  class 在嵌套類的關鍵字。 

在這裏,我們引用  OuterClass 從  InnerClass 使用   this@OuterClass

6.  枚舉類

枚舉類型聲明由標識符表示的壹組常量。這種特殊的類是由關鍵字enum之前指定的class關鍵字創建的。 

要根據名稱檢索枚舉值(就像在Java中壹樣),我們這樣做:

或者我們可以使用Kotlin enumValueOf()輔助方法以通用方式訪問常量:

另外,我們可以像這樣獲得所有的值(例如Java枚舉類型):

最後,我們可以使用Kotlin enumValues()輔助方法以通用方式獲取所有枚舉條目:

這將返回壹個包含枚舉條目的數組。  

枚舉構造函數

就像壹個普通的類壹樣,enum類型可以有自己的構造函數,其屬性與每個枚舉常量相關聯。

Country枚舉類型的主構造函數中,我們callingCodes為每個枚舉常量定義了不可變屬性。在每個常量中,我們向構造函數傳遞了壹個參數。 

然後我們可以像這樣訪問常量屬性:

7.密封類

Kotlin中的密封類是壹個抽象類(妳從來不打算從它創建對象),其他類可以擴展。這些子類是在密封的類體中定義的 - 在同壹個文件中。 因為所有這些子類都是在密封的類體內定義的,所以我們可以通過查看文件來了解所有可能的子類 。 

我們來看壹個實際的例子。

為了將類聲明為密封類,我們在類聲明頭中的sealed修飾符之前插入修飾符class- 在我們的例子中,我們將該Shape類聲明為sealed。壹個密封的類沒有它的子類是不完整的 - 就像壹個典型的抽象類 - 所以我們必須在同壹個文件中聲明各個子類(在這種情況下是shape.kt)。 請註意,您不能從另壹個文件中定義密封類的子類。  請註意,您不能從另壹個文件中定義密封類的子類。 

在上面的代碼中,我們指定的Shape類只能通過類來擴展  Circle,  Triangle和  Rectangle

Kotlin的密封課程有以下附加規則:

  • 我們可以將修飾符添加  abstract 到密封類,但這是多余的,因為密封類默認是抽象的。
  • 密封的類不能有  openfinal修飾符。
  • 我們也可以自由地將數據類和對象聲明為壹個密封類的子類(它們仍然需要在同壹個文件中聲明)。
  • 密封類不允許使用公共構造函數 - 它們的構造函數默認是私有的。

擴展密封類的子類的類可以放在同壹個文件或其他文件中。密封的類子類必須使用open 修飾符標記  (在下壹篇文章中,您將在Kotlin中學習更多關於繼承的知識)。 

壹個密封的類和它的子類在when表達式中非常方便。例如:

這裏編譯器很聰明,可以確保我們涵蓋了所有可能的  when情況。這意味著沒有必要添加該else 條款。 

如果我們要這樣做:

代碼不會編譯,因為我們沒有包含所有可能的情況。我們會遇到以下錯誤:

因此,我們可以包括is Rectangle案例或包含else條款來完成when表達式。

結論

在本教程中,您了解了更多關於Kotlin的課程。我們介紹了關於類屬性的以下內容:

  • 後期初始化
  • 內聯屬性
  • 擴展屬性

此外,您還了解了壹些很酷和高級的類,例如數據,枚舉,嵌套和密封類。在Kotlin From Scratch系列的下壹個教程中,您將介紹Kotlin中的接口和繼承。再見! 

要了解有關Kotlin語言的更多信息,我建議訪問  Kotlin文檔。或者在Envato Tuts上查看我們的其他壹些Android應用程序開發帖子! 

关注我们的公众号
Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.