面向对象(一)

Kotlin 支持面向对象编程,Kotlin 提供了定义类、属性、方法等最基本的功能。类可被认为是一种自定义的数据类型,可以使用类来定义变量,所有使用类定义的变量都是引用变量,它们将会引用类的对象。类用于描述客观世界中某一类对象的共同特征,而对象则是类的具体存在, Kotlin也使用构造器来创建类的对象。

Kotlin 也支持面向对象的三大特征 : 封装、继承和多态。 Kotlin 提供了 private、 protected、 internel 和 public 四个访问控制符来实现良好的封装;子类继承父类就可以继承父类的属性和方法,如果访问权限允许,子类对象可以直接调用父类中定义的方法。

类和对象

类是面向对象的重要内容,可以把类当成一种自定义类型,可以使用类来定义变量,这种类型的变量统称为引用变量。也就是说,所有类都是引用类型。

定义类

在面向对象的程序设计过程中有两个重要概念:类( class)和对象( object,也被称为实例,instance)。其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。
Kotlin 定义类的简单语法如下:

[修饰符] class 类名 [constructor 主构造器]{
零个到多个次构造器定义 ...
零个到多个属性 ...
零个到多个方法 ...
}

在上面的语法格式中,修饰符可以是 public | internal | private (只能出现其中之一)、 final | open | abstract (也只能出现其中之一),或者完全省略修饰符。类名只要是一个合法的标识符即可。

Kotlin 的类定义由类名、类头(指定其泛型声明、主构造器等)和用花括号包围的类体构成。类头和类体都是可选的。 对于一个类定义而言,可以包含三种最常见的成员:构造器、属性和方法,这三种成员都可以定义零个或多个,如果三种成员都只定义零个,就是定义了一个空类。空类没有类体,可以省略花括号。例如,如下类定义是允许的。

    class Empty

属性用于定义该类的对象所包含的状态数据,方法则用于定义该类的对象的行为特征或者功能实现。构造器用于构造该类的对象, Kotlin通过调用构造返回该类的对象(无须使用 new)。

构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建对象。一个 Kotlin类可以有 0~1 个主构造器和 0~N个次构造器。主构造器是类头的一部分,它跟在类名(和泛型声明)后。

class User constructor(firstName:String){
}

constructor(firstName:String) 就是主构造器(主构造器属于类头部分),由此可见,主构造器就是在类头使用 constructor关键字定义一个无执行体的构造器。虽然主构造器不能定义执行体,但可以定义多个形参,这些形参可以在属性声明、初始化块中使用。

如果主构造器没有任何注解或修饰符,则可以省略 constructor关键字。即上面代码可改为如下形式:

class User (firstName:String){
}

Kotlin 还提供了一个功能:如果程序员没有为非抽象类定义任何(主或次)构造器,系统会自动提供一个无参数的主构造器,该构造器默认用 public修饰。一旦程序员为一个类提供了构造器,系统将不再为该类提供构造器。

定义属性的语法格式如下:

[修饰符) var | val 属性名:类型[=默认值]
[<getter>]
[<setter>]

对定义属性的语法格式详细说明如下。

修饰符 : 修饰符可以省略,也可以是 public | protected | internal | private、final | open | abstract, public | protected | private 只能出现其中之一, final | open | abstract也只能出现其中之一。对于属性使用 public、 protected、 internal 修饰和不使用访问控制符,效果是一样的。如果使用 private修饰该属性,该属性将作为幕后属性使用。
var | val : 使用 var 声明读写属性 ,使用 val 声明只读属性(不能修改) 。
属性名 : 从语法的角度来说,属性名只要是一个合法的标识符即可。
类型 : 类型可以是 Kotlin 允许的任何数据类型。如果 Kotlin 可以从初始值或 getter方法的返回值推断出属性的类型,那么此处可省略属性的类型 。
默认值 : 定义属性时还可以指定一个可选的默认值。要么在此处指定初始值要么在构造器或初始化块中指定初始值 。
getter、 setter : 用于为该属性编写自定义的 getter、 setter 方法。 如果不指定,Kotlin会为读写属性提供默认的 getter、 setter 方法:为只读属性提供默认的 getter 方法(只读属性不允许定义 setter方法)。

