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

在前面幾個章節章節中,詳細的講解了Koltin中的接口類(Interface)枚舉類(Enmu),還不甚瞭解的能夠查看個人上一篇文章Kotlin——中級篇(五):枚舉類(Enum)、接口類(Interface)詳解。固然,在Koltin中,除了接口類、枚舉類以外,還有抽象類、內部類、數據類以及密封類。在今天的章節中,爲你們詳細講解數據類密封類。在下一章節中,再爲你們奉上Kotlin中的抽象類以及內部類的知識。若是還對Kotlin的分類還不清楚的能夠查看個人另外一篇博文Kotlin——中級篇(一):類(class)詳解git

目錄

1、數據類

  • Java中,或者在咱們平時的Android開發中,爲了解析後臺人員給咱們提供的接口返回的Json字符串,咱們會根據這個字符串去建立一個或者實例對象,在這個類中,只包含了一些咱們須要的數據,以及爲了處理這些數據而所編寫的方法。這樣的類,在Kotlin中就被稱爲數據類

一、關鍵字

聲明數據類的關鍵字爲:datagithub

1.一、聲明格式json

data class 類名(var param1 :數據類型,...){}
複製代碼

或者jvm

data class 類名 可見性修飾符 constructor(var param1 : 數據類型 = 默認值,...)
複製代碼

說明:編輯器

  • data爲聲明數據類的關鍵字,必須書寫在class關鍵字以前。
  • 在沒有結構體的時候,大括號{}可省略。
  • 構造函數中必須存在至少一個參數,而且必須使用valvar修飾。這一點在下面數據類特性中會詳細講解。
  • 參數的默認值無關緊要。(若要實例一個無參數的數據類,則就要用到默認值)

例:ide

// 定義一個名爲Person的數據類
data class Preson(var name : String,val sex : Int, var age : Int)
複製代碼

1.二、約定俗成的規定函數

  • 數據類也有其約定俗成的一些規定,這只是爲增長代碼的閱讀性。

即,當構造函數中的參過多時,爲了代碼的閱讀性,一個參數的定義佔據一行。源碼分析

例:post

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.三、編輯器爲咱們作的事情學習

當咱們聲明一個數據類時,編輯器自動爲這個類作了一些事情,否則它怎麼又比Java簡潔呢。它會根據主構造函數中所定義的全部屬性自動生成下列方法:

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

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

二、數據類的特性

數據類有着和Kotlin其餘類不同的特性。除了含有其餘類的一些特性外,還有着其獨特的特色。而且也是數據類必須知足的條件:

  • 主構造函數須要至少有一個參數
  • 主構造函數的全部參數須要標記爲 val 或 var;
  • 數據類不能是抽象、開放、密封或者內部的;
  • 數據類是能夠實現接口的,如(序列化接口),同時也是能夠繼承其餘類的,如繼承自一個密封類。

三、用實例說明其比Java的簡潔性

3.一、數據類的對比

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.二、修改數據類屬性

例:修改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()去修改

四、解構聲明

  • 在前面講到,Kotlin中定義一個數據類,則系統會默認自動根據參數的個數生成component1() ... componentN()函數。其...,componentN()函數就是用於解構聲明的
val mUser = User("kotlin","123456")
val (name,pwd) = mUser
println("name = $name\tpwd = $pwd")
複製代碼

輸出結果爲:

name = kotlin	pwd = 123456
複製代碼

五、系統標準庫中的標準數據類

  • 標準庫提供了 Pair 和 Triple。儘管在不少狀況下命名數據類是更好的設計選擇, 由於它們經過爲屬性提供有意義的名稱使代碼更具可讀性。
  • 其實這兩個類的源碼部分很少,故而貼出這個類的源代碼來分析分析

5.一、源碼分析

@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.二、用法

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)
複製代碼

2、密封類

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

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

  • 所謂受限的類繼承結構,即當類中的一個值只能是有限的幾種類型,而不能是其餘的任何類型。
  • 這種受限的類繼承結構從某種意義上講,它至關因而枚舉類的擴展。可是,咱們知道Kotlin的枚舉類中的枚舉常量是受限的,由於每個枚舉常量只能存在一個實例。若對Kotlin中的枚舉類不甚瞭解的,請參見個人另外一篇文章Kotlin——中級篇(五):枚舉類(Enum)、接口類(Interface)詳解
  • 可是其和枚舉類不一樣的地方在於,密封類的一個子類能夠有可包含狀態的多個實例。
  • 也能夠說成,密封類是包含了一組受限的類集合,由於裏面的類都是繼承自這個密封類的。可是其和其餘繼承類(open)的區別在,密封類能夠不被此文件外被繼承,有效保護代碼。可是,其密封類的子類的擴展是是能夠在程序中任何位置的,便可以不在同一文件下。

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

二、關鍵字

定義密封類的關鍵字:sealed

2.一、聲明格式

sealed class SealedExpr()
複製代碼

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

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

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

三、密封類的做用及其詳細用法。

3.一、做用

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

例:

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.二、和普通繼承類的區別

  • 咱們知道普通的繼承類使用open關鍵字定義,在項目中的類均可繼承至該類。若是你對Koltin的繼承類還不甚瞭解。請參見個人另外一篇文章Kotlin——中級篇(四):繼承類詳解
  • 而密封類的子類必須是在密封類的內部或必須存在於密封類的同一文件。這一點就是上面提到的有效的代碼保護。

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.四、其子類的類擴展實例

  • 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.五、使用密封類的好處

  • 有效的保護代碼(上面已說明緣由)
  • 在使用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
複製代碼

3、總結

在實際的項目開發當中,數據類(data)類的用處是不少的,由於在開發APP時,每每會根據後臺開發者所提供的接口返回的json而生成一個實體類,如今咱們學習了數據類後,就不用再像Java同樣寫那麼多代碼了,即便是用編輯器提供的方法去自動生成。可是代碼量上就能節省咱們不少時間,而且也更加簡潔。何樂而不爲呢!密封類的狀況在實際開發中不是很常見的。只有當時特殊的需求會用到的時候,纔會使用密封類。固然咱們仍是要學習的。

源代碼
若是各位大佬看了以後感受還闊以,就請各位大佬隨便star一下,您的關注是我最大的動力。
個人我的博客Jetictors
個人githubJetictors

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

QQ羣號:497071402

相關文章
相關標籤/搜索