Kotlin专题「七」:类和继承

前言:不是所有的坚持都有结果,但是总有一些坚持,能从一寸冰封的土地里,培养出十万朵怒放的玫瑰。

一、概述

  任何一种面向对象的编程语言里面,类 class 是非常基础也是非常重要的,万物皆对象,对象就是我们所说的类,Kotlin 也不例外。这里讲解给大家介绍类的声明以及使用、类的主构造函数和辅助构造函数、类的继承、函数和属性的重写等。

这里先来介绍一下类的几种修饰符,它包含 classModifier 和 accessModifier

1.1 classModifier

  • open:     表示类可继承,类默认是final的;
  • final:     表示类不可继承,默认属性;
  • abstract:   表示抽象类;
  • enum:    表示枚举类;
  • annotation: 表示注解类;

1.2 accessModifier_

  • public:   表示公有,范围最大,所有调用的地方可见,具有最大的访问权限,可以被其他所有类访问,如果没有声明修饰符则系统会默认使用这个修饰符;
  • private:   表示私有,范围最小,仅在同一文件可见,只能被自己访问和修改;
  • protected: 表示私有+子类,同一个文件中或者子类可见,自己和子类及同一包中的类可以访问,不能用于顶层声明;
  • internal:  表示模块,同一个模块中可见。

二、类

2.1 类的声明

Kotlin 中的类使用关键字 class 来声明,后面紧跟类名:

    class A {
        //属性
        //TODO
        
        //函数
        //TODO
        
        //构造函数
        //TODO
        
        //内部类
        //TODO
    }

类的声明由类名,类头(指定其类型参数,主构造函数等)和类主体组成(初始化代码块、函数、属性、内部类、对象声明等),用花括号{}括起来。类头和类主体都是可选的,如果类没有主体,那么花括号{}可以省略。

	class B

类中的属性可以使用 var 声明为可变变量,用 val 声明为不可变变量:

    class Test {
        //成员属性
        var name = "Kotlin"
        val num = 0
    }

在类中定义成员函数:

    class Test {
        fun refresh() {
            Log.e("ClassActivity", "成员函数")
        }
    }

2.2 类的实例化和属性使用

(1) 类的实例化

创建一个类的实例,我们调用构造函数,就像它是一个常规函数一样:

    var chicken = Chicken()
    var chicken2 = Chicken("HelloWord")
    var chicken3 = Chicken("Do My Self", 20)

上面的例子就是实例化类的运用。注意:和 Java 不同的是,Kotlin 中没有new关键字。

(2) 类的属性使用

使用构造函数创建类实例,要使用类中的属性,使用类实例引用名称即可,成员函数也是同类。

    class Test {
        //成员属性
        var name = "Kotlin"
        val num = 0
		
		//成员函数
        fun refresh() {
            Log.e("ClassActivity", "成员函数")
        }
    }

    var test = Test()//类的实例化
    test.name//调用类中的属性,使用.号来引用
    test.num
    test.refresh()//调用成员方法

三、类的构造函数

在 Kotlin 类中可以有一个主构造函数和一个或多个辅助构造函数,主构造函数是类头的一部分,它位于类名(和可选类型参数)后面,使用 constructor 修饰主构造函数,格式如下:

类名 constructor(参数)

辅助构造函数格式:

class 类名 {
	constructor(参数……)
}

3.1 主构造函数

(1)constructor

主构造函数是类头的一部分,它位于类名(和可选类型参数)后面,使用 constructor 修饰主构造函数:

    class Student constructor(name: String){
		//TODO
    }

如果构造函数不具备任何的注解或者默认的可见修饰符时,关键字 constructor 可以省略;如果构造器有注解,或者有可见性修饰符,constructor 关键字是必须的,注解和修饰符要放在它之前。

    class Student(name: String){
		//TODO
    }

下面两种情况不能省略关键字 constructor

	//constructor不能省略
    class Student private constructor(name: String) {
		//TODO
    }
    class Student @Inject constructor(name: String) {
		//TODO
    }

(2)构造函数中的初始化代码块

