文章目录
前言
本章节记录下kotlin的属性,以及比较重要的Getters 与 Setters的使用。在之前的章节中,简单的介绍了属性(成员变量)Kotlin学习之路(四):类,构造函数,对象,接下来将会比较详细的介绍kotlin的属性相关使用。
一.类的属性
1.1 属性初始化
对于kotlin来说属性可以用val
修饰,也可以用var
修饰。
class Person {
// val和var修饰的属性必须初始化
// var修饰的属性
var name:String = "Jack"
// val修饰的属性
val id = 1001
}
fun main(){
val person = Person() // 创建一个只读的Person对象
// var person = Person()
person.id = 1 // 报错,val修饰的属性是只读,不能写入
println(person.name)
println(person.age)
println(person.id)
}
通过上面的代码实例,可以知道,var
可读可写,val
只能读取,不能写入。背后的编译原理可以参考我之前的文章:kotlin var,val,const val修饰符编译
var
和val
还有几点需要注意:
- 一般情况下,无论val和var必须初始化。
- 访问属性和java一样使用
.
访问。 - 属性前面不加任何访问权限修饰符,代表默认访问权限修饰符:
public
。
1.2 属性延迟初始化
一般情况下必须立刻对属性初始化,当然也可以延迟初始化,这里就需要使用by lazy
和lateinit
。
1.2.1 lateinit
class Person {
lateinit var name:String // 这里不需要初始化
}
fun main() {
val person = Person()
person.name = "Tom" // 这里初始化,到这里就和java一样了
println("name is ${person.name}")
}
对于lateinit
有以下这么几个特点:
lateinit
只能用于var修饰的属性lateinit
只能修饰非基本数据类型,比如,不能修饰Int,Float等基本数据类型
1.2.2 by lazy
对于by lazy
也有以下这么几个特点:
- 只能修饰val的属性。
- 具备延迟性,只有在使用的时候才会初始化一次。
例子:
class Person {
val name:String by lazy {
println("初始化!")
"Tom"
}
}
fun main() {
val person = Person()
println("name is ${person.name}") // 第一次使用的时候才初始化
println("-------------------------------")
println("name is ${person.name}")
}
以上两点中最重要的是第二点,被修饰的属性只在被使用的时候才会初始化。上面的例子执行的结果如下:
初始化!
name is Tom
-------------------------------
name is Tom
仔细看看上面的结果,首次使用的时候才会初始化,再使用的时候就不需要初始化,而是直接执行结果!
二.getter方法和setter方法
2.1 Java的getter和setter方法
首先我们先来回顾下Java的getter方法和setter方法:
public class Person {
private String mName;
private int mAge;
public String getName() {
return mName;
}
public void setName(String mName) {
this.mName = mName;
}
public int getAge() {
return mAge;
}
public void setAge(int mAge) {
this.mAge = mAge;
}
}
上面是一个最简单的带有getter和setter的类。通过隐藏mName
和mAge
,向外暴露name和age的getter和setter。
2.2 kotlin的getter和setter方法
上面是Java的getter和setter的简单介绍,kotlin也有setter和getter,同时也更加的简单:
class Person {
var name:String = ""
var age:Int = 0
}
对,就这么简单,不信?看下反编译之后的代码:
public final class Person {
@NotNull
private String name = "";
private int age;
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
public final int getAge() {
return this.age;
}
public final void setAge(int var1) {
this.age = var1;
}
}
通过反编译可以看到,生成的代码里面已经自动加上了getter和setter方法。如果你做java和kotlin混合开发,其他的java代码就可以直接调用kotlin的getter和setter方法。
三 Kotlin的get()和set()默认方法
3.1 get()和set()默认方法
kotlin有种Java所不具备的可以给每个属性变量设置一个get()和set()默认方法:
基本语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
(来自kotlin官方文档:Kotlin:属性)
其中的getter和setter就是你"可选的"get()和set()默认方法。
接下来看下这些默认方法的简单使用:
get():
class Person {
var age:Int = 0
get() = -1
}
fun main() {
val person = Person()
println("age is ${person.age}") // age is -1
person.age = 22
println("age is ${person.age}") // age is -1
}
set():
class Person {
var age:Int = -1
set(value){
if (value <= 0){
field = 0
}
}
}
fun main() {
val person = Person()
println("age is ${person.age}") // age is -1
person.age = -10
println("age is ${person.age}") // age is 0
}
看到上面的代码,是不是感到有点迷糊,是的,我刚开始也感到有些迷糊!
接下来我总结下:
- 对于set()默认方法只有在属性变量被赋值的时候才会调用该方法。
- 对于get()默认方法只有在获取(使用)属性变量的时候才会调用该方法。
针对上面的两个总结,举一个例子:
set()
class Person {
var age:Int = -1
set(value){
println("调用set()")
}
}
fun main() {
val person = Person()
println("age is ${person.age}")
person.age = -10
println("age is ${person.age}")
}
输出结果:
age is -1
调用set()
age is -1
注意!这里的"调用set()"打印语句,就是在执行person.age = -10
的时候调用的。
get()
class Person {
var age:Int = -1
get(){
println("调用get()")
return field
}
}
fun main() {
val person = Person()
println("age is ${person.age}")
}
输出结果:
调用get()
age is -1
同理"调用get()"则是在调用person.age
的时候执行的。
通过上面的例子,可以了解set()和get()的调用时机,那么get和set默认方法到底是怎么实现的?很简单,反编译成Java看看:
kotlin代码
class Person {
var age:Int = -1
set(value){
if (value <= 0){
field = 0
}
}
}
反编译成Java后:
public final class Person {
private int age = -1;
public final int getAge() {
return this.age;
}
public final void setAge(int value) {
if (value <= 0) {
this.age = 0;
}
}
}
看到上面反编译后的Java代码,聪明的你应该就知道了,get和set默认方法其实就是类的getter和setter方法。
3.2 get()和set()默认方法的权限修饰符
对于get()和set()默认方法是可以加上权限修饰符的,比如private,protected。
class Person {
var age:Int = -1
private set
// protected get // 这里直接报错(Getter visibility must be the same as property visibility)
}
fun main(){
val person = Person()
// person.age = 22 // 报错,age不能被赋值
}
- 对于
set()
默认方法,显示权限修饰符的范围不能超过该属性的显示权限修饰符的范围。 - 对于
get()
默认方法,它的显示权限必须和属性的显示权限一样。
我们可以通过这一个特性,轻松将某个成员变量改为不能赋值的成员变量。
3.3 幕后字段
幕后字段其实就是field
,一般情况下在get和set默认方法中可以隐藏。
class Person {
var age:Int = -1
get(){
println("调用get()")
return field
}
}
field指代的就是那个属性变量,上面的例子就是指age
。这里做一个大胆的假设,如果将field改为age会如何?
事实上将会出现java.lang.StackOverflowError
栈溢出的问题。所以,以后在get和set默认方法内部写一些逻辑的时候,注意不要使用属性变量,而应该是使用field。