Kotlin学习之旅(D3)-类与继承

版权声明:本文作者为BlueLzy,原出处为CSDN博客 - http://blog.csdn.net/blue_zy,转载请注明出处。 https://blog.csdn.net/blue_zy/article/details/82989640

Day 3

Kotlin学习之旅-第三天

今天的主题是:类与继承

前言

Kotlin学习之旅(D1)-学习计划&基本语法

Kotlin学习之旅(D2)-基本语法

今日目标

今天空闲的时候 baidu一下,发现简书,掘金上有很多Kotlin学习总结,但是基本上都是把官方文档一字不落地复制粘贴了过来。我不希望自己也是这样子的,因此在Kotlin学习之旅里面:

  • 我只会把最常用的归纳总结起来,其他的大家可以去官方文档进行查看
  • 除了官方文档有的知识点,也会加上自己的思考,标注一些不太容易理解,或者容易踩坑的地方
  • 根据自己经验,或者评论的意见,不断补充和修改

把这一系列的学习经验写成简洁易懂又实用的文章

话不多说, 今天我们的目标就是搞定下面几个知识点:

  • 类与对象基本用法
  • 数据类
  • 嵌套类
  • 内部类
  • 继承与接口

Tips:

Kotlin中文参考手册

Kotlin Docs Reference

类与对象基本用法

最基本的用法:

class Day3

没有类体,只有类名,连大括号都省了

然后在Day3中加上构造方法

class Day3 constructor(name: String) {...}  // 关键字constructor

但是一般我们看别人写的代码都是没有constructor这个关键字的,为什么呢?

官方文档给了答案:

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字

也就是说,Day3可以写成

class Day3(name: String) {...}

直接 类名(参数1,参数2){…} 这种格式就可以了

在kotlin里面,只能有一个主构造函数,和多个次构造函数,例如:

class Day3(name: String){  // 1
    constructor(name: String, age: Int): this(name){  // 2
        println("this is second constructor and the age is " + age)
    }
}

fun main(args: Array<String>) {
    var day3 = Day3("hello")  // 3
}
  1. Day3(…) 括号里的就是主构造函数,只是省略了constructor关键字
  2. { } 大括号里的 constructor(…) 就是次构造函数,每个次构造函数都要委托给主构造函数
  3. 实例化一个Day3对象

运行代码的结果:啥都没有~

因为我们没有在主构造函数里面做任何的操作,那么如果我们要做初始化操作,要怎么写呢?

class Day3(name: String){

    init {   // 1
        println("this is main constructor")
    }
    constructor(name: String, age: Int): this(name){
        println("this is second constructor and the age is " + age)
    }
}

fun main(args: Array<String>) {
    var day3 = Day3("hello")    // 2
    var day33 = Day3("hello", 1)  // 3
}
  1. init就是主构造函数的初始化方法
  2. 使用主构造函数初始化Day3对象
  3. 使用次构造函数初始化Day3对象

运行代码的结果:

this is main constructor
this is main constructor
this is second constructor and the age is 1

这里输出了两次main和一次second,原因就是上面我们讲到的 每个次构造函数都要委托给主构造函数 ,通过this关键字,在调用次构造函数之前,都会先调用一次主构造函数,因此会有两个main输出~

通过这个例子,应该就能弄懂 类的定义,主/次构造函数,init的用法,如何实例化对象了

数据类

Kotlin中通过 data 关键字来表示数据类:

data class dataClass(val name: String, val age: Int)

在实际开发中,数据类的使用是很常见的,那么Kotlin里的数据类具有哪些特性呢?

编译器自动从主构造函数中声明的所有属性导出以下成员:

  • equals()/hashCode() 对;
  • toString() 格式是 "User(name=John, age=42)"
  • componentN() 函数 按声明顺序对应于所有属性;
  • copy() 函数(见下文)。

为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求:

  • 主构造函数需要至少有一个参数;
  • 主构造函数的所有参数需要标记为 valvar
  • 数据类不能是抽象、开放、密封或者内部的;

因此,有两点需要注意:

  • 我们不能像普通类一样 class test(name: String, age: Int) 参数必须标记 val 或 var
  • 不能写 data class test(),而是必须至少有一个参数

这样创建数据类,编译器才不会报错~

请注意,对于那些自动生成的函数,编译器只使用在主构造函数内部定义的属性。如需在生成的实现中排出一个属性,请将其声明在类体中

这句话是什么意思呢?举个例子:

data class Person(val name: String) {
    var age: Int = 0
}
fun main(args: Array<String>) {
    val person1 = Person("John")
    val person2 = Person("John")
    person1.age = 10
    person2.age = 20
    println("person1 == person2: ${person1 == person2}")
    println("person1 with age ${person1.age}: ${person1}")
    println("person2 with age ${person2.age}: ${person2}")
}