主构造函数不能包含任何代码,初始化代码可以放在初始化代码块中,初始化代码块使用 init 关键字作为前缀:

    class Person constructor(name: String) {
        val TAG = "Person"
        val firstProperty = "First property: $name".also {
            Log.e(TAG, "初始化代码块 == $it")
        }

        init { //TODO 初始化代码块
            Log.e(TAG, "初始化代码块 == First initializer block:$name")
        }

        val secondProperty = "Second property: ${name.length}".also {
            Log.e(TAG, "初始化代码块 == $it")
        }

        init {
            Log.e(TAG, "初始化代码块 == Second initializer block:${name.length}")
        }
    }
    
    //调用
	var person = Person("Android")

在实例化过程中,初始化块按照它们在类体中出现的顺序执行,与属性初始化块交叉执行。打印数据如下:

初始化代码块 == First property: Android
初始化代码块 == First initializer block: Android
初始化代码块 == Second property: 7
初始化代码块 == Second initializer block: 7

注意:主构造函数的参数可以在初始化代码块中使用,也可以在类主体定义的属性初始化代码中使用。

    class People2(name: String) {
        var keyWord = name
    }

(3)简便用法

对于从主构造器函数声明和初始化属性,有一个简洁的语法:

    class People(var name: String, val type: Int) {
        //TODO
    }

与常规属性的方式非常相似,主构造函数中声明的属性可以是可变的 var 或只读的 val。上面类中相当于声明了两个参数:nametype,如果参数没有声明 varval,则默认为 val 类型。

3.2 辅助构造函数

Kotlin 中还支持辅助构造函数,也是使用 constructor 关键字修饰为前缀,如下:

    class cat {
        constructor(name: String)
    }

如果类有主构造函数,则每个辅助构造函数需要直接或者间接通过另一个辅助构造函数委托给主构造函数,在同一个类中委托给另一个构造函数使用 this 关键字。

    class Dog(var name: String) {
        //二级构造函数中的参数name是委托了主构造函数中的参数name
        constructor(name: String, age: Int, sex: String) : this(name) {
        }
    }

注意:初始化块中的代码有效地成为主构造函数的一部分。委托给主构造函数是作为辅助构造函数的第一条语句执行的。因此所有初始化块和属性初始化器中的代码都在辅助构造函数体之前执行。即使类没有主构造函数,委托仍然隐式地发生,初始化块仍然被执行。

    class Dragon {
        init {
            Log.e(TAG, "初始化代码块")
        }

        constructor(type: Int) {
            Log.e(TAG, "辅助构造函数:type == " + type)
        }
    }

	//执行
	var dragon = Dragon(10)

打印数据如下:

初始化代码块
辅助构造函数:type == 10

3.3 构造器私有

如果一个非抽象类没有声明任何构造函数(主构造函数和辅助构造函数),那么它将生成一个没有参数的主构造函数。构造函数的可见性是公有的,如果不希望你的类有一个公共构造函数,你需要声明一个空的主构造函数的非默认可见性。

    class Sheep private constructor() {//构造函数私有
		//TODO
    }

3.4 构造器参数默认值

在JVM虚拟机中,如果主构造函数所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值,这样 Kotlin 可以与库(像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例)一起更简单地使用。

    class Chicken(var name: String = "Kotlin") {
        init {//初始化代码块
            Log.e("ClassActivity", "构造函数有默认值: name == $name")
        }
		//辅助构造函数
        constructor(name: String = "Java", age: Int = 10) : this(name) {
            Log.e("ClassActivity", "构造函数有默认值: name == $name  | age == $age")
        }
    }

	var chicken = Chicken()
    var chicken2 = Chicken("HelloWord")
    var chicken3 = Chicken("Do My Self", 20)

打印数据如下:

构造函数有默认值: name == Kotlin
构造函数有默认值: name == HelloWord
构造函数有默认值: name == Do My Self
构造函数有默认值: name == Do My Self  | age == 20

可以看到实例化无参构造函数时,使用了参数的默认值;当实例化主构造函数时,只会执行 init 中的代码;当实例化辅助构造函数时,除了执行 init 中的代码外,还会执行辅助构造函数中的代码。

四、类的继承

4.1 超类 Any

  Kotlin 中的所有类都有一个通用的超类 Any,它是 Kotlin 层次结构的根,每个 Kotlin 类都有Any作为超类。也就是说如果一个类没有继承任何超类,那么这个类默认隐式继承超类 Any

	class Snake { //隐式继承Any
		//TODO
	}

超类 Any 有三个方法:equals()hashCode()toString(),它们是为所有 Kotlin 类定义的。

注意:默认情况下,Kotlin 类是final的,它们不能被继承,要使一个类可继承,使用open关键字标记:

	open class Base {//继承类是Open的
		//TODO
	}

