Kotlin 中的 data class 和 sealed class

Kotlin 中的 data class

在使用 java 的時候,我們經常會重寫類的 equalshashCodetoString 方法。這些方法往往都是模板化的氏捞。在 kotlin 中提供了更為簡便的方法讓我們使用一行代碼搞定這些工作。這就是 data class冒版。

// 定義一個 Person 類
data class Person(val name: String, val age: Int) {
}

寫好上面的代碼之后液茎,Person 類中的上述幾個方法的重寫就由 kotlin 幫我們自動完成了。運行下面的代碼

fun main() {
    val p1 = Person("Jack", 24)
    val p2 = Person("Jack", 24)

    val p3 = Person("Jack", 32)
    val p4 = Person("Rose", 31)

    println(p1 == p2) 

    println(p1 === p2)

    println(p1 == p3)

    println("""
        p1 hashCode = ${p1.hashCode()}
        p2 hashCode = ${p2.hashCode()}
        p3 hashCode = ${p3.hashCode()}
        p4 hashCode = ${p4.hashCode()}
    """.trimIndent())
    
    println("p1 = $p1")
}

結果如下:

true
false
false
p1 hashCode = 71328761
p2 hashCode = 71328761
p3 hashCode = 71328769
p4 hashCode = 79149200
p1 = Person(name=Jack, age=24)

可以看到equals 辞嗡、hashCodetoString 方法可以直接調用捆等,并且已經被覆寫了。

data class 究竟做了什么续室?

data class 是如何做到上述實現的呢栋烤?查看 Person 類的字節(jié)碼反編譯得到的 java 代碼,如下

public final class Person {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public Person(@NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String component1() {
      return this.name;
   }

   public final int component2() {
      return this.age;
   }

   @NotNull
   public final Person copy(@NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      return new Person(name, age);
   }

   // $FF: synthetic method
   public static Person copy$default(Person var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.name;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.age;
      }

      return var0.copy(var1, var2);
   }

   @NotNull
   public String toString() {
      return "Person(name=" + this.name + ", age=" + this.age + ")";
   }

