Kotlin学习之路(五):属性,Getters 与 Setters

前言
本章节记录下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
}

看到上面的代码,是不是感到有点迷糊,是的,我刚开始也感到有些迷糊!
接下来我总结下:

  1. 对于set()默认方法只有在属性变量被赋值的时候才会调用该方法。
  2. 对于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会如何?

field改为age

 事实上将会出现java.lang.StackOverflowError栈溢出的问题。所以,以后在get和set默认方法内部写一些逻辑的时候,注意不要使用属性变量,而应该是使用field。

猜你喜欢

转载自blog.csdn.net/liujun3512159/article/details/128541976
今日推荐