Kotlin使用心得(六):data class

Carter Chen
7 min readJun 6, 2018
Photo by Kelly Sikkema on Unsplash

寫在前面

本篇要探討Kotlin中獨特的「data class」與一般的Class有何不同。
以下我們將以Player這個類別為例,看看使用data class前後的差異。

前情提要

先來看看我們的Player類別:

為了方便區分出有使用以及未使用data class的Player類別,先進行以下改寫:

一般class(未使用data class):

data class版(其實也只有在class前面加了個data):

我們來取得屬性看看,可以發現不論是class或是data class,取得的屬性數值都一樣:

那究竟還有什麼地方不一樣呢?讓我們繼續看下去~

Diff 1:data class覆寫了toString()方法

data class會override toString()方法,變成對我們比較friendly的顯示方式:

Diff 2:data class新增了componentN()方法

data class新增了componentN的方法,而N取決於這個class有多少個屬性。
也因為這個特性,讓我們可以按照順序的從Destructuring Declarations去取得我們要的變數:

Diff 3:data class新增了copy()方法

data class新增了copy()方法,使用這種方式可以快速產生一個同樣結構的class。而且只要在copy()方法中指定屬性與要修改的數值,就可以快出產生出一個略為不同的孿生兄弟:

Diff 4:data class覆寫了hashCode()方法

先來看一下程式碼:

從程式碼中可以觀察到:

  • 類型為PlayerClassplayer1player2使用相同的參數初始化,兩者調用equals()的結果為「false」
  • 類型為DataPlayerClassdataClassPlayer1dataClassPlayer2使用相同的參數初始化,兩者調用equals()的結果為「true」

Why?

判斷兩個物件「相不相同」,是個很大的學問。這個學問牽涉到了「hashCode」與「equals」,讓我們先來看一下hashCode在文件中的定義:

The general contract of hashCode is:

Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

簡單的說就是:

  1. 在Java應用程式執行期間,對一個物件呼叫hashCode()方法應該都傳回同一個integer。
  2. 如果兩個物件調用equals()得到相同的結果,那這兩個物件呼叫hashCode()的結果應該也要一樣。
  3. 如果兩個物件調用equals()得到不同的結果,那這兩個物件呼叫hashCode()的結果不一定要不一樣。

可以看到一般的class本身就設計為一個現存的物件的hashCode一定和「新創造出來」的不一樣,所以就算類型屬性相同,呼叫hashCode()時還是傳回了不同的數值,而在Kotlin的data class中,data class按照了類型中的屬性自行去override了hashCode,讓屬性相同的類型可以傳回相同的hashCode,符合了eqauls()方法中要求的條件。

每一個一般的class在Java中,預設都extend了Object類別。也就是說,Java中的每個類別,在未override equals()方法的前提下,都是呼叫Objecteqauls()方法,其實作代碼如下:

public boolean equals(Object obj) {
return (this == obj);
}

可以看到在Java中預設是如何去判斷兩個物件是否相等,即使用==來判斷兩個物件reference的記憶體,是不是在同一個地址,也可以歸納為,一般的class在比較的對象為:記憶體位址。

Kotlin中的data class比較的對象為何呢?他比較的,是該類型中資料本身。一個data class中會有多個屬性,可以看成是這個類別中夾帶的「資料」;使用equals來比較兩個data class的實例時,它比較的並不是記憶體位址,而是在比較「兩個實例中夾帶的資料一不一樣」,所以就不難看出範例中屬性一樣的類型會被判斷為是相等的了。

同場加映:Kotlin中的Equality

Java中的Equality分為兩種,分別是:

  • Structural Equality(結構性的,和equals()有關)
  • Referential Equality(參考性的,和記憶體位址有關)

在Kotlin中也是一樣,也分為上述兩種,只是符號有些許差異:

Java:

  • 判斷是否為Structural Equality,使用equals()
  • 判斷是否為Referential Equality,使用==

Kotlin:

  • 判斷是否為Structural Equality,使用equals()==
  • 判斷是否為Referential Equality,使用===

因為data class override了equals(),將預設判斷是否為Referential Equality更改為判斷是否為Structural Equality,但只要使用===進行判斷,就可以知道其實兩個data class實例使用的記憶體位址還是不一樣的:

這篇寫比較亂,要優化修改的地方太多了,不如就改天吧。

--

--