Kotlin——中級篇(六):資料類(data)、密封類(sealed)詳解

Jetictors發表於2018-01-02

Kotlin——中級篇(六):資料類(data)、密封類(sealed)詳解

在前面幾個章節章節中,詳細的講解了Koltin中的介面類(Interface)列舉類(Enmu),還不甚瞭解的可以檢視我的上一篇文章Kotlin——中級篇(五):列舉類(Enum)、介面類(Interface)詳解。當然,在Koltin中,除了介面類、列舉類之外,還有抽象類、內部類、資料類以及密封類。在今天的章節中,為大家詳細講解資料類密封類。在下一章節中,再為大家奉上Kotlin中的抽象類以及內部類的知識。如果還對Kotlin的分類還不清楚的可以檢視我的另一篇博文Kotlin——中級篇(一):類(class)詳解

目錄

Kotlin——中級篇(六):資料類(data)、密封類(sealed)詳解

一、資料類

  • Java中,或者在我們平時的Android開發中,為了解析後臺人員給我們提供的介面返回的Json字串,我們會根據這個字串去建立一個或者例項物件,在這個類中,只包含了一些我們需要的資料,以及為了處理這些資料而所編寫的方法。這樣的類,在Kotlin中就被稱為資料類

1、關鍵字

宣告資料類的關鍵字為:data

1.1、宣告格式

data class 類名(var param1 :資料型別,...){}
複製程式碼

或者

data class 類名 可見性修飾符 constructor(var param1 : 資料型別 = 預設值,...)
複製程式碼

說明:

  • data為宣告資料類的關鍵字,必須書寫在class關鍵字之前。
  • 在沒有結構體的時候,大括號{}可省略。
  • 建構函式中必須存在至少一個引數,並且必須使用valvar修飾。這一點在下面資料類特性中會詳細講解。
  • 引數的預設值可有可無。(若要例項一個無引數的資料類,則就要用到預設值)

例:

// 定義一個名為Person的資料類
data class Preson(var name : String,val sex : Int, var age : Int)
複製程式碼

1.2、約定俗成的規定

  • 資料類也有其約定俗成的一些規定,這只是為增加程式碼的閱讀性。

即,當建構函式中的參過多時,為了程式碼的閱讀性,一個引數的定義佔據一行。

例:

data class Person(var param1: String = "param1",
              var param2: String = "param2", 
              var param3 : String,
              var param4 : Long,
              var param5 : Int = 2,
              var param6 : String,
              var param7 : Float = 3.14f,
              var param8 : Int,
              var param9 : String){
    // exp
    .
    .
    .
}
複製程式碼

1.3、編輯器為我們做的事情

當我們宣告一個資料類時,編輯器自動為這個類做了一些事情,不然它怎麼又比Java簡潔呢。它會根據主建構函式中所定義的所有屬性自動生成下列方法:

  • 生成equals()函式與hasCode()函式
  • 生成toString()函式,由類名(引數1 = 值1,引數2 = 值2,....)構成
  • 由所定義的屬性自動生成component1()、component2()、...、componentN()函式,其對應於屬性的宣告順序。
  • copy()函式。在下面會例項講解它的作用。

其中,當這些函式中的任何一個在類體中顯式定義或繼承自其基型別,則不會生成該函式

2、資料類的特性

資料類有著和Kotlin其他類不一樣的特性。除了含有其他類的一些特性外,還有著其獨特的特點。並且也是資料類必須滿足的條件:

  • 主建構函式需要至少有一個引數
  • 主建構函式的所有引數需要標記為 val 或 var;
  • 資料類不能是抽象、開放、密封或者內部的;
  • 資料類是可以實現介面的,如(序列化介面),同時也是可以繼承其他類的,如繼承自一個密封類。

3、用例項說明其比Java的簡潔性

3.1、資料類的對比

Kotlin版:

data class User(val name : String, val pwd : String)
複製程式碼

Java版:

public class User {
    private String name;
    private String pwd;

    public User(){}

    public User(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
複製程式碼

分析:實現同一個功能,從程式碼量來說,KoltinJava少了很多行程式碼,比起更簡潔。

3.2、修改資料類屬性

例:修改User類的name屬性

Kotlin版:

  • Koltin要修改資料類的屬性,則使用其獨有的copy()函式。其作用就是:修改部分屬性,但是保持其他不變
val mUser = User("kotlin","123456")
println(mUser)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser)
複製程式碼

輸出結果為:

User(name=kotlin, pwd=123456)
User(name=new Kotlin, pwd=123456)
複製程式碼

Java版:

User mUser = new User("Java","123456");
System.out.println(mUser);
mUser.setName("new Java");
System.out.println(mUser);    
複製程式碼

輸出結果為:

User{name='Java', pwd='123456'}
User{name='new Java', pwd='123456'}
複製程式碼

分析:從上面對兩種方式的實現中可以看出,Kotlin是使用其獨有的copy()函式去修改屬性值,而Java是使用setXXX()去修改

4、解構宣告

