Kotlin学习——属性和字段

Posted DayFight_DayUp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin学习——属性和字段相关的知识,希望对你有一定的参考价值。

首先我们要分清属性和字段的区别

属性:Java中的属性,通常可以理解为get和set方法。
看下面Java代码:

public class Person 
    private String name;
    private int age;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

字段只要有set或get方法,就是属性

字段:,通常叫做“类成员”。使用来承载数据的,
看下面Java代码:

class A
    private String s = "xiaohua"; //没有响应的get和set,那么就是字段或者成员变量

Kotlin中,在我的学习之下,好像只有属性和幕后字段,并没有字段(Kotlin中不能有字段)

属性声明

Kotlin的类可以有属性。属性可以⽤关键字var 声明为可变的,否则使⽤只读关键字val

class Address 
    var name: String = "hua话"
    var street: String = "beikejie"
    var city: String = "shenzhen"
    var state: String? = null
    var zip: String = "hello.zip"

要使⽤⼀个属性,只要⽤名称引⽤它即可,就像 Java 中的字段

fun copyAddress(address: Address): Address 
    val result = Address() // Kotlin 中没有“new”关键字
    result.name = address.name // 将调⽤访问器getter
    result.street = address.street
    return result

getter和setter

下面看一段Kotlin代码:

class Box <T>
    var array :ArrayList<T> = ArrayList()
    var size : Int
        get() = array.size
        set(value) 
            var tmpArray : ArrayList<T> = array.clone() as ArrayList<T>
            array= ArrayList(array.size + value)
            for ( i in tmpArray)
                array.add(i)
            
        
    val isEmpt :Boolean
        get() = this.size ==0

    fun add(a : T)
        array.add(a)
    

    override fun toString():String
        return array.toString()
    

    fun getCapacity():Int
        return array.count()
    
    fun remove(a:T)
        array.remove(a)
    

现在做必要的说明:
1. 声明⼀个属性的完整语法是
var propertyName[: PropertyType] [= property_initializer]
[getter]
[setter]
方括号里的是可选的,setter和getter是访问器
2. 属性的初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器(或者从其 getter 返回值,如下⽂所⽰)中推断出来,也可以省略。
3. 省略了访问器,默认会自带访问器

class A 
    var name:String = ""
    var age:Int = 0
        set(value)
            field =value
        


fun main(args: Array<String>) 
    var a = A()
    a.name ="lala" //调用默认的setter
    println(a.name)//调用默认的getter
    println(a.age)  //就算省略了也会默认带有getter的

4. ⼀个只读属性的语法和⼀个可变的属性的语法有两⽅⾯的不同:1、只读属性的⽤ val 开始代替 var 2、只读属性不允许 setter(上面的Box类中的各个属性有定义getter和setter的示例)
5. ⾃ Kotlin 1.1 起,如果可以从 getter 推断出属性类型,则可以省略它(如Box中isEmpty一样)

 val isEmpt :Boolean
        get() = this.size ==0

另外:
如果你需要改变⼀个访问器的可⻅性或者对其注解,但是不需要改变默认的实现,你可以定义访问器⽽不定义其实现

var setterVisibility: String = "abc"
    private set // 此 setter 是私有的并且有默认实现
var setterWithAnnotation: Any? = null
    @Inject set // ⽤ Inject 注解此 setter

幕后字段

Kotlin 中类不能有字段。然⽽,当使⽤⾃定义访问器时,有时有⼀个幕后字段(backing field)有时是必要的。为此 Kotlin 提供⼀个⾃动幕后字段,它可通过使⽤ field 标识符访问
如果像一下代码:

class A 
    var age:Int = 0
        get() = this.age  //这样会陷入死循环的

fun main(args: Array<String>) 
    var a = A()
    println(a.age) 

上面的代码看似正确,但是实际上是一个死循环
正确的做法如下:

class A 
    var age:Int = 0
        get() = field  //使用幕后字段就不会陷入死循环

fun main(args: Array<String>) 
    var a = A()
    println(a.age)