输出结果:

person1 == person2: true
person1 with age 10: Person(name=John)
person2 with age 20: Person(name=John)

虽然age不一样,但是 == 结果是 true

说明只有在主构造函数内部定义的属性才具有toString()、 equals()、 hashCode() 、copy()这几个方法,由于属性age定义在类体中,因此是没有的。

最后,标准库提供了Pair和Triple这两个标准数据类

我们来看一下Pair的源码:

public data class Pair<out A, out B>(
        public val first: A,
        public val second: B
                                    ) : Serializable {

    /**
     * Returns string representation of the [Pair] including its [first] and [second] values.
     */
    public override fun toString(): String = "($first, $second)"
}

会发现其中他就是key-value格式的数据类,重写了toString()方法,其他的默认属性都是一样的~

在代码中运行一下:

fun main(args: Array<String>) {

    var pair = Pair("1","2")
    println(pair)
    println(pair.toString())
    println(pair.toList())
}

输出结果:

(1, 2)
(1, 2)
[1, 2]

Triple也是类似的,这里就不看了。数据类知识点大概就是这么多~

嵌套类与内部类

嵌套在类里面的类,就是嵌套类,这句话说起来比较拗口,直接看代码:

class Outer {   // 1
    private val bar: Int = 1
    class Nested {      // 2
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2
  1. 外部类
  2. 内部类

通过Outer.Nested().foo()调用内部类的方法

但是这个时候 Nested 类是不能访问Outer类的成员变量的,直接访问的话会报错~

如果需要,要加上inner关键字,让Nested成为内部类

class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1

继承与接口

这部分在实际应用中也是非常重要的,经常都会用到,毕竟抽象封装多态这三大特性,除了类与对象以外基本上就是通过继承和接口实现了

继承

我们都知道,在Java里面是只能实现单继承的,也就是一个子类只能有一个父类,但是通过接口的方式,其实也就相当于实现了多继承,在Kotlin里面也是一样的,我们先说继承

open class Father(name: String)     // 1
class Son(name: String) : Father(name)  // 2
  1. open class Father就是父类,主构造函数里面需要传入String类型的参数name
  2. Son继承自Father,需要用父类的主构造函数参数进行初始化

覆盖方法

继承自然免不了要覆盖父类的方法,Kotlin里面通过关键字override 来标识

open class Base {
    open fun v() { ... }   // 1
    fun nv() { ... }
}
class Derived() : Base() {
    override fun v() { ... }  // 2
}
  1. 被覆盖的方法需要用open标识
  2. 覆盖的方法需要用override标识

那么如果不想让子类继续覆盖要怎么做呢,只要加上final关键字就可以了

open class AnotherDerived() : Base() {
    final override fun v() { ... }
}

调用父类实现

在子类中可以通过super关键字来调用父类的属性和方法

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }
    
    override val x: Int get() = super.x + 1
}

接口

在Kotlin中,我们使用interface关键字来定义接口

interface MyInterface {    // 1
    fun bar()    // 2
    fun foo() {
      // 可选的方法体
    }
}
  1. interface 表示接口
  2. fun xxx() 定义方法名,不需要实现

接口中的继承

interface Named {
    val name: String
}

interface Person : Named {
    val firstName: String   // 1
    val lastName: String
    
    override val name: String get() = "$firstName $lastName"  // 2
}

data class Employee(
    // 不必实现“name”
    override val firstName: String,
    override val lastName: String,
    val position: Position
) : Person

可以看到,Person 继承 Named ,并且重写了 name 属性的get()方法,因此在Employee类实现Person接口的时候,只要实现 firstName, lastName 两个属性就可以了,而不用实现 name 属性

一般来说,我们都会使用到继承+接口两种方式,格式是这样的:

class Day3(name: String) : DayFather(), MyInterface{  // 1,2

    init {
        println("this is main constructor")
    }
    constructor(name: String, age: Int): this(name){
        println("this is second constructor and the age is " + age)
    }
    
     override fun test1() {   // 3
        TODO("not implemented") 
    }

    override fun test2() {
        TODO("not implemented")
    }
}

interface test{
    fun test1()
    fun test2()
}

open class DayFather{
    
}
  1. 通过 :Father() 来继承父类
  2. 通过 ,Interface 来实现接口
  3. 实现接口中定义的方法

总结

  • 类与对象基本用法
  • 数据类
  • 嵌套类
  • 内部类
  • 继承与接口

这几个知识点我们今天就学习完了,明天我们会继续学习 函数Lambda表达式

Day 3 - Learning Kotlin Trip,Completed.


猜你喜欢

转载自blog.csdn.net/blue_zy/article/details/82989640
D3