定义方法的语法和前面定义函数的语法完全 一样,换句话说,如果将函数放在类中定义,那么它就变成了方法。独立的才叫函数,在类中定义的叫方法。

顶层函数的语法与类中方法的语法的主要区别就在于修饰符:顶层函数不能使用 protected、abstract和final修饰符,但类中的方法可使用public | protected | internal | private | final | abstract | open 这些修饰符。

定义构造器的语法格式如下:

[修饰符] constructor (形参列表){
//由零条到多条可执行语句组成的构造器执行体
}

对定义构造器的语法格式详细说明如下。

修饰符 : 修饰符可以省略,也可以是 public、 protected、 internal、 private其中之一。
形参列表 : 其格式和定义方法的形参列表的格式完全相同。值得指出的是,构造器既不能定义返回值类型,也不能使用Unit声明构造器没有返回值。

class Person {
    //下面定义了两个属性
    var name: String = ""
    var age: Int = 0
    //下面定义了一个 say 方法
    fun say(content: String) {
        println(content)
    }
}

在上面的Person类中没有定义构造器,系统将为它提供一个默认的构造器,系统提供的构造器总是没有参数的 。

对象的产生和使用

创建对象的根本途径是构造器,调用某个类的构造器即可创建这个类的对象,并且无须使用 new关键字。

    //使用 Person 类定义一个 Person 类型的变量
    var p: Person
    //调用 Person 类的构造器,返回一个 Person 对象 
    // 将该 Person 对象赋给 p 变量
    p = Person()

创建对象之后,接下来即可使用该对象了。如果访问权限允许,那么在类中定义的方法和属性都可通过对象来调用。通过对象访问方法或属性的语法是:对象.属性|方法(参数)。在这种方式中,对象是主调者,用于访问该对象的属性或方法。

    var p: Person = Person()
    //访问 p 的name 属性,直接为该属性赋值
    p.name ="xq"
    //调用 p 的 say ()方法
    p.say("sss")
    //直接输出 p 的 name 属性
    println(p.name)

方法详解

方法是类或对象的行为特征的抽象,方法是类或对象最重要的组成部分。但 Kotlin 的方法并不仅仅是单纯的方法, Kotlin 的方法与函数也有极大的关系 。

方法与函数的关系

Kotlin 的方法与函数其实是统一的,不仅定义函数和方法的语法相同,而且定义在类中的方法依然可独立出来。也就是说,即使我们将方法定义在类里面,这个方法也依然可以转换为函数。

class Dog {
    //定义一个无参数的 run 方法
    //其类型是()->Unit
    fun run() {
        println("run方法")
    }

    //定义一个带 String 参数 的 eat 方法
    //其类型是 (String)->Unit
    fun eat(food: String) {
        println("正在吃:$food")

    }
}

fun main(args: Array<String>) {
    //将 Dog 的 run 方法赋值给 rn 变量
    //rn 变量的类型应该是(Dog)->Unit
    var rn:(Dog)->Unit = Dog::run

    val d = Dog()
    rn(d)

    //将 Dog 的 eat 方法赋值给 et 变量
    //et 变量的类型应该是( Dog, String)->Unit
    var et:(Dog,String)->Unit= Dog::eat
    et(d,"肉骨头")
}

上面程序定义了 一个 Dog 类,并为该 Dog 类定义了两个方法,但这两个方法依然可以被分离出来使用 。 接下来程序的 main()函数中的两行粗体字代码负责将 run()、 eat()方法依次赋值 给rn、 et变量。

为了与前面介绍的获取函数的引用保持一致,这里要获取某个方法的引用同样需要在方法名前面添加两个冒号(::) 。 由于此处的方法属于特定的类,因此还需要在方法引用之前添加类名 。
var rn:(Dog)->Unit = Dog::run,我们显式指定了rn变量的类型, 虽然 Dog 的 run()方法是()->Unit类型,但当程序将 run()方法独立成函数时,调用 run()方法的调用者(Dog对象)将作为第一个参数传入,因此 Dog::run 的实际类型是(Dog)->Unit。

