一、对比java的类声明和kotlin的类声明
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<String>) {
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, "<set-?>");
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<String>) {
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, "<set-?>");
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 333 "
}
}
fun main(args: Array<String>) {
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<String>) {
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<String>) {
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删掉,则编译通不过,并且这种情况下不可使用幕后字段