注意:field 标识符只能⽤在属性的访问器内,其他地方不能出现
如果属性⾄少⼀个访问器使⽤默认实现,或者⾃定义访问器通过 field 引⽤幕后字段,将会为该属性⽣成⼀个幕后字段。
但是像如下代码就不会有幕后字段:

 val isEmpty :Boolean
        get() = this.size ==0 //因为isEmpty只有一个getter访问器,没有默认,且没有使用field关键字,所以没有幕后字段

幕后属性

如果你的需求不符合这套“隐式的幕后字段”⽅案,那么总可以使⽤ 幕后属性(backing property)

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() 
        if (_table == null) 
            _table = HashMap() // 类型参数已推断出
        
        return _table ?: throw AssertionError("Set to null by another thread")
    

其中_table就是table的幕后属性
从各⽅⾯看,这上面的代码与 Java 相同的⽅式。因为通过默认 getter 和 setter 访问私有属性会被优化,所以不会引⼊函数调⽤开销。

编译期常量

已知值的属性可以使⽤ const 修饰符标记为 编译期常量。这些属性需要满⾜以下要求:
1. 位于顶层(直接定义在包中kt文件中)或者是 object 的⼀个成员
2. ⽤ String 或原⽣类型 值初始化
3. 没有⾃定义 getter
4. 这些属性可以⽤在注解中

//编译时常量,位于顶层就是直接定义在kt里,和class 一个级别
const val NAME = "King"  
@Deprecated(NAME) fun f()


class Three()
    var name = NAME  //访问常量
object Obj
    const val NAME ="tiger" //定义在对象里,使用对象表达式


fun main(args: Array<String>) 
    Obj.NAME

延迟初始化属性

之前我们在第四节就已经说到了延迟初始化属性,这里我们要详细的说明:
看官方语言:
⼀般地,属性声明为⾮空类型必须在构造函数中初始化。然⽽,这经常不⽅便。例如:属性可以通过依赖注⼊来初始化,或者在单元测试的 setup ⽅法中初始化。这种情况下,你不能在构造函数内提供⼀个⾮空初始器。但你仍然想在类体中引⽤该属性时避免空检查。
为处理这种情况,你可以⽤ lateinit 修饰符标记该属性

分析以上的话:
1. 为了避免编译的时候空检查,又想在本类中引用
2. 现在不要初始化,而是要依赖其他组件初始化这个属性。比如使用SpringIOC,或者JUnit测试时才传入参数
为了满足以上两点的需求,kotlin加入了来lateinit延迟初始化

public class MyTest 
    lateinit var subject: TestSubject
    @SetUp fun setup() 
        subject = TestSubject() //装配的时候才让属性初始化
    
    @Test fun test() 
        subject.method() // 测试的时候才用,所以才之前装配
    

该修饰符只能⽤于在类体中(不是在主构造函数中)声明的 var 属性,并且仅当该属性没有⾃定义 getter 或 setter 时。该属性必须是⾮空类型,并且不能是原⽣类型

上面得到的信息:
1. lateinit只能修饰var类型的属性(因为要调用setter)
2. 原生类型(Byte,Short,Int,Long,Float,Double,Char,Boolean)不能使用lateinit修饰,也就是说,一旦属性是原生类型就要必须被初始化

在初始化前访问⼀个 lateinit 属性会抛出⼀个特定异常,该异常明确标识该属性被访问及它没有初始化的事实
其实这个异常就是:kotlin.UninitializedPropertyAccessException异常 ,是在调用getter之前没有被初始化就会抛出的

以上是关于Kotlin学习——属性和字段的主要内容,如果未能解决你的问题,请参考以下文章

kotlin 中的 Observable.combineLatest 类型推断

kotlin 学习记录

了解 Kotlin 中的字段和属性

Kotlin属性和字段与基本语法

Kotlin属性和字段与基本语法

Kotlin类与对象 ① ( 成员属性 | Kotlin 自动为成员字段生成 getter 和 setter 方法 | 手动设置成员的 getter 和 setter 方法 | 计算属性 )