  • 在前面講到,Kotlin中定義一個資料類,則系統會預設自動根據引數的個數生成component1() ... componentN()函式。其...,componentN()函式就是用於解構宣告的
val mUser = User("kotlin","123456")
val (name,pwd) = mUser
println("name = $name\tpwd = $pwd")
複製程式碼

輸出結果為:

name = kotlin	pwd = 123456
複製程式碼

5、系統標準庫中的標準資料類

  • 標準庫提供了 Pair 和 Triple。儘管在很多情況下命名資料類是更好的設計選擇, 因為它們通過為屬性提供有意義的名稱使程式碼更具可讀性。
  • 其實這兩個類的原始碼部分不多,故而貼出這個類的原始碼來分析分析

5.1、原始碼分析

@file:kotlin.jvm.JvmName("TuplesKt")
package kotlin

// 這裡去掉了原始碼中的註釋
public data class Pair<out A, out B>(
        public val first: A,
        public val second: B) : Serializable {
    
    // toString()方法
    public override fun toString(): String = "($first, $second)"
}

// 轉換
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

// 轉換成List集合
public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)

// 這裡去掉了原始碼中的註釋
public data class Triple<out A, out B, out C>(
        public val first: A,
        public val second: B,
        public val third: C ) : Serializable {

    // toString()方法
    public override fun toString(): String = "($first, $second, $third)"
}

// 轉換成List集合
public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)
複製程式碼

分析:從上面的原始碼可以看出,標準庫中提供了兩個標準的資料類,Pair類以及Triple類.其中:

  • 兩個類中都實現了toList()方法以及toString()方法。
  • to()方法乃Pair類特有,作用是引數轉換
  • Pair類需要傳遞兩個引數,Triple類需要傳遞三個引數。

5.2、用法

val pair = Pair(1,2)        // 例項
val triple = Triple(1,2,3)  // 例項
println("$pair \t $triple") // 列印:即呼叫了各自的toString()方法
println(pair.toList())      // 轉換成List集合
println(triple.toList())    // 轉換成List集合
println(pair.to(3))         // Pair類特有: 其作用是把引數Pair類中的第二個引數替換
複製程式碼

輸出結果為:

(1, 2) 	 (1, 2, 3)
[1, 2]
[1, 2, 3]
((1, 2), 3)
複製程式碼

二、密封類

密封類是用來表示受限的類繼承結構。若還不甚清楚Kotlin的類繼承,請參見我的上一篇文章Kotlin——中級篇(四):繼承類詳解

1、什麼是受限的類繼承結構

  • 所謂受限的類繼承結構,即當類中的一個值只能是有限的幾種型別,而不能是其他的任何型別。
  • 這種受限的類繼承結構從某種意義上講,它相當於是列舉類的擴充套件。但是,我們知道Kotlin的列舉類中的列舉常量是受限的,因為每一個列舉常量只能存在一個例項。若對Kotlin中的列舉類不甚瞭解的,請參見我的另一篇文章Kotlin——中級篇(五):列舉類(Enum)、介面類(Interface)詳解
  • 但是其和列舉類不同的地方在於,密封類的一個子類可以有可包含狀態的多個例項。
  • 也可以說成,密封類是包含了一組受限的類集合,因為裡面的類都是繼承自這個密封類的。但是其和其他繼承類(open)的區別在,密封類可以不被此檔案外被繼承,有效保護程式碼。但是,其密封類的子類的擴充套件是是可以在程式中任何位置的,即可以不在同一檔案下。

上面的幾點內容是密封類的特點,請詳細的看下去,小生會對這幾點內容進行詳細的分析。

2、關鍵字

定義密封類的關鍵字:sealed

2.1、宣告格式

sealed class SealedExpr()
複製程式碼

注意:密封類是不能被例項化的

val mSealedExpr = SealedExpr()  // 這段程式碼是錯誤的,編譯器直接會報錯不能編譯通過。
複製程式碼

既然密封類是不能例項化,那麼我們要怎麼使用,或者說它的作用是什麼呢?請繼續往下看