中缀表示法

Kotlin 的方法还可使用 infix 修饰,这样该方法就可通过中缀表示法调用,就像这些方法是双目运算符一样。 需要指出的是, infix 方法只能有一个参数,原因很简单,因为双目运算符的后面只能带一个参数 。

class ApplePack(weight: Double) {
    var weight = weight
    override fun toString(): String {
        return "ApplePack[weight=${this.weight})"
    }
}

class Apple(weight: Double) {
    var weight = weight

    override fun toString(): String {
        return "Apple[weight=${this.weight})"
    }

    //定义中缀方法,使用 infix 修饰
    infix fun add(other: Apple): ApplePack {
       return ApplePack(this.weight + other.weight)
    }

    //定义中缀方法,使用 infix 修饰
    infix fun drop(other: Apple):Apple{
        this.weight -= other.weight
        return this
    }
}

fun main(args: Array<String>) {
    var origin =Apple(2.4)
    //使用 add方法
    val ap = origin add Apple(2.4)
    println(ap)

    origin drop Apple(3.5)
    println(origin)
}  

上面程序中定义了两个 infix 方法,其实没有任何特别的地方,只要注意两点:为方法添加 infix修饰;该方法只有一个形参。

接下来 main()函数中示范了使用双目运算符的语法调用 infix 方法——两个 Apple 执行 add 运算(调用 add()方法)时,程序返回了一个 ApplePack 实例;两个 Apple 执行 drop 运算(执行 drop()方法)时,程序将名为 origin 的 Apple 对象的weight减少了。

componentN方法与解构

Kotlin 允许将一个对象的 N个属性“解构”给多个变量,写法如下:

var (name, pass) = user

上面这行代码相当于将 user对象的两个属性分别赋值给 name、 pass 两个变量,这两个变量的类型会根据 user对象的属性类型来推断。

Kotlin 怎么知道把哪两个属性分别赋值给 name、pass变量呢?其实 Kotlin会将上面的赋值代码转换为如下两行:

var name= user.component1()
var pass= user.component2()

从上面介绍可以看出,如果希望将对象解构给多个变量,那么必须为该对象的类定义componentN()方法。程序希望将对象解构给几个变量,就需要为该类定义几个 componentN() 方法,且该方法需要使用 operator修饰。

下面程序示范了为 User 类定义 componentN()方法,然后即可将该对象解构给 3 个变量。

class User(name: String, pass: String, age: Int) {
    var name = name
    var pass = pass
    var age = age
    //定义 operator 修饰的 componentN 方法,用于解构
    operator fun component1(): String {
        return this.name
    }

    //定义 operator 修饰的 componentN 方法,用于解构
    operator fun component2(): String {
        return this.pass
    }


    //定义 operator 修饰的 componentN 方法,用于解构
    operator fun component3(): Int {
        return this.age
    }

}

fun main(args: Array<String>) {
    var user :User = User("xq","123",18)
    //将 User 对象解构给 2 个变量
    //只利用 user 对象的 component1 ()和 component2 ()方法
    var (name, pass : String) = user
    println(name)
    println(pass)
    
}

上面程序中为 User类定义了 3个 operator修饰的 compoentN()方法,这就表明该类的实例最多可同时解构给 3 个变量。
接下来 main()函数中的代码将 user对象解构给 2个变量,此时将只解构 user 对象的前两个 componentN()方法的返回值。这行代码还为 pass变量声明了类型: String,实际 上该类型是可推断出来的,因此完全可以不指定。

在某些时候,程序希望解构对象后面几个 componentN()方法的返回值、忽略前面几个 componentN()方法的返回值,此时可通过下画线(_)来占位

    //如果不想要前面的某个属性,用“_”代替它
    var (_,passs, age) = user

理解了对象解构的用法之后,接下来详细谈谈遍历 Map 的语法。我们知道可使用如下语法来遍历 Map。

for ((key, value) in map) {
     //使用 key、 value
}

