Chinese (Traditional) (中文(繁體)) translation by Zhang Xiang Liang (you can also view the original English article)
Kotlin是壹種編譯為Java字節碼的現代編程語言。它是免費的, 開源,並有望使Android開發更有趣。
在 前面的文章中,您學習了Kotlin中函數的高級用法,例如擴展函數,閉包,高階函數和內聯函數。
在本文中,您將通過了解Kotlin中的面向對象編程入門:構造函數和屬性,轉換以及Kotlin變得更加簡單的更高級的類功能。
1. 類
類是壹個程序單元,將功能和數據分組在壹起以執行壹些相關的任務。我們使用class關鍵字(類似於Java)在Kotlin中聲明壹個類。
class Book
前面的代碼是最簡單的類聲明 - 我們只創建了壹個名為的空類 Book。即使它不包含使用其默認構造函數的主體,我們仍然可以實例化此類。
val book = Book()
正如妳可以在上面的代碼中看到的那樣,我們沒有使用new關鍵字來實例化這個類,就像在其他編程語言中那樣。 new在Kotlin中不是關鍵字。 這使得我們的源代碼在創建類實例時變得簡潔。但請註意,在Java中實例化Kotlin類將需要new關鍵字。 但請註意,在Java中實例化Kotlin類將需要new關鍵字。
// In a Java file Book book = new Book()
類構造函數和屬性
讓我們來看看如何為我們的類添加構造函數和屬性。但首先,讓我們看壹下Java中的典型類:
/* Java */ public class Book { private String title; private Long isbn; public Book(String title, Long isbn) { this.title = title; this.isbn = isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Long getIsbn() { return isbn; } public void setIsbn(Long isbn) { this.isbn = isbn; } }
讓我們來看看如何為我們的類添加構造函數和屬性。但首先,讓我們看壹下Java中的典型類:
- 兩個領域:
title
和isbn
- 壹個構造函數
- 這兩個領域的getter和setter(幸運的是,IntelliJ IDEA可以幫助我們生成這些方法)
現在讓我們看看我們如何在Kotlin中編寫前面的代碼:
/* Kotlin */ class Book { var title: String var isbn: Long constructor(title: String, isbn: Long) { this.title = title this.isbn = isbn } }
壹個相當整潔的類!我們現在已經將代碼行數從20減少到了9.該constructor()函數在Kotlin中稱為次級構造函數。 這個構造函數等同於我們在實例化類時調用的Java構造函數。
在Kotlin,沒有像妳可能熟悉的領域概念; 相反,它使用“屬性”的概念。
例如,我們已經宣布與兩個可變(讀寫)性能var
關鍵字:title
和isbn
在Book
類。 (如果您需要對Kotlin中的變量進行更新,請參閱本系列的第壹篇文章: 變量,基本類型和數組。)
令人驚奇的是,這些屬性的getters和setter是由Kotlin編譯器為我們自動生成的。 請註意,我們沒有為這些屬性指定任何可見性修飾符,因此默認情況下它們是公共的。換句話說,他們可以從任何地方訪問。
讓我們來看看Kotlin中同壹個類的另壹個版本:
class Book constructor(title: String, isbn: Long) { var title: String var isbn: Long init { this.title = title this.isbn = isbn } }
在這段代碼中,我們刪除了第二個構造函數。 相反,我們在稱為主構造函數的類頭中聲明了壹個構造 函數。 請註意, init 代碼塊在創建類實例時立即執行。 正如妳所看到的,我們的代碼仍然有很多樣板。
正如妳所看到的,我們的代碼仍然有很多樣板。
class Book constructor(var title: String, var isbn: Long)
我們的Book
類現在只是壹行代碼。太棒了!請註意,在主構造函數參數列表中,我們定義了可變屬性: title
並isbn
直接在主構造函數中使用var關鍵字。
我們也可以將默認值添加到構造函數中的任何類屬性中。
class Book constructor(var title: String = "default value", var isbn: Long)
事實上,我們也可以省略 constructor
關鍵字,但只有當它沒有任何可見性修飾符(public
,private
,或 protected
)或任何註釋。
class Book (var title: String = "default value", var isbn: Long)
壹個非常整潔的類,我必須說!
我們現在可以像這樣創建壹個類實例:
val book = Book("A Song of Ice and Fire", 9780007477159) val book2 = Book(1234) // uses the title property's default value
访问和设置属性
在Kotlin中,我們可以通過類對象獲取屬性book,然後是點分隔符 .,然後是屬性名稱title。這種訪問屬性的簡潔風格稱為 屬性訪問語法。 換句話說,我們不必調用屬性getter方法來訪問或調用setter來設置Kotlin中的屬性,就像我們在Java中所做的那樣。
println(book.title) // "A Song of Ice and Fire"
由於該 isbn
屬性是使用 var 關鍵字(讀寫)聲明的,因此我們也可以使用賦值運算符更改屬性值=
。
book.isbn = 1234 println(book.isbn) // 1234
我們來看另壹個例子:
class Book ( var title: String, val isbn: Long ) val book = Book("A Song of Ice and Fire", 9780007477159) book.isbn = 1234 // error: read-only property book.title = "Things Fall Apart" // reassigned title with value
在這裏,我們isbn
使用val
關鍵字將參數更新為不可變(只讀)。我們實例化了壹個類實例,book
並重新分配了title
屬性值“Things Fall Apart Apart”。 請註意,當我們試圖重新分配isbn屬性值時1234,編譯器抱怨道。這是因為該屬性是不可變的,已經用val關鍵字定義。
Java互操作性
請註意,通過var 在主構造函數中聲明帶有修飾符的參數 ,Kotlin編譯器(幕後)幫助我們生成了屬性訪問器:getter和setter。如果妳使用 val,它將只產生getter。
/* Kotlin */ class Book ( var title: String, val isbn: Long )
這意味著Java調用者可以通過分別調用屬性的setter或getter方法來簡單地獲取或設置屬性字段。請記住,這取決於用於定義Kotlin屬性的修飾符:var或val。
/* Java */ Book book = new Book("A Song of Ice and Fire", 9780385474542) println(book.getTitle()) // "A Song of Ice and Fire" book.setTitle("Things Fall Apart") // sets new value println(book.getTitle()) // "Things Fall Apart" book.getIsbn() // 9780385474542 book.setIsbn(4545454) // won't compile
自定義Getters和Setters
在本節中,我將向您展示如何在Kotlin中為屬性創建自定義訪問器(getter和setter)。 如果要在將值設置為類屬性之前驗證或驗證值,則創建自定義設置器可能很有用。並且,當您想要更改或修改應該返回的值時,自定義屬性獲取器可能很有用。
創建壹個自定義設置器
因為我們要為屬性創建自己的自定義getter或setter,所以我們必須在類體中定義該屬性,而不是構造器標題。
class Book (val isbn: Long) { var title = "default value" }
這就是為什麽我們將mutable(讀寫) title 屬性移動到類體中並給它壹個默認值(否則它不會編譯)。
class Book (val isbn: Long) { var title = "default value" set(value) { if (!value.isNotEmpty()) { throw IllegalArgumentException("Title must not be empty") } field = value } }
妳可以看到我們 在屬性定義set(value)
的 title右下方定義了我們自己的setter
方法 - 註意妳不能修改這個 set()
方法簽名,因為這是編譯器期望的自定義屬性設置函數。
value
傳遞給該 set
方法的參數 表示由用戶分配給該屬性的實際值 - 如果願意,可以更改該參數名稱,但是 value
更受歡迎。 我們 value 通過檢查值是否為空來驗證。如果為空,則停止執行並拋出異常; 否則,將該值重新分配給壹個特殊 field 變量。
我們 value
通過檢查值是否為空來驗證。如果為空,則停止執行並拋出異常; 否則,將該值重新分配給壹個特殊 field
變量。
創建壹個自定義的Getter
為Kotlin中的壹個屬性創建壹個自定義getter非常簡單。
class Book (val isbn: Long) { var title = "default value" //... set method get() { return field.toUpperCase() } }
在該get
方法內部,我們簡單地返回壹個修改 field
- 在我們的情況下,我們用大寫字母返回了書名。
val book = Book(9780007477159) book.title = "A Song of Ice and Fire" println(book.title) // "A SONG OF ICE AND FIRE" println(book.isbn) // 9780007477159
請註意,每次我們為該title屬性設置壹個值時,其set方法塊都會執行 - get 每次我們檢索時,方法都是壹樣的。
如果您想了解Kotlin類(在類,對象或接口中定義的函數類型)的成員函數,請訪問 本系列中的More Fun With Functions發布。
更多關於構造函數
正如我前面所討論的,我們在Kotlin中有兩種類型的構造函數:小學和中學。我們可以自由地將它們結合在壹個類中,如下例所示:
class Car(val name: String, val plateNo: String) { var new: Boolean = true constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) { this.new = new } }
請註意,我們不能像在主構造函數中那樣在次構造函數中聲明屬性。如果我們想這樣做,我們必須在類體內聲明它,然後在二級構造函數中初始化它。
在上面的代碼中,我們設置了new
該類的屬性的默認值Car
(請記住,new
不是Kotlin中的關鍵字) - 然後,如果需要,我們可以使用輔助構造函數進行更改。在Kotlin中,每個輔助構造函數都必須調用主構造函數,或者調用另壹個調用主構造函數的輔助構造函數 - 我們使用this
關鍵字來實現該構造函數 。
還要註意,我們可以在壹個類中有多個二級構造函數。
class Car(val name: String, val plateNo: String) { var new: Boolean? = null var colour: String = "" constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) { this.new = new } constructor(name: String, plateNo: String, new: Boolean, colour: String ) : this(name, plateNo, new) { this.colour = colour } }
如果壹個類擴展了壹個超類,那麽我們可以使用super關鍵字(類似於Java)來調用超類的構造函數(我們將在未來的帖子中討論Kotlin中的繼承)。
// directly calls primary constructor val car1 = Car("Peugeot 504", "XYZ234") // directly calls 1st sec. constructor val car2 = Car("Peugeot 504", "XYZ234", false) // directly calls last sec. constructor val car3 = Car("Peugeot 504", "XYZ234", false, "grey")
正如我前面所說,為了我們明確地將可見性修飾符包含到類中的構造函數中,我們必須包含constructor關鍵字 - 默認情況下,構造函數是公共的。
class Car private constructor(val name: String, val plateNo: String) { //...
在這裏,我們將構造函數設為private - 這意味著用戶無法直接使用其構造函數實例化對象。如果您希望用戶調用另壹個方法(工廠方法)間接創建對象,這可能很有用。
2.任何和無類型
在Kotlin中,類型層次結構中最高的類型被調用Any
。這相當於Java Object
類型。 這意味著,在科特林所有類明確地從繼承 Any的類型,包括 String
, Int
,Double
,等等。該Any類型包含三種方法:
equals
, toString
,和hashcode
。
我們還可以Nothing在Kotlin中的類中使用總是返回異常的函數 - 換句話說,對於不能正常終止的函數。當函數返回時Nothing,我們知道它會拋出異常。在Java中不存在這種類型。 當函數返回時Nothing,我們知道它會拋出異常。在Java中不存在這種類型。
fun throwException(): Nothing { throw Exception("Exception message) }
在單元測試中測試錯誤處理行為時,這可以派上用場。
3.可見性修飾符
可見性修飾符幫助我們限制我們的API對公眾的可訪問性。我們可以為我們的類,接口,對象,方法或屬性提供不同的可見性修飾符。Kotlin為我們提供了四種可見性修飾符:
Public
這是默認設置,任何具有此修飾符的類,函數,屬性,接口或對象都可以從任何地方訪問。
Private
聲明為頂級函數,接口或類private只能在同壹個文件中訪問。
private在類,對象或接口內聲明的任何函數或屬性只能對同壹類,對象或接口的其他成員可見。
class Account { private val amount: Double = 0.0 }
Protected
所述protected 改性劑可以僅被應用到性能或功能內的類,對象或接口不能被應用於頂層函數,類或接口。使用此修飾符的屬性或函數只能在定義它的類和任何子類中訪問。
Internal
在具有模塊(Gradle或Maven模塊)的項目中,internal 只能在該模塊中訪問使用該模塊內聲明的修飾符指定的類,對象,接口或函數。
internal class Account { val amount: Double = 0.0 }
4.智能Casting
Casting意味著采用另壹種類型的對象並將其轉換為另壹種對象類型。例如,在Java中,我們使用instanceof運算符來確定特定的對象類型是否屬於其他類型,然後再進行轉換。
/* Java */ if (shape instanceof Circle) { Circle circle = (Circle) shape; circle.calCircumference(3.5); }
正如妳所看到的,我們檢查了shape
實例是否是Circle
,然後我們必須明確地將shape
引用轉換為Circle
類型,以便我們可以調用circle
類型的方法。
關於Kotlin的另壹個令人敬畏的事情是編譯器在編譯時的智能。現在讓我們看看Kotlin的壹個版本。
/* Kotlin */ if (shape is Circle) { shape.calCircumference(3.5) }
很簡約!編譯器很聰明,知道if
只有當shape
對象是壹個實例時才會執行該塊Circle
,因此投影機制在我們的引擎下完成。 現在我們可以輕松地調用Circle該if塊內的屬性或類型的函數。
if (shape is Circle && shape.hasRadius()) { println("Circle radius is {shape.radius}") }
這裏,在最後壹個條件&&
的if
頭會被調用,只有當第壹個條件是true
。如果shape
不是Circle
,那麽最後的條件將不會被評估。
5.明確的Casting
我們可以使用as操作符(或不安全的轉換操作符)將類型的引用顯式轉換為Kotlin中的另壹種類型。
val circle = shape as Circle circle.calCircumference(4)
如果顯式的轉換操作是非法的,請註意a ClassCastException
會被拋出。為了防止在投射時拋出異常,我們可以使用安全的投射運算符as
(或可為空值的投射運算符) ?。
val circle: Circle? = shape as? Circle
as?運營商將嘗試轉換為預期的類型,並返回 null 如果該值不能代替投拋出異常。 請記住, 本系列中的Nullability,Loops和Conditions發布中的Nullability部分討論了類似的機制 。閱讀那裏進行復習。
6. 對象
Kotlin中的對象比Java對象更類似於JavaScript對象。請註意,Kotlin中的對象不是特定類的實例!
對象與類非常相似。以下是Kotlin中對象的壹些特征:
- 他們可以擁有屬性,方法和 init塊。
- 這些屬性或方法可以具有可見性修飾符。
- 他們不能有構造函數(小學或中學)。
- 他們可以擴展其他類或實現壹個接口。
現在我們來深入探討如何創建壹個對象。
object Singleton { fun myFunc(): Unit { // do something } }
我們將object關鍵字放在我們想要創建的對象的名稱之前。實際上,當我們使用object構造在Kotlin中創建對象時,我們正在創建單例,因為只有壹個對象的實例存在。 當我們討論與Java的對象互操作性時,您將會學到更多。 當我們討論與Java的對象互操作性時,您將會學到更多。
單例是壹種軟件設計模式,它保證壹個類只有壹個實例,並且該類提供了壹個全局訪問點。 無論何時多個類或客戶要求類,他們都會得到同壹類的實例。 您可以查看我的關於Java中的單例模式的文章以了解更多信息。
您可以訪問項目中任何位置的對象或單例 - 只要您導入它的包。
Singleton.myFunc()
如果您是Java編碼人員,那麽我們通常會創建單例:
public class Singleton { private static Singleton INSTANCE = null; // other instance variables can be here private Singleton() {}; public static synchronized Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return(INSTANCE); } // other instance methods can follow }
正如您所看到的,使用Kotlin object構造可以簡化創建單例。
Kotlin中的對象也可以用來創建常量。通常在Java中,我們通過將其設置為公共靜態final字段來在類中創建常量:
public final class APIConstants { public static final String baseUrl = "https://www.myapi.com/"; private APIConstants() {} }
Java中的這些代碼可以更簡潔地轉換為Kotlin,如下所示:
package com.chike.kotlin.constants object APIConstants { val baseUrl: String = "http://www.myapi.com/" }
在這裏,我們APIConstants
用baseUrl
壹個包內的屬性聲明常量com.chike.kotlin.constants
。在底層,baseUrl
我們為我們創建了壹個Java私有靜態最終成員,並使用字符串URL進行了初始化。
要在Kotlin的另壹個包中使用此常量,只需導入包。
import com.chike.kotlin.constants.APIConstants APIConstants.baseUrl
Java互操作性
Kotlin將對象轉化為引擎蓋下的最終Java類。這個類有壹個私有的靜態字段INSTANCE,它包含壹個類的單個實例(單例)。以下代碼顯示了用戶如何從Java調用Kotlin對象。
/* Java */ Singleton.INSTANCE.myFunc()
在這裏,調用的Java類Singleton是使用公共靜態最終成員生成的INSTANCE,其中包括公共final函數myFunc()。
為了使Kotlin中的對象函數或屬性成為生成的Java類的靜態成員,我們使用@JvmStatic
註釋。以下是如何使用它:
object Singleton { @JvmStatic fun myFunc(): Unit { // do something } }
通過應用@JvmStatic
註釋myFunc
(),編譯器將其轉換為靜態函數。
現在,Java調用者可以像調用普通的靜態成員調用壹樣調用它。請註意,使用INSTANCE靜態字段來調用成員將仍然有效。
/* Java */ Singleton.myFunc()
7. Companion對象
現在我們已經了解了Kotlin中的對象,讓我們深入另壹種稱為伴侶對象的對象。
由於Kotlin不支持靜態類,方法或屬性,就像我們在Java中所使用的那樣,Kotlin團隊為我們提供了壹個更強大的替代方法,稱為伴隨對象。 伴侶對象基本上是壹個屬於類的對象 - 這個類被稱為對象的伴隨類。這也意味著我提到的對象的特征也適用於伴隨對象。
創建伴隨對象
類似於Java中的靜態方法,伴隨對象不與類實例相關聯,而與類自身相關聯 - 例如,工廠靜態方法,它具有創建類實例的工作。
class Person private constructor(var firstName: String, var lastName: String) { companion object { fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) } }
在這裏,我們構造了構造函數private
- 這意味著類外的用戶不能直接創建實例。在我們的伴隨對象塊中,我們有壹個函數create
(),它創建壹個Person
對象並將其返回。
調用伴隨對象函數
companion
對象實例化是懶惰的。換句話說,它只會在第壹次需要的時候被實例化。 companion 當companion 創建壹個類的實例或 companion 訪問對象成員時,就會發生對象 的實例化 。
我們來看看如何在Kotlin中調用伴隨對象函數。
val person = Person.create("Cersei", "Lannister") println(person.firstName) // prints "Cersei"
正如妳所看到的,這就像正常情況下在Java中調用靜態方法壹樣。換句話說,我們只是調用這個類然後調用成員。請註意,除了函數之外,我們還可以在伴隨對象中包含屬性。
class Person private constructor(var firstName: String, var lastName: String) { init { count++ } companion object { var count: Int = 0 fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) init { println("Person companion object created") } } }
另請註意,companion類對其伴隨對象中聲明的所有屬性和函數進行了無限制的訪問,而伴隨對象無法訪問類成員。我們可以init在companion對象內部有壹個代碼塊 - 當創建伴隨對象時立即調用這個代碼塊。
Person.create("Arya", "Stark") Person.create("Daenerys", "Targaryen") println(Person.count)
執行上面的代碼的結果將是:
Person companion object created 2
請記住,只能有壹個類companion對象的單個實例存在。
我們也可以自由地為我們的伴侶對象提供壹個名稱。
// ... companion object Factory { var count: Int = 0 fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) } // ...
在這裏,我們給它起了壹個名字Factory。我們可以在Kotlin中這樣稱呼它:
Person.Factory.create("Petyr", "Baelish")
這種風格是冗長的,所以堅持以前的方式更受歡迎。但是,當從Java調用伴隨對象函數或屬性時,這可能會派上用場。
正如我前面所說,與對象壹樣,伴隨對象也可以包含屬性或函數,實現接口,甚至可以擴展壹個類。
interface PersonFactory { fun create(firstName: String, lastName: String): Person } class Person private constructor(var firstName: String, var lastName: String) { companion object : PersonFactory { override fun create(firstName: String, lastName: String): Person { return Person(firstName, lastName) } } }
在這裏,我們PersonFactory
只有壹個create
()功能的界面。看看我們新的修改後的 companion
對象,它現在實現了這個接口(您將在後面的文章中了解Kotlin中的接口和繼承)。
Java互操作性
在引擎蓋下,伴隨對象的編譯與Kotlin對象的編譯方式類似。在我們自己的案例中,為我們生成了兩個類:壹個最終Person
類和壹個內部靜態最終類Person$Companion
。
Person類包含稱為最終靜態成員Companion
-這個靜態字段是的壹個目的Person$Companion
內部類。該 Person$Companion
內部類也有自己的成員,其中壹人是被稱為公眾最終功能create
()。
請註意,我們沒有給我們的伴侶對象壹個名字,所以生成的靜態內部類是Companion。如果我們給了它壹個名字,那麽生成的名字就是我們在Kotlin中給出的名字。
/* Java */ Person person = Person.Companion.create("Jon", "Snow");
這裏,Kotlin中的伴隨對象沒有名稱,所以我們使用Companion編譯器為Java調用者提供的名稱來調用它。
@JvmStatic應用於伴隨對象成員的註釋的工作方式與常規對象的工作方式類似。
伴隨對象擴展
與擴展函數如何擴展類的功能類似,我們也可以擴展伴隨對象的功能。(如果您想要復習Kotlin中的擴展函數,請參閱本系列中的高級函數教程)。
class ClassA { companion object { } } fun ClassA.Companion.extFunc() { // ... do implementation } ClassA.extFunc()
在這裏,我們在伴隨對象ClassA.Companion上定義了壹個擴展函數extFunc()。換句話說,extfunc() 是對伴侶對象的擴展。 然後,我們可以調用擴展,就好像它是伴隨對象的成員函數(它不是!)。
在幕後,編譯器將創建壹個靜態實用程序功能extFunc()。接收器對象作為該實用程序函數的參數
結論
在本教程中,您了解了Kotlin中的基本類和對象。我們介紹了關於類的以下內容:
- 類的創建
- 構造方法
- 屬性
- 可見性修飾符
- 智能casting
- 顯式投射
此外,您還了解了Kotlin中的對象和伴隨對象如何輕松替換您在Java中編寫的靜態方法,常量和單例。 但那不是全部! 還有更多需要了解Kotlin的課程。在下壹篇文章中,我將向您展示Kotlin面向對象編程的更多精彩功能。再見!
要了解有關Kotlin語言的更多信息,我建議訪問 Kotlin文檔。或者在Envato Tuts +上查看我們的其他壹些Android應用程序開發帖子!
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