public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
熟悉java的一定很清楚,这是最常见的写法
然而在kotlin里面,写法就非常简单了:
class Person { lateinit var name:String } fun main(args: Array) { var person = Person() person.name = "hello" kotlin.io.println("name: ${person.name}") }
lateinit是让name保持和java的写法一样,不必在声明时就赋值
一般情况下,不用像java那样实现setter和getter方法,编译器已默认实现这两个方法了,在Android studio双击shift,输入Show Kotlin Bytecode,然后点击Decompile,便可查看kotlin文件对应的java代码,上面kotlin代码的person类经过编译器后的java代码是这样的:
public final class Person { public String name; @NotNull public final String getName() { String var10000 = this.name; if (var10000 == null) { Intrinsics.throwUninitializedPropertyAccessException("name"); } return var10000; } public final void setName(@NotNull String var1) { Intrinsics.checkNotNullParameter(var1, ""); this.name = var1; } }
虽然多了很多东西,但是可以看到默认实现了getter和setter
如果我想在get和set方法里面做一些 *** 作呢,如果是java,在get或set方法里面做一些判断,取出或者设置不同的值是很常见的做法,如果是kolin,则需要可借助幕后字段field了。
首先是自定义gettter:
class Person { var name:String = "" get() { kotlin.io.println("访问了get") return name +"666" } } fun main(args: Array) { var person = Person() person.name = "hello" kotlin.io.println("name: ${person.name}") }
运行结果:
可以看到访问了很多次get(),并且还出现了StackOverflowError异常,如果习惯了java会以为以上写法没什么问题,我们再次反编译kotlin代码看看:
public final class Person { @NotNull private String name = ""; @NotNull public final String getName() { String var1 = "访问了get"; System.out.println(var1); return this.getName() + "666"; //这里又访问了getName(),循环调用了 } public final void setName(@NotNull String var1) { Intrinsics.checkNotNullParameter(var1, ""); this.name = var1; } }
可以看到,getName()里面又调用了getName(),并且没有退出时机,一直递归调用最后导致了StackOverflowError。
二、幕后字段field以下是自定义getter和setter的各种情形,想要改变值,必须借助幕后字段field
情况一:属性是String等引用类型,即使getr和sett都不使用默认的实现方法,也可以使用幕后字段field(笔者曾在各种地方看到过,说如果get和set都不使用编译器默认提供的,则没有幕后字段,但是经过笔者验证,这种说法并不正确),这种情况下需要赋初始值,并且不可使用lateinit
class Person { var name:String = "" //需要赋初始值,并且不可使用lateinit!!! get() { kotlin.io.println("访问了get") return field +"666" } set(value) { kotlin.io.println("访问了set") field = value } } fun main(args: Array) { var person = Person() person.name = "hello" kotlin.io.println("name: ${person.name}") }
运行结果:
可以看到get()和set()对属性都起到了修改的作用
情况二:属性是Int,boolen等基础属性,不给予初始赋值,并且get和set都使用了自定义的方法,这种情况下不可使用幕后字段
class Person { var age:Int get() { kotlin.io.println("访问了get") return 2 } set(value) { kotlin.io.println("访问了set") value + 1 } } fun main(args: Array) { var person = Person() person.age = 9 kotlin.io.println("name: ${person.age}") }
运行结果:
但是这种写法,无论set里面做了什么 *** 作,最终取到的值都是get返回的值,并且这种情况下反编译为java代码,会看不到age属性了,以下是反编译后的java代码:
public final class Person { public final int getAge() { String var1 = "访问了get"; System.out.println(var1); return 2; } public final void setAge(int value) { String var2 = "访问了set"; System.out.println(var2); int var10000 = value + 1; } }
情况三:属性是Int,boolen等基础属性,给予初始赋值,并且get和set都使用了自定义的方法,这种情况下可以使用幕后字段
class Person { var age:Int = 0 get() { kotlin.io.println("访问了get") return field + 2 } set(value) { kotlin.io.println("访问了set") field = value + 1 } } fun main(args: Array) { var person = Person() person.age = 9 kotlin.io.println("age: ${person.age}") }
运行结果:
由上面的例子可以得出以下结论:
1.只用通过幕后字段field去 *** 作属性值才能改变属性的值,自定义的get和set可同时作用于属性,get和set不是必须都要自定义,可以根据实际情况去自定义哪一个方法
2.如果需要使用幕后字段,则需要在声明时赋予初始值
3.对于String等非基础数据类型,不管是自定义了get或者set,则必须赋予初始值
4.只要自定义了get或者set。无论是那种数据类型,都不能使用lateinit
5.如果是基础数据类型,如果不赋予初始值又不使用lateinit,则必须同时自定义get和set,如情况二,如果把自定义的get或者set删掉,则编译通不过,并且这种情况下不可使用幕后字段
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)