Kotlin数据类深度解析与底层剖析
今天来学习一下全新关于Kotlin的概念---数据类【data class】,也是非常有用的东东,下面先来对其进行理论化的了解:
数据类其实跟java的实体类(model)很类似,像Java定义一个Person类,里面有各种属性然后再生成它的get和set方法,当然可以借助于IDE来生成,但是其实java的这种做法是挺冗长啰嗦的,当然其实有现成的解决方案来解决这个冗长了,比如用lombok库,利用注解就可以动态生成get和set,接下来咱们来看一下kotlin数据类是怎么使用的:
这样数据库就定义成了,也没任何额外的get和set方法,是不是特别的简洁,好,下面来使用一下:
如果将data关键字去掉,其输出就会是对象的toString()了,如下:
数据类的定义是不是很简单,其实数据类是有一些要求的,下面来瞅下:
1、主构造方法,至少要有一个参数。
2、所有的主构造方法参数都需要被标记为var或val。
3、数据类不能是抽象的、open的、sealed【密封类,还未学到】的以及inner的。
另外对于数据类,编译器会自动的生成如下内容:
1、equals/hashcode对。
2、toString()方法,形式为我们看到的:
3、针对属性的componentN方法,并且是按照属性的声明顺序来生成的。
啥意思,也就是针对咱们的这个类会生成component1()、component2()、component3(),而这三个方法的返回值是按照我们定义的name、age、address。
好,下面来反编译一下,来看一下数据类的底层细节:
xiongweideMacBook-Pro:kotlin_lecture xiongwei$ javap -c com/kotlin/test2/Person.class Compiled from "HelloKotlin1.kt" public final class com.kotlin.test2.Person { public final java.lang.String getName(); Code: 0: aload_0 1: getfield #11 // Field name:Ljava/lang/String; 4: areturn public final int getAge(); Code: 0: aload_0 1: getfield #19 // Field age:I 4: ireturn public final void setAge(int); Code: 0: aload_0 1: iload_1 2: putfield #19 // Field age:I 5: return public final java.lang.String getAddress(); Code: 0: aload_0 1: getfield #26 // Field address:Ljava/lang/String; 4: areturn public final void setAddress(java.lang.String); Code: 0: aload_1 1: ldc #29 // String <set-?> 3: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_0 7: aload_1 8: putfield #26 // Field address:Ljava/lang/String; 11: return public com.kotlin.test2.Person(java.lang.String, int, java.lang.String); Code: 0: aload_1 1: ldc #38 // String name 3: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_3 7: ldc #39 // String address 9: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 12: aload_0 13: invokespecial #42 // Method java/lang/Object."<init>":()V 16: aload_0 17: aload_1 18: putfield #11 // Field name:Ljava/lang/String; 21: aload_0 22: iload_2 23: putfield #19 // Field age:I 26: aload_0 27: aload_3 28: putfield #26 // Field address:Ljava/lang/String; 31: return public final java.lang.String component1(); Code: 0: aload_0 1: getfield #11 // Field name:Ljava/lang/String; 4: areturn public final int component2(); Code: 0: aload_0 1: getfield #19 // Field age:I 4: ireturn public final java.lang.String component3(); Code: 0: aload_0 1: getfield #26 // Field address:Ljava/lang/String; 4: areturn public final com.kotlin.test2.Person copy(java.lang.String, int, java.lang.String); Code: 0: aload_1 1: ldc #38 // String name 3: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_3 7: ldc #39 // String address 9: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 12: new #2 // class com/kotlin/test2/Person 15: dup 16: aload_1 17: iload_2 18: aload_3 19: invokespecial #49 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V 22: areturn public static com.kotlin.test2.Person copy$default(com.kotlin.test2.Person, java.lang.String, int, java.lang.String, int, java.lang.Object); Code: 0: iload 4 2: iconst_1 3: iand 4: ifeq 12 7: aload_0 8: getfield #11 // Field name:Ljava/lang/String; 11: astore_1 12: iload 4 14: iconst_2 15: iand 16: ifeq 24 19: aload_0 20: getfield #19 // Field age:I 23: istore_2 24: iload 4 26: iconst_4 27: iand 28: ifeq 36 31: aload_0 32: getfield #26 // Field address:Ljava/lang/String; 35: astore_3 36: aload_0 37: aload_1 38: iload_2 39: aload_3 40: invokevirtual #53 // Method copy:(Ljava/lang/String;ILjava/lang/String;)Lcom/kotlin/test2/Person; 43: areturn public java.lang.String toString(); Code: 0: new #56 // class java/lang/StringBuilder 3: dup 4: invokespecial #57 // Method java/lang/StringBuilder."<init>":()V 7: ldc #59 // String Person(name= 9: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: getfield #11 // Field name:Ljava/lang/String; 16: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc #65 // String , age= 21: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: aload_0 25: getfield #19 // Field age:I 28: invokevirtual #68 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 31: ldc #70 // String , address= 33: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: aload_0 37: getfield #26 // Field address:Ljava/lang/String; 40: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 43: ldc #72 // String ) 45: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 48: invokevirtual #74 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 51: areturn public int hashCode(); Code: 0: aload_0 1: getfield #11 // Field name:Ljava/lang/String; 4: dup 5: ifnull 14 8: invokevirtual #77 // Method java/lang/Object.hashCode:()I 11: goto 16 14: pop 15: iconst_0 16: bipush 31 18: imul 19: aload_0 20: getfield #19 // Field age:I 23: invokestatic #82 // Method java/lang/Integer.hashCode:(I)I 26: iadd 27: bipush 31 29: imul 30: aload_0 31: getfield #26 // Field address:Ljava/lang/String; 34: dup 35: ifnull 44 38: invokevirtual #77 // Method java/lang/Object.hashCode:()I 41: goto 46 44: pop 45: iconst_0 46: iadd 47: ireturn public boolean equals(java.lang.Object); Code: 0: aload_0 1: aload_1 2: if_acmpeq 64 5: aload_1 6: instanceof #2 // class com/kotlin/test2/Person 9: ifeq 66 12: aload_1 13: checkcast #2 // class com/kotlin/test2/Person 16: astore_2 17: aload_0 18: getfield #11 // Field name:Ljava/lang/String; 21: aload_2 22: getfield #11 // Field name:Ljava/lang/String; 25: invokestatic #91 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z 28: ifeq 66 31: aload_0 32: getfield #19 // Field age:I 35: aload_2 36: getfield #19 // Field age:I 39: if_icmpne 46 42: iconst_1 43: goto 47 46: iconst_0 47: ifeq 66 50: aload_0 51: getfield #26 // Field address:Ljava/lang/String; 54: aload_2 55: getfield #26 // Field address:Ljava/lang/String; 58: invokestatic #91 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z 61: ifeq 66 64: iconst_1 65: ireturn 66: iconst_0 67: ireturn } xiongweideMacBook-Pro:kotlin_lecture xiongwei$
其中是不是可以看到自动为其生成属性的set和get方法了,除了name之外,因为它声明的是一个常量:
接着生成了两个参数的构造方法:
再接着就看到我们第三点说的componentN了,如下:
再往下看:
还帮我们生成了toString()方法:
最后就是hashCode()和equals():
下面继续来对数据类成员的继承要点进行说明:
1、如果数据类中显示定义了equals,hashCode或者是toString方法,或者是在数据类的父类中将这些方法声明为final,那么这些方法就不会再生成,转而使用已有的。
2、如果父类拥有componentN方法,并且是open的以及返回兼容的类型,那么编译器就会在数据类中生成相应的componentN方法,并且重写父类的这些方法;如果父类中的这些方法由于不兼容的签名或者被定义为final的,那么编译器就会报错。
3、在数据类中显示提供componentN方法以及copy方法实现是不允许的。下面来试一下:
其错误提示为:
接下来来对生成的copy方法进行一个说明,它的重用主要是根据已有的属性来创建一个新的对象,比如:
如果咱们这样写:
接下来想一个问题:既然Kotlin编译主动给属性生成了set和get方法了,那。。是不是可以直接像Java那样来操作属性呢,下面瞅下:
不通这样写,只能:
解构声明:
在主构造方法中有多少个参数,就会依次生成相应的component1、component2、component3...,这些方法返回的就是对应字段的值,componentN方法是用来实现解构声明的。如何理解,看代码:
那,如果只写两个变量呢?
另外,我们通过反编译数据类可以发现,貌似生成的是带参的构造方法,如下:
那,如何声明不带参的构造方法呢,其实在JVM平台上,如果生成的类需要拥有无参构造方法,那么就需要为所有属性指定默认值,如下:
再次反编译论证一下:
xiongweideMacBook-Pro:kotlin_lecture xiongwei$ javap -c com/kotlin/test2/Person2.class Compiled from "HelloKotlin1.kt" public final class com.kotlin.test2.Person2 { public final java.lang.String getName(); Code: 0: aload_0 1: getfield #11 // Field name:Ljava/lang/String; 4: areturn public final int getAge(); Code: 0: aload_0 1: getfield #19 // Field age:I 4: ireturn public final void setAge(int); Code: 0: aload_0 1: iload_1 2: putfield #19 // Field age:I 5: return public final java.lang.String getAddress(); Code: 0: aload_0 1: getfield #26 // Field address:Ljava/lang/String; 4: areturn public final void setAddress(java.lang.String); Code: 0: aload_1 1: ldc #29 // String <set-?> 3: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_0 7: aload_1 8: putfield #26 // Field address:Ljava/lang/String; 11: return public com.kotlin.test2.Person2(java.lang.String, int, java.lang.String); Code: 0: aload_1 1: ldc #38 // String name 3: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_3 7: ldc #39 // String address 9: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 12: aload_0 13: invokespecial #42 // Method java/lang/Object."<init>":()V 16: aload_0 17: aload_1 18: putfield #11 // Field name:Ljava/lang/String; 21: aload_0 22: iload_2 23: putfield #19 // Field age:I 26: aload_0 27: aload_3 28: putfield #26 // Field address:Ljava/lang/String; 31: return public com.kotlin.test2.Person2(java.lang.String, int, java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker); Code: 0: iload 4 2: iconst_1 3: iand 4: ifeq 10 7: ldc #45 // String 9: astore_1 10: iload 4 12: iconst_2 13: iand 14: ifeq 20 17: bipush 20 19: istore_2 20: iload 4 22: iconst_4 23: iand 24: ifeq 30 27: ldc #47 // String henan 29: astore_3 30: aload_0 31: aload_1 32: iload_2 33: aload_3 34: invokespecial #49 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V 37: return public com.kotlin.test2.Person2(); Code: 0: aload_0 1: aconst_null 2: iconst_0 3: aconst_null 4: bipush 7 6: aconst_null 7: invokespecial #51 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V 10: return public final java.lang.String component1(); Code: 0: aload_0 1: getfield #11 // Field name:Ljava/lang/String; 4: areturn public final int component2(); Code: 0: aload_0 1: getfield #19 // Field age:I 4: ireturn public final java.lang.String component3(); Code: 0: aload_0 1: getfield #26 // Field address:Ljava/lang/String; 4: areturn public final com.kotlin.test2.Person2 copy(java.lang.String, int, java.lang.String); Code: 0: aload_1 1: ldc #38 // String name 3: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_3 7: ldc #39 // String address 9: invokestatic #35 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 12: new #2 // class com/kotlin/test2/Person2 15: dup 16: aload_1 17: iload_2 18: aload_3 19: invokespecial #49 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V 22: areturn public static com.kotlin.test2.Person2 copy$default(com.kotlin.test2.Person2, java.lang.String, int, java.lang.String, int, java.lang.Object); Code: 0: iload 4 2: iconst_1 3: iand 4: ifeq 12 7: aload_0 8: getfield #11 // Field name:Ljava/lang/String; 11: astore_1 12: iload 4 14: iconst_2 15: iand 16: ifeq 24 19: aload_0 20: getfield #19 // Field age:I 23: istore_2 24: iload 4 26: iconst_4 27: iand 28: ifeq 36 31: aload_0 32: getfield #26 // Field address:Ljava/lang/String; 35: astore_3 36: aload_0 37: aload_1 38: iload_2 39: aload_3 40: invokevirtual #60 // Method copy:(Ljava/lang/String;ILjava/lang/String;)Lcom/kotlin/test2/Person2; 43: areturn public java.lang.String toString(); Code: 0: new #63 // class java/lang/StringBuilder 3: dup 4: invokespecial #64 // Method java/lang/StringBuilder."<init>":()V 7: ldc #66 // String Person2(name= 9: invokevirtual #70 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 12: aload_0 13: getfield #11 // Field name:Ljava/lang/String; 16: invokevirtual #70 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc #72 // String , age= 21: invokevirtual #70 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: aload_0 25: getfield #19 // Field age:I 28: invokevirtual #75 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 31: ldc #77 // String , address= 33: invokevirtual #70 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: aload_0 37: getfield #26 // Field address:Ljava/lang/String; 40: invokevirtual #70 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 43: ldc #79 // String ) 45: invokevirtual #70 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 48: invokevirtual #81 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 51: areturn public int hashCode(); Code: 0: aload_0 1: getfield #11 // Field name:Ljava/lang/String; 4: dup 5: ifnull 14 8: invokevirtual #84 // Method java/lang/Object.hashCode:()I 11: goto 16 14: pop 15: iconst_0 16: bipush 31 18: imul 19: aload_0 20: getfield #19 // Field age:I 23: invokestatic #89 // Method java/lang/Integer.hashCode:(I)I 26: iadd 27: bipush 31 29: imul 30: aload_0 31: getfield #26 // Field address:Ljava/lang/String; 34: dup 35: ifnull 44 38: invokevirtual #84 // Method java/lang/Object.hashCode:()I 41: goto 46 44: pop 45: iconst_0 46: iadd 47: ireturn public boolean equals(java.lang.Object); Code: 0: aload_0 1: aload_1 2: if_acmpeq 64 5: aload_1 6: instanceof #2 // class com/kotlin/test2/Person2 9: ifeq 66 12: aload_1 13: checkcast #2 // class com/kotlin/test2/Person2 16: astore_2 17: aload_0 18: getfield #11 // Field name:Ljava/lang/String; 21: aload_2 22: getfield #11 // Field name:Ljava/lang/String; 25: invokestatic #98 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z 28: ifeq 66 31: aload_0 32: getfield #19 // Field age:I 35: aload_2 36: getfield #19 // Field age:I 39: if_icmpne 46 42: iconst_1 43: goto 47 46: iconst_0 47: ifeq 66 50: aload_0 51: getfield #26 // Field address:Ljava/lang/String; 54: aload_2 55: getfield #26 // Field address:Ljava/lang/String; 58: invokestatic #98 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z 61: ifeq 66 64: iconst_1 65: ireturn 66: iconst_0 67: ireturn }
果真如此,跟Java不同的是,如果Java中的属性没有赋值会有默认值,而在Kotlin中默认是不会自动给默认值给属性进行赋值的,其实这样做更加的安全,这样既便我们在使用Kotlin的无参构造方法时,它里面的属性也能保证都有初始值。
关于数据类在实际中使用是非常多的,可见也比Java要简洁好使得多,可以多多体会。