   public int hashCode() {
      String var10000 = this.name;
      return (var10000 != null ? var10000.hashCode() : 0) * 31 + this.age;
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Person) {
            Person var2 = (Person)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

從上面可以一目了然地看見 kotlin 是如何構造 data class 的挺狰。由于我定義的成員變量是 val 不可變類型的明郭,所以沒有 Getter 和 Setter。

  • 對于equals 方法丰泊,對于 var1 , 如果它的地址和自己不同薯定,那么先檢查其是否是 Person 類型 ,如果是瞳购,則逐個對比每個成員變量是否相等话侄。這里用到的 Intrinsics.areEqual(Object o1, Object o2) 定義如下:

    public static boolean areEqual(Object first, Object second) {
          return first == null ? second == null : first.equals(second);
      }
    

    最終調用了被比較對象的 equals 方法。
    對于 Int 類型的成員變量 age , 直接使用 == 比較学赛。

    這是覆寫一個類的 equals 方法的常規(guī)寫法年堆。

  • hashCodetoString 方法也是我們常規(guī)覆寫的套路。

  • 提供了 component1component2 兩個方法來獲取成員變量盏浇。這兩個方法可以用來做解構聲明变丧。如下:

    val (name, age) = Person("Rose", 43) 
    println("$name, $age")
    // (name, age) 就是解構聲明,name 對應 component1 , age 對應 component2 
    
  • 提供了 copy 方法構造一個 Person 對象绢掰。有 // $FF: synthetic method 注釋的 copy$default 方法是給 kotlin 編譯器調用的锄贷,我們用不到译蒂。

    如果我們只想要 age 不同的 Person , 可以這樣寫

      val newPerson = p1.copy(age = 30)
    

    編譯器編譯到這句代碼時,會幫我們調用 copy$default 來構造一個 name 值和 p1 一樣的 newPerson 對象 谊却。

總之柔昼, data class 就是用常規(guī)套路來生成一個已經覆寫好上述方法的類。

如果 Person 類不需要自動生成 age 炎辨,只需要把 age 從主構造函數中拿出捕透,放到類體中就可以。如下

data class Person(val name: String) {
    val age: Int = 0
}

Kotlin 中的 sealed class

sealed class 是一種同時擁有枚舉類 enum 和 普通類 class 特性的類碴萧,叫做密封類乙嘀。使用起來很簡單,如下

sealed class Result
class Success(val code: Int) : Result()
class Exception(val code: Int, val message: String) : Result()

在同一個 kotlin 文件中聲明三個類破喻。首先聲明 sealed classResult, 然后定義出兩個子類 Success, Exception 繼承自 Result虎谢。注意,密封類及其子類必須聲明在同一個 kotlin 文件中曹质。

這是一個非常常見的場景婴噩。比如對于網絡請求的結果 Result , 往往只有兩種類型羽德,成功 Success 或者是失敗 Exception 几莽。使用普通的類不能把限制關系表達出來,使用枚舉類則無法靈活地自定義需要的類的內容宅静。這時候章蚣,sealed class 就派上用場了。比如在處理結果 Result 的時候:

fun handleResult(result: Result): String{
    return when(result) {
        is Success -> {
            "success"
        }
        is Exception -> {
            "exception"
        }
    }
}

這樣姨夹,對于 handleResult 的入參就做了類型的限制纤垂,防止傳入類型不匹配的參數。

還有一個好處是磷账,使用密封類的話峭沦,when 表達式可以覆蓋所有情況,不需要再添加 else 語句(表達式即有返回值的 when , 沒有返回值的稱為 when 語句)够颠。

sealed class 究竟做了什么熙侍?

同樣地榄鉴,讓我們來看看 sealed class 在 java 層面做了什么履磨,實現了前面的效果。
上述密封類反編譯得到的 java 代碼如下:

public final class Exception extends Result {
   private final int code;
   @NotNull
   private final String message;

   public final int getCode() {
      return this.code;
   }

   @NotNull
   public final String getMessage() {
      return this.message;
   }

   public Exception(int code, @NotNull String message) {
      Intrinsics.checkParameterIsNotNull(message, "message");
      super((DefaultConstructorMarker)null);
      this.code = code;
      this.message = message;
   }
}
// Success.java
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
public final class Success extends Result {
   private final int code;

   public final int getCode() {
      return this.code;
   }

   public Success(int code) {
      super((DefaultConstructorMarker)null);
      this.code = code;
   }
}
// Result.java
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
// 最重要的地方
public abstract class Result {
   private Result() {
   }

   // $FF: synthetic method
   public Result(DefaultConstructorMarker $constructor_marker) {
      this();
   }
}

可以看到庆尘,Result 類其實是一個抽象類剃诅,SuccessException 繼承了這個抽象類。Result 類的構造函數是私有的驶忌,不能在外部訪問到矛辕。

通過繼承這個抽象類笑跛,達到限制類型的做法。

這其實和 java 中使用接口來限定參數類型的做法類似聊品,很好理解飞蹂。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翻屈,隨后出現的幾起案子陈哑,更是在濱河造成了極大的恐慌,老刑警劉巖伸眶,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惊窖,死亡現場離奇詭異,居然都是意外死亡厘贼,警方通過查閱死者的電腦和手機界酒,發(fā)現死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘴秸,“玉大人毁欣,你說我怎么就攤上這事×抟牛” “怎么了署辉?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岩四。 經常有香客問我哭尝,道長,這世上最難降的妖魔是什么剖煌? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任材鹦,我火速辦了婚禮,結果婚禮上耕姊,老公的妹妹穿的比我還像新娘桶唐。我一直安慰自己,他們只是感情好茉兰,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布尤泽。 她就那樣靜靜地躺著,像睡著了一般规脸。 火紅的嫁衣襯著肌膚如雪坯约。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天莫鸭,我揣著相機與錄音闹丐,去河邊找鬼。 笑死被因,一個胖子當著我的面吹牛卿拴,可吹牛的內容都是我干的衫仑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼堕花,長吁一口氣:“原來是場噩夢啊……” “哼文狱!你這毒婦竟也來了?” 一聲冷哼從身側響起缘挽,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤如贷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后到踏,有當地人在樹林里發(fā)現了一具尸體杠袱,經...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年窝稿,在試婚紗的時候發(fā)現自己被綠了楣富。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡伴榔,死狀恐怖纹蝴,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情踪少,我是刑警寧澤塘安,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站援奢,受9級特大地震影響兼犯,放射性物質發(fā)生泄漏。R本人自食惡果不足惜集漾,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一切黔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧具篇,春花似錦纬霞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至埃疫,卻和暖如春伏恐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熔恢。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工脐湾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留臭笆,地道東北人叙淌。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓秤掌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鹰霍。 傳聞我的和親對象是個殘疾皇子闻鉴,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355