1. 程式人生 > >Kotlin——最詳細的數據類、密封類詳解

Kotlin——最詳細的數據類、密封類詳解

實現 分析 及其 驗證 pri gpo ava 兩個 有著

技術分享圖片

在前面幾個章節章節中,詳細的講解了Koltin中的接口類(Interface)枚舉類(Enmu),還不甚了解的可以查看我的上一篇文章Kotlin——接口類、枚舉類詳解。當然,在Koltin中,除了接口類、枚舉類之外,還有抽象類、內部類、數據類以及密封類。在今天的章節中,為大家詳細講解數據類密封類。在下一章節中,再為大家奉上Kotlin中的抽象類以及內部類的知識。如果還對Kotlin的分類還不清楚的可以查看我的另一篇博文Koltin——類(class)詳解。

目錄

技術分享圖片

一、數據類

  • 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
我的github:Jetictors
我的掘金:Jetictors

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

QQ群號:497071402

技術分享圖片

Kotlin——最詳細的數據類、密封類詳解