Kotlin 中不在有extendsimplements关键字,取而代之的是冒号:表示,要声明一个显式超类型,可以将类型放在类头的冒号后面:

	open class Base(type: Int) {//继承类是Open的
    	//TODO
	}

	class Mouse(type: Int) : Base(type) {//有主构造函数,使用主构造函数的参数初始化Base
   		//TODO
	}

如果子类有主构造函数,则基类必须在主构造函数中使用主构造函数的参数立即被初始化。

如果子类没有主构造函数,那么每个辅助构造函数都必须使用 super 关键字初始化基类型,或者委托给另一个构造函数来执行该操作。注意:在这种情况下,不同的辅助构造函数可以调用基类型的不同构造函数:

    class MyView : View {
        constructor(ctx: Context) : super(ctx)
        
        constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
    }

举个例子:

	//基类
    open class Animal(name: String) {
        constructor(name: String, sex: String) : this(name) {
            Log.e(TAG, "基类辅助构造函数: name == $name  | sex == $sex")
        }
    }
	//子类
    class Tiger : Animal {//继承父类Animal
        constructor(name: String, sex: String, age: Int) : super(name, sex) {
            Log.e(TAG, "子类辅助构造函数: name == $name  | sex == $sex| age == $age")
        }
    }
    
	//调用
	var tiger = Tiger("老虎", "母老虎", 3)

打印数据如下:

基类辅助构造函数: name == 老虎 | sex == 母老虎
子类辅助构造函数: name == 老虎 | sex == 母老虎 | age == 3

4.2 方法重写

在基类中,使用 fun 声明函数时,此函数默认为 final 修饰,不能被子类重写,如果要允许子类重写该函数,那么要手动添加 open 关键字修饰它,子类重写方法使用 override 关键字:

	//基类
    open class Animal {
    	//open 关键字修饰才可以被子类重写
        open fun describe() {
            Log.e(TAG, "父类方法:金色花边老虎")
        }
        
        fun preyOn() {//没有 open 关键字修饰
            //TODO
        }
    }
	//子类
    class Tiger : Animal() {//继承父类Animal 
        override fun describe() {//重写父类中的方法,override修饰
            Log.e(TAG, "子类方法重写:它是纸老虎")
        }
        
	//override fun preyOn() {//报错,基类中preyOn()函数没有声明`open`,无论是否有`override`修饰都是非法的
	//
	//  }
    }
    
	//调用
	var tiger = Tiger()
    tiger.describe()

上面的子类重写基类的describe()函数需要用 override 关键字修饰,否则编译器会报错;如果基类函数上没有 open 修饰符,比如基类中的函数 preyOn() ,那么在子类中声明具有相同名字的方法是非法的,无论是否使用 override 关键字修饰它。当 open 修饰符被添加到最终类的成员上时,它没有任何作用(即没有 open 修饰的类)。打印数据如下:

子类方法重写:它是纸老虎

重写的方法即被 override 标记的成员方法默认是 open 修饰的,也就是说它可能也会被下一个子类重写,如果你想禁止这个成员被重写,使用 final 修饰:

    //子类
    class Tiger : Animal() {
        final override fun describe() {//默认为open,如果不想被重写则加`final`关键字,子类就无法重写该方法
        	//TODO
        }
    }

4.3 属性重写

属性重写的方式与方法重写类似,在基类上声明然后在子类上重新声明的属性必须用 override 关键字修饰作为前缀,而且它们不行具有兼容类型。每个声明的属性都可以由初始化器或get方法重写。

    //基类
    open class Animal {
        open val type: Int = 0
    }
    
    //子类
    class Tiger : Animal() {
        //重写父类中属性
        override val type = 8
    }

您可以用 var 属性重写 val 属性,但是反过来不行。因为 val 属性的本质上声明了一个 get 方法,重写为 var 属性会在在子类中额外声明了一个 set 方法。

注意:可以在主构造函数中使用 override 关键字作为声明属性的一部分。

    interface Shape {
        val type: Int
    }

    class Rectangle(override val type: Int = 2) : Shape
    
    class Rectangle2 : Shape {
        override val type: Int = 10 //以后可以设置为任何数字
    }

4.4 子类初始化顺序

