《Kotlin核心编程》笔记:面向对象

kotlin 中的类

// Kotlin中的一个类
class Bird {
   
    val weight: Double = 500.0
    val color: String = "blue"
    val age: Int = 1

    fun fly() {
    } // 全局可见
}

把上述代码反编译成Java的版本,然后分析它们具体的差异:

public final class Bird {
   
    private final double weight = 500.0D;

    @NotNull
    private final String color = "blue";

    private final int age = 1;

    @NotNull
    public final double getWeight() {
   
        return this.weight;
    }

    @NotNull
    public final String getColor() {
   
        return this.color;
    }

    public final int getAge() {
   
        return this.age;
    }

    public final void fly() {
   
 
    }
}

虽然Kotlin中类声明的语法非常近似Java,但也存在很多不同:

  • 1)不可变属性成员。Kotlin用val在类中声明引用不可变的属性成员,是利用Java中的final修饰符来实现的,使用var声明的属性则反之引用可变。
  • 2)属性默认值。因为Java的属性都有默认值,所以在声明时我们不需要指定默认值。而在Kotlin中,除非显式地声明延迟初始化,不然就必须指定默认值
  • 3)不同的可访问修饰符。Kotlin类中的成员默认是全局可见,而Java的默认可见域是包作用域,因此在Java版本中,我们必须采用public修饰才能达相同的效果。

可带有属性和默认方法的接口

众所周知,Java 8 引入了一个新特性——接口方法支持默认实现:

// Java 8 中的接口
public interface Flyer {
   
    String kind();

    default void fly() {
   
        System.out.println("I can fly");
    }
}

这使得我们在向接口中新增方法时,之前继承过该接口的类则可以不需要实现这个新方法。

接下来再来看看在Kotlin中如何声明⼀个接口:

// Kotlin 中的接口
interface Flyer {
   
    val speed: Int
    fun kind()
    fun fly() {
   
        println("I can fly")
    }
}

与 java 类似,它还支持抽象属性,但是抽象属性不能有默认值,而在 Java 中接口中是可以有默认值的(Java 接口声明的成员变量默认是public static final的),这一点与Java不同。

假如在kotlin给它强行设置默认值,则编译器会报错:

interface Flyer {
   
	val height = 1000 //error Property initializers are not allowed in interfaces
}

Kotlin 提供了另外⼀种方式来实现这种效果,通过覆写成员的get()方法:

interface Flyer {
   
	val height
		get() = 1000
}

若没有指定默认行为,则在实现该接口的类中必须对该属性进行初始化。

更简洁地创建对象

Kotlin 中并没有new关键字,可以直接调用构造函数创建对象:

val bird = Bird() 

在Java中构造方法重载存在两个缺点:

  • 如果要支持任意参数组合来创建对象,那么需要实现的构造方法将会非常多。
  • 每个构造方法中的代码会存在冗余

构造方法默认参数

要解决构造方法过多的问题,在 Kotlin 中你只需要给构造方法的参数指定默认值,从而避免不必要的方法重载。

class Bird(val weight: Double = 0.0, val age: Int = 0, val color: String = "blue")
// 可以省略 {}

调用:

val bird1 = Bird(color = "black")
val bird2 = Bird(weight = 1000.00, color = "black") 

需要注意的是,由于参数默认值的存在,我们在创建类对象时,最好指定参数的名称,否则必须按照实际参数的顺序进行赋值。不然容易出现参数类型匹配不上的错误警告。

init 语句块

Kotlin引入了⼀种叫作init语句块的语法,它属于上述构造方法的⼀部分,两者在表现形式上却是分离的。

当构造方法的参数没有valvar修饰的时候,可以在init语句块被直接调用。

class Bird(
    weight: Double = 0.00, // 参数名前没有 val 
    age: Int = 0,
    color: String = "blue"
) {
   
    val weight: Double
    val age: Int
    val color: String

    init {
   
        this.weight = weight // 构造方法参数可以在 init 语句块被调用 
        this.age = age
        this.color = color
    }
}

如果我们需要在初始化时进行其他的额外操作,那么我们就可以使用init语句块来执行。比如:

class Bird(weight: Double, age: Int, color: String) {
   
    init {
   
        println("do some other things")
        println("the weight is $weight")
    }
}

其实它们还可以用于初始化类内部的属性成员的情况:

class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
   
    val weight: Double = weight // 在初始化属性成员时调用 weight
    val age: Int = age
    val color: String = color
}

除此之外,我们并不能在其他地方使用。以下是一个错误的用法:

class Bird(weight: Double, age: Int, color: String) {
   
    fun printWeight() {
   
        print(weight) // Unresolved reference: weight 
    }
}

事实上,构造方法可以拥有多个init块,它们会在对象被创建时按照类中从上到下的顺序先后执行。

看看以下代码的执行结果:

class Bird(weight: Double, age: Int, color: String) {
   
    val weight: Double
    val age: Int
    val color: String

    init {
   
        this.weight = weight
        println("The bird's weight is ${
     this.weight}.")
        this.age = age
        println("The bird's age is ${
     this.age}.")
    }

    init {
   
        this.color = color
        println("The bird's color is ${
     this.color}.")
    }
}

fun main(args: Array<String>) {
   
    val bird = Bird(1000.0, 2, "blue")
}

运行结果:

The bird's weight is 1000.0. 
The bird's age is 2.
The bird's color is bule. 

可以发现,多个init语句块有利于我们进一步对初始化的操作进行职能分离,这在复杂的业务开发(如Android)中显得特别有用。

注意:正常情况下,Kotlin规定类中的所有非抽象属性成员都必须在对象创建时被初始化值。

下面代码会运行报错:

class Bird(val weight: Double, val age: Int, val color: String) {
   
    val sex: String

    fun printSex() {
   
        this.sex = if (this.color == "yellow") "male" else "female"
        println(this.sex)
    }
}

fun main(args: Array<String>) {
   
    val bird = Bird(1000.0, 2, "blue")
    bird.printSex()
}

运行结果

Error:(2, 1) Property must be initialized or be abstract 
Error:(5, 8) Val cannot be reassigned

由于sex必须被初始化值,上述的printSex方法中,sex会被视为二次赋值,这对val声明的变量来说也是不允许的。

我们可以把sex变成用var声明并指定默认值,但是假如我们不想要默认值,可以声明为可空类型String?,这样默认值就是null

然而实际上也许我们又不想让sex具有可空性,而只是想稍后再进行赋值。

延迟初始化:by lazy 和 lateinit

更好的做法是让sex能够延迟初始化,即它可以不用在类对象初始化的时候就必须有值。

在Kotlin中,我们主要使用 lateinitby lazy 这两种语法来实现延迟初始化的效果。

class Bird(val weight: Double, val age: Int, val color: String) {
   
    val sex: String by lazy {
   
        if (color == "yellow") "male" else "female"
    }
}

fun main() {
   
    val bird = Bird(1000.0, 2, 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值