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一样写那么多代码了,即使是用编辑器提供的方法去自动生成。但是代码量上就能节省我们很多时间,并且也更加简洁。何乐而不为呢!密封类的情况在实际开发中不是很常见的。只有当时特殊的需求会用到的时候,才会使用密封类。当然我们还是要学习的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容