在构造子类的新实例时,基类初始化是作为第一步完成的(之前只有求值基类构造函数的参数),因此在运行子类的初始化逻辑之前进行。

    //基类
    open class Base(name: String) {
        init {
            println("基类初始化")
        }

        open val length: Int = name.length.also {
            println("基类初始化 length == $it")
        }
    }

	//子类
    class Bovine(name: String, type: Int) : Base(name.toUpperCase().also { println("基类参数 name == $it") }) {
        init {
            println("子类初始化")
        }

        override val length: Int = (super.length + type).also {
            println("子类初始化 length == $it")
        }
    }

	//调用
	var bovine = Bovine("Kotlin", 4)

打印数据如下:

基类参数 name == KOTLIN
基类初始化
基类初始化 length == 6
子类初始化
子类初始化 length == 10

在执行基类构造函数时,子类中声明或者重写的属性还没有初始化。如果在基类初始化逻辑中使用了这些属性中的任何一个(直接或间接地,通过另一个被重写的 open 成员实现),可能会发生错误或者运行时报错。在设计基类时,因此应该避免在构造函数,属性初始化和 init 块中使用 open 成员。

4.5 调用基类实现

可以在子类中使用 super 关键字调用其基类的函数和属性访问器:

    //基类
    open class Animal {
        open fun describe() {
			//TODO
        }

        open val type: Int
            get() = 1000
    }

    //子类
    class Tiger : Animal() {
        override fun describe() {
            super.describe()//super关键字调用基类的函数
        }

        //super关键字调用基类的属性
        override val type = super.type
    }

在一个内部类中,访问外部类的超类是通过 super 关键字完成的,该关键字由外部类名称指定:super@Outer,即 super@ + 外部类超类的类名

    //基类
    open class Animal {
    	open val type: Int = 0
        open fun describe() {
            Log.e(TAG , "父类方法:金色花边老虎")
        }
    }

    //子类
    class Tiger : Animal() {
        //匿名内部类访问外部类的超类
        inner class Cat{
            fun appearance() {
                super@Tiger.describe() //调用基类Animal中的describe()的实现
                super@Tiger.type //调用基类Animal中的type 
                Log.e(TAG, "内部类访问外部类的超类:type == ${super@Tiger.type}")
            }
        }
    }

	//调用
	var tiger = Tiger()
    tiger.Cat().appearance()

打印数据如下:

父类方法:金色花边老虎
内部类访问外部类的超类:type == 0

4.6 规则重写

在 Kotlin 中,实现继承由以下规则管理:如果一个类从其直接多个基类中继承了同一个(同名)成员的多个实现,那么它必须重写这个成员并提供自己的实现(可能是使用继承的实现之一)。为了表示继承的实现所来自的基类类型,我们使用尖括号 super<> 中的基类名称限定的,例如:super<Base>

    //基类
    open class D {
        open fun describe() { println("父类D的函数") }
    }

    interface E {
        fun describe() { println("父类E的函数") }
    }

    class MixedBlood : D(), E {
        override fun describe() {
            super<D>.describe()//调用类 D 中的函数
            super<E>.describe()//调用接口 E 中的函数
            println("子类MixedBlood的实现")//MixedBlood 提供自己的实现
        }
    }

	//调用
	var mixedBlood = MixedBlood()
    mixedBlood.describe()

如果有多个相同方法(继承或实现自其他类,如:类 D, 接口 E),则必须重写该方法,使用super<父类名>选择性调用父类的实现。打印数据如下:

父类D的函数
父类E的函数
子类MixedBlood的实现

MixedBlood 继承自类D, 接口E,MixedBlood 不仅可以从 D 或者 E 中继承函数,而且 MixedBlood 可以继承 D 或者 E 中公有的函数,此时该函数在 MixedBlood 只有一个实现,为了消除歧义,重写的函数必须调用 D 或者 E 中该函数的实现,并提供自己的实现。

4.7 抽象类

一个类和它的一些成员可以被声明为 abstract。抽象成员在其类中没有实现。注意:我们不需要用 open 修饰抽象类或者函数。

    open class Birds {
        open fun fly() {
            //TODO
        }
    }

    abstract class Glede : Birds() {
        abstract override fun fly()//抽象成员在其类中没有实现
    }

可以用抽象成员覆盖非抽象的 open 成员。

源码地址:https://github.com/FollowExcellence/KotlinDemo-master

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才

我是suming,感谢各位的支持和认可,您的点赞、评论、收藏【一键三连】就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !

要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值