3、密封類的作用及其詳細用法。

3.1、作用

用來表示受限的類繼承結構。

例:

sealed class SealedExpr{
data class Person(val num1 : Int, val num2 : Int) : SealedExpr()

object Add : SealedExpr()   // 單例模式
object Minus : SealedExpr() // 單例模式
}

// 其子類可以定在密封類外部,但是必須在同一檔案中。`v1.1`之前只能定義在密封類內部
object NotANumber : SealedExpr() 
複製程式碼

分析:即所定義的子類都必須繼承於密封類,表示一組受限的類

3.2、和普通繼承類的區別

  • 我們知道普通的繼承類使用open關鍵字定義,在專案中的類都可繼承至該類。如果你對Koltin的繼承類還不甚瞭解。請參見我的另一篇文章Kotlin——中級篇(四):繼承類詳解
  • 而密封類的子類必須是在密封類的內部或必須存在於密封類的同一檔案。這一點就是上面提到的有效的程式碼保護。

3.3、和列舉類的區別

  • 列舉類的中的每一個列舉常量都只能存在一個例項。而密封類的子類可以存在多個例項。

例:

val mPerson1 = SealedExpr.Person("name1",22)
println(mPerson1)

val mPerson2 = SealedExpr.Person("name2",23)
println(mPerson2)

println(mPerson1.hashCode())
println(mPerson2.hashCode())
複製程式碼

輸出結果為:

Person(name=name1, age=22)
Person(name=name2, age=23)
-1052833328
-1052833296
複製程式碼

3.4、其子類的類擴充套件例項

  • Kotlin支援擴充套件功能,其和C#Go語言類似。這一點是Java沒有的。如果你還對Koltin中的擴充套件功能還不甚清楚的。請參見我的另一篇博文Kotlin——擴充套件功能詳解

為了演示密封類的子類的擴充套件是可以在專案中的任何位置這個功能,大家可以下載原始碼。原始碼連結在文章末尾會為大家奉上。 例:

// 其存在於SealedClassDemo.kt檔案中

sealed class SealedExpr{
    data class Person(val name : String, val age : Int) : SealedExpr()
    object Add : SealedExpr()
    companion object Minus : SealedExpr()
}

object NotANumber : SealedExpr()

其存在TestSealedDemo.kt檔案中

fun  <T>SealedExpr.Add.add(num1 : T, num2 : T) : Int{
    return 100
}

fun main(args: Array<String>) {
    println(SealedExpr.Add.add(1,2))
}
複製程式碼

輸出結果為:

100
複製程式碼

說明:上面的擴充套件功能沒有任何的意義,只是為了給大家展示密封類子類的擴充套件不侷限與密封類同檔案這一個功能而已。如果你還對Koltin中的擴充套件功能還不甚清楚的。請參見我的另一篇博文Kotlin——擴充套件功能詳解

3.5、使用密封類的好處

  • 有效的保護程式碼(上面已說明原因)
  • 在使用when表示式 的時候,如果能夠驗證語句覆蓋了所有情況,就不需要為該語句再新增一個else子句了。

例:

sealed class SealedExpr{
    data class Person(val name : String, val age : Int) : SealedExpr()
    object Add : SealedExpr()
    companion object Minus : SealedExpr()
}

object NotANumber : SealedExpr()

fun eval(expr: SealedExpr) = when(expr){
    is SealedExpr.Add -> println("is Add")
    is SealedExpr.Minus -> println("is Minus")
    is SealedExpr.Person -> println(SealedExpr.Person("Koltin",22))
    NotANumber -> Double.NaN
}
複製程式碼

輸出結果為:

is Minus
複製程式碼

三、總結

在實際的專案開發當中,資料類(data)類的用處是很多的,因為在開發APP時,往往會根據後臺開發者所提供的介面返回的json而生成一個實體類,現在我們學習了資料類後,就不用再像Java一樣寫那麼多程式碼了,即使是用編輯器提供的方法去自動生成。但是程式碼量上就能節省我們很多時間,並且也更加簡潔。何樂而不為呢!密封類的情況在實際開發中不是很常見的。只有當時特殊的需求會用到的時候,才會使用密封類。當然我們還是要學習的。

原始碼
如果各位大佬看了之後感覺還闊以,就請各位大佬隨便star一下,您的關注是我最大的動力。
我的個人部落格Jetictors
我的githubJetictors

歡迎各位大佬進群共同研究、探索

QQ群號:497071402

Kotlin——中級篇(六):資料類(data)、密封類(sealed)詳解

相關文章