程序可通过 for-in循环来遍历 Map结构,这是由于 Map包含了 一个 operator修饰的 iterator() 方法,且该方法返回了 Iterator<Map.Entry<K,V>>对象。

Iterator代表了可迭代的对象,它的泛型参数代表了被迭代的元素的类型一Map.Entry<K,V> (Entry是Map的嵌套类),而Map.Entry则定义了如下两个方法:

operator fun <K, V> Map . Entry<K, V>.component1() = getKey ()
operator fun <K, V> Map.Entry<K, V>.component2 () = getValue ()

上面两个方法就是 Map.Entry提供的 operator修饰的 componentN()方法,因此程序可执行如下解构:

var (key, value) = Map.Entry 对象

Map 本身是可迭代的,我们原本可按如下方式遍历 Map 集合

for(entry in map) { 
    //使用entry获取key、 value
}

但是由于Kotlin允许对entry执行解构操作,因此上面的遍历可改写为如下形式:

for ((key, value) in map) {
     //使用 key、 value
}

数据类和返回多个值的函数

Kotlin 本身并不支持定义返回多个值的函数或方法,但通过上面所介绍的对象解构,我们同样可让 Kotlin 函数返回多个值,本质是让 Kotlin 返回一个支持解构的对象。

为了简化解构的实现,Kotlin 提供了 一种特殊的类:数据类。数据类专门用于封装数据 。
数据类除使用 data修饰之外,还要满足如下要求。

  • 构造器至少需要有一个参数。
  • 构造器的所有参数需要用 val 或 var 声明为属性。
  • 数据类不能用 abstract、 open、 sealed修饰,也不能定义成内部类。
  • 在Kotlin1.1之前,数据类只能实现接口:现在数据类也可继承其他类。定义数据类之后,系统自动为数据类生成如下内容 。
  • 生成 equals()、hashCode()方法。
  • 自动重写 toString()方法,返回形如”User(name=John,age=42)”的字符串。
  • 为每个属性自动生成 operator修饰的 componentN()方法。
  • 生成 copy()方法,用于完成对象复制。

下面程序将会先定义一个数据类,然后通过数据类来实现返回多个值的函数 。

//定义一个数据类
//数据类会自动为每个属性定义对应的 componentN 方法
data class Result(val result:Int,val status:String)

fun factorial1(n:Int):Result{
    if(n==1){
        return Result(1,"成功")
    }else if(n>1){
        return Result(factorial1(n-1).result*n,"成功")
    }else{
        return Result(-1,"参数必须大于0")
    }
}

fun main(args: Array<String>) {
    //通过解构获取函数返回的两个值
    var(rt,sta) = factorial1(6)

    println(rt)
    println(sta)
}

上面程序中定义了一个factorial1()函数,该函数的返回值类型是 Result 数据类,该数据类会自动为它的 result、 status 两个属性生成 componentN()方法,因此 Result对象支持解构。

上面程序的 main()函数中的代码将 factorial1()函数的执行结果解构给rt、sta两个变量,这就相当于让该函数返回了两个值。

在Lamdba表达式中解构

Kotlin允许对Lambda 表达式使用解构 ,如果 Lambda 表达式的参数是支持解构的类型(如 Pair 或 Map.Entry 等 ,它们都具有 operator 修饰的 componentN()方法),那么即可通过将它们放在括号中引入多个新参数来代替单个参数。例如,如下两种写法是 一样的。

fun main(args: Array<String>) {
    var map = mapOf<Int, String>(1 to "xq")
    map.mapKeys { entry -> entry.value }
    ///使用解构,将 entry 解构成( key, value)
    map.mapKeys { (key, value) -> value }
}

请注意 Lambda表达式包含两个参数和使用解构的区别:

    { a ->...}//一个参数
    { a , b ->...}//两个参数
    {(a, b) -> ... }//一个解构对
    { (a, b) , c -> ... }// 一个解构对和第三个参数

从上面代码不难看出,Lambda表达式的多个参数是不需要使用圆括号的,只要看到在 Lambda表达式的形参列表中出现圆括号,那就是使用解构。
类似的,如果希望只使用后面几个 componentN()方法的返回值,则可使用下画线来代替。

属性和字段

属性是Kotlin的一个重要特色, Kotlin的属性相当于Java的字段 (field)再加上getter和 setter方法(只读属性没有setter方法),而且开发者不需要自己实现 getter和 setter方法。

读写属性和只读属性

Kotlin 使用 val 定义只读属性,使用var定义读写属性 ,系统会为只读属性生成 getter 方法,会为读写属性生成 getter和 setter方法。
在定义 Kotlin 的普通属性时,需要显式指定初始值:要么在定义时指定初始值,要么在构造器中指定初始值。

下面代码定义了一个 Address 类,并为该类定义了多个属性 。

class Address {
    var street = ""
    var city = ""
    var province = ""
    var postcode: String? = null
}

fun main(args: Array<String>) {
    var address = Address()
    //通过点语法对属性赋值,实际就是调用 setter 方法
    address.city="北京"
    //通过点语法访问属性,实际就是调用 getter 方法
    println(address.city)
}

对于上面的 Address类,相当于如下 Java类。

public class Address {
    private String street;
    private String city;
    private String province;
    private String postcode;

    public Address(){

    }

    // street 的 setter 和 getter 方法
    public final String getStreet() {
        return street;
    }

    public final void setStreet(String street) {
        this.street = street;
    }

    public final String getCity() {
        return city;
    }

    public final void setCity(String city) {
        this.city = city;
    }

    public final String getProvince() {
        return province;
    }

    public final void setProvince(String province) {
        this.province = province;
    }

    public final String getPostcode() {
        return postcode;
    }

    public final void setPostcode(String postcode) {
        this.postcode = postcode;
    }
}

从上面代码可以看出, Kotlin 定义一个属性,就相当于定义一个Java 类的 private修饰的field,以及 public、 final修饰的 getter和 setter方法。

需要指出的是,虽然 Kotlin确实会为属性生成 getter、 setter方法,但由于源程序中并未真正定义这些 getter、 setter 方法,因此 Kotlin 程序不允许直接调用 Address 对象的 getter、 setter 方法。但如果使用 Java 程序来调用 Address 类,由于该 Address 类中各属性对应的 field都用了private 修饰,因此不能用点语法直接访问这些field,所以只能用 getter、 setter 方法来访问属性。

class Item(name: String, price: String) {
    //定义属性,使用主构造器的参数为它们分配初始值
    val name =name
    val price = price
}

对于上面的 Item类,相当于如下 Java类。

public class Item {
    private String name;
    private String price;

    public Item(String name, String price) {
        this.name = name;
        this.price = price;
    }

    public final String getName() {
        return name;
    }

    public final String getPrice() {
        return price;
    }
}

Kotlin 程序使用 Item 类时,同样只能用点语法来获取其属性值;但如果是 Java 程序使用 Item类,则需要使用 getter方法来获取属性值。

自定义 getter 和 setter

在定义属性时可指定自定义的 getter和 setter方法,这些方法可加入自己的控制逻辑,其中getter是一个形如 get(){}的方法(当然也可使用单表达式方法体) , getter应该是无参数、带一个返回值的方法: setter是一个形如 set(value){}的方法(当然也可使用单表达式方法体),setter应该是带一个参数、无返回值的方法。定义getter、setter方法时无须使用 fun关键字。

class User(first: String, last: String) {
    var first = first
    var last = last
    val fullName: String
    //自定义 getter 方法
        get() {
            println("执行 fullName 的 getter 方法")
            return last + "." + first
        }

    //由于可通过 getter 方法推断出该属性的类型,因此可省略类型声明
    var fullNames
    //使用单表达式定义 getter 方法 的方法体
        get() = "${first} .${last}"
        set(value) {
            println("执行sex的setter方法")
            //value字符串中不包含“.”或包含几个“.”都不行
            if("." !in value || value.indexOf(".") != value.lastIndexOf(".")){
                println("您输入的 fullName 不合法")
            }else{
                var tokens = value.split(".")
                first = tokens[0]
                last = tokens[1]
            }
        }

}

fun main(args: Array<String>) {
    var user = User("悟空", "孙")
    //输出 user.fullName,实际上是调用其 getter 方法返回值
    println(user.fullName)

    user.fullNames ="八戒.猪"
    println(user.fullNames)
}

代码为 User类定义了fullName属性,并为该属性重新定义了getter方法。 由于该属性是一个只读属性,因此系统不需要为它生成 setter 方法。需要指出的是,由于fullName并不需要真正存储状态 ,它的返回值其实是通过 first 和 last 两个属性计算出来的, 因此 Kotlin 也不需要为其生成对应的 field。当 Kotlin不需要为该属性生成对应的 field 时,也就不能为该属性指定初始值,所以上面程序没有为fullName属性指定初始值。

上面程序将fullNames定义成一个读写属性,程序重写了它的getter方法。由于getter方法的方法体非常简单,故此处使用单表达式作为方法体。如果 Kotlin 可根据 getter方法的返回值推断出该属性的类型,那么在定义该属性时也可省略声明属性的类型 。
此外,该程序还为 fullNames 属性定义了 setter方法,该 setter方法会将程序传入的字符串分解成两个部分,分别赋值给 first和 last属性。
从上面介绍可以看出,虽然 fullNames 是一个读写属性,但其实该属性并不保存状态,因此 Kotlin 依然不需要为该属性生成 field,故该属性同样不能指定初始值。

如果仅需要改变 getter或 setter方法的可见性或者对其添加注解,但不需要改变默认的实现,那么 Kotlin 允许只自定义 getter或 setter方法名,而不重新定义其代码实现 。 例如如下代码:

    var foot ="abc"
    //将 setter 方法改为 private 修饰,但依然使用默认实现
    private set
    //使用自 Inject 修饰 getter 方法,但依然使用默认实现
    @Inject get

幕后字段

前面己经多次提到,在Kotlin中定义一个普通属性时, Kotlin 会为该属性生成一个 field (字段)、 getter和 setter方法(只读属性没有 setter方法)。 Kotlin为该属性所生成的field就被称为幕后字段 (backing field)。
此处有一点需要强调: 如果Kotlin类的属性有幕后字段, 则Kotlin要求为该属性显式指定初始值,要么在定义时指定,要么在构造器中指定: 如果Kotlin类的属性没有幕后字段, 则 Kotlin 不允许为该属性指定初始值(这是理所当然的 , 由于没有 field,即使指定了初始值也没地方保存) 。那么 Kotlin 何时会为属性生成幕后字段呢?只要满足以下条件 ,系统就会为属性生成幕后字段 。

  • 该属性使用 Kotlin 自动生成的 getter和 setter方法或其中之一。换句话说,对于只读属性,必须重写 getter方法:对于读写属性,必须重写getter、 setter方法 : 否则总会为该属性生成幕后字段 。
  • 重写 getter、 setter方法时,使用field关键字显式引用了幕后字段。

通过上面描述可以发现,Kotlin允许开发者在 getter或setter方法中通过 field 引用系统自动生成的字段(幕后字段)。例如,有时候我们希望对用户设置的属性值进行控制,此时就可以重写setter方法,并在 setter方法中加入自己的控制。例如如下代码。

class Person(name:String,age:Int){
    var name =name
    set(newName) {
        //执行合理性校验,要求用户名必须在 2-6 位之间
        if(newName.length<2 || newName.length>6){
            println("”您设置的人名不符合要求”")
        }else{
            field = newName
        }
    }

    var age = age
    set(newAge) {
        //执行合理性校验,要求用户年龄必须在 0-120 之间
        if(newAge<0 || newAge>120){
            println("”您设置的年龄不合法”")
        }else{
            field = newAge
        }
    }
}

fun main(args: Array<String>) {
    var p = Person("xq",18)
    //赋值非法,赋值失败
    p.name= "x"
    println(p.name)
    p.name = "sqq"
    println(p.name)
}

当程序重写getter或setter方法时,不能通过点语法来对name、age赋值 ! 假如在name的 setter方法中使用点语法对name赋值,由于点语法赋值的本质是调用 setter方法,这样就会造成在setter方法中再次调用setter方法,从而形成无限递归。因此,上面程序重写 name、age属性的 setter方法时 , 实际上是通过 field 引用幕后字段,从而实现对幕后字段的赋值的。

幕后属性

在个别情况下,开发者希望自己定义field,并为该field提供setter、 getter方法,就像 Java 所使用的方法。此时可使用 Kotlin 的幕后属性。
幕后属性就是用 private修饰的属性, Kotlin不会为幕后属性生成任何getter、 setter方法。 因此程序不能直接访问幕后属性,必须由开发者为幕后属性提供 getter、 setter方法 。

例如如下程序:

class BackingProperty(name: String) {
    //定义 private 修饰的属性,该属性是幕后属性
    private var _name: String = name
    var name
        //重写 getter 方法,返回幕后属性的值
        get() = _name
        set(newName) {
            //执行合理性校验,要求用户名必须在 2-6 位之间
            if (newName.length < 2 || newName.length > 6) {
                println("”您设置的人名不符合要求”")
            } else {
                _name = newName
            }
        }
}

fun main(args: Array<String>) {
    var p = BackingProperty("xq")
    //访问 p.name,实际上会转为访问幕后属性_name
    println(p.name)

}

上面程序中代码定义了一个private修饰的_name属性,该属性就是一个幕后属性, Kotlin不会为该属性生成 getter、setter方法,因此程序无法直接访问 Person对象的_name属性。 接下来程序定义了一个 name属性,井重写了 name属性的 getter、setter方法,重写的 getter、setter方法实际上访问的是 name 幕后属性.
上面这种方式就是Java的做法:先定义一个 private 修饰的字段,然后再为该字段定义public修饰的 getter、setter方法。这种方式比较烦琐,通常没有必要采用如此烦琐的方式。

延迟初始化属性

Kotlin要求所有属性必须显式初始化,要么在定义该属性时赋初始值;要么在构造器中对该属性赋初始值。但在某些时候,这不是必需的。比如我们可能通过依赖注入为属性设置初始值,或者在单元测试的 setUp方法中初始化该属性...。总之并不需要在定义属性时或在构造器中对属性执行初始化。

Kotlin提供了lateinit修饰符来解决属性的延迟初始化。使用lateinit修饰的属性,可以在定义该属性时和在构造器中都不指定初始值。
对 lateinit修饰符有以下限制。

  • lateinit 只能修饰在类体中声明的可变属性(使用 val 声明的属性不行,在主构造器中声明的属性也不行)。
  • lateinit修饰的属性不能有自定义的 getter或 setter方法。
  • lateinit修饰的属性必须是非空类型。
  • lateinit修饰的属性不能是原生类型(即 Java 的 8 种基本类型对应的类型)。

与 Java 不同的是, Kotlin 不会为属性执行默认初始化。因此,如果在 lateinit 属性赋初始值之前访问它,程序将会引发异常。

class User{
    //延迟初始化属性
    lateinit var name:String
}

fun main(args: Array<String>) {
    var user = User()
    //不能在初始化值之前使用
    //user.name
    user.name ="xq"
    println(user.name)
}

内联属性

从 Kotlin1.1开始, inline修饰符可修饰没有幕后字段的属性的 getter或 setter方法,既可单独修饰属性的getter或setter方法;也可修饰属性本身,这相当于同时修饰该属性的getter和 setter方法。
对于使用 inline修饰的 getter、setter方法,就像前面介绍的内联函数一样,程序在调用 getter 或 setter方法时也会执行内联化。

class Name(name: String, desc: String) {
    var name = name
    var desc = desc
}

class Product {
    var productor: String? = null
    //inline 修饰属性的 getter 方法,表明读取属性时会内联化
    val proName: Name
        inline get() = Name("xq", "null")

    //inline 修饰属性的 setter 方法,表明设置属性时会内联化
    var author: Name
        get() = Name("xy", "111")
        inline set(value) {
            this.productor = value.name
        }


    //inline修饰属性本身,表明读取和设置属性时都会内联化
    inline var pubHouse: Name
        get() = Name("sq", "22")
        set(value) {
            this.productor = value.name
        }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容