Kotlin——从无到有系列之中级篇(四):面向对象的特征与类(class)继承详解

如果您对Kotlin很有兴趣,或者很想学好这门语言,可以关注我的掘金,或者进入我的QQ群大家一起学习、进步。

欢迎各位大佬进群共同研究、探索

QQ群号:497071402

进入正题

在前面的章节中,详细的详解了的使用,但是由于篇幅的限制,关于类的很多特性都没有讲解到。今天在这篇文章中,详细的讲解Kotlin中类的特性。如果您对Kotlin中的类还没有一个整体的了解的话,请参见我上一篇文章Kotlin——类(class)详解

众所周知,Kotlin是一门面向对象的开发语言。那么他也有面向对象语言的特性。而面向对象的三大特性即封装继承多态。这是每一门面向对象语言否具有的特性。今天这一节会着重的讲解Kotlin的继承Java的不同处和Kotlin独有的特点。

目录

一、面向对象的特征

面向对象的三大特征:封装继承多态

由于面向对象的三大特征太过于普通,而且这并不是Kotlin中特有的知识。在这里就不多做描述。

二、Kotlin继承类

Kotlin中,继承这个特性除了定义关键字,以及所有的父类和Java语言不通之外,其他的其实无太大的差别。不过既然写到了这里,还是从始至终的写完这个特性,如果您有Java的基础,您可以当复习一遍。

2.1、超类(Any)

Kotlin中,说有的类都是继承与Any类,这是这个没有父类型的类。即当我们定义各类时,它默认是继承与Any这个超类的

例:

class Demo    // 这里定义了一个Demo类,即这个类默认是继承与超类的。

因为Any这个类只是给我们提供了equals()hashCode()toString()这三个方法。我们可以看看Any这个类的源码实现:

package kotlin

/**
 * The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
 * 看这个源码注释:意思是任何一个Kotlin的类都继承与这个[Any]类
 */
public open class Any {

    // 比较: 在平时的使用中经常用到的equals()函数的源码就在这里额
    public open operator fun equals(other: Any?): Boolean

    // hashCode()方法:其作用是返回该对象的哈希值
    public open fun hashCode(): Int

    // toString()方法
    public open fun toString(): String
}

从源码可以我们看出,它直接属于kotlin这个包下。并且只定义了上面所示的三个方法。或许你具有Java的编程经验。在我们熟知的Java中,所有的类默认都是继承与Object类型的。而Object这个类除了比Any多了几个方法与属性外,没有太大的区别。不过他们并不是同一个类。这里就不多种讲解了....

从上面源码中所产生的疑惑:类与函数前面都加上了open这个修饰符。那么这个修饰符的作用是什么呢?

其实我们分析可以得出:既然Any类是所有类的父类,那么我们自己要定义一个继承类,跟着Any类的语法与结构就能定义一个继承类。故而,open修饰符是我们定义继承类的修饰符

2.2、定义

2.2.1、继承类的基础使用

  • 定义继承类的关键字为:open。不管是类、还是成员都需要使用open关键字。
  • 定义格式为:

    open class 类名{

    ...

    open var/val 属性名 = 属性值

    ...

    open fun 函数名()

    ...

    }

例:这里定义一个继承类Demo,并实现两个属性与方法,并且定义一个DemoTest去继承自Demo

open class Demo{

    open var num = 3

    open fun foo() = "foo"

    open fun bar() = "bar"

}

class DemoTest : Demo(){
    // 这里值得注意的是:Kotlin使用继承是使用`:`符号,而Java是使用extends关键字
}

fun main(args: Array<String>) {

    println(DemoTest().num)
    DemoTest().foo()
    DemoTest().bar()

}

输出结果为:

3
foo
bar

分析:从上面的代码可以看出,DemoTest类只是继承了Demo类,并没有实现任何的代码结构。一样可以使用Demo类中的属性与函数。这就是继承的好处。

2.2.2、继承类的构造函数

在上一篇文章中,讲解到了Kotlin类,可以有一个主构造函数,或者多个辅助函数。或者没有构造函数的情况。如果您对Kotlin的构造函数还不了解的情况,请阅读我的上一篇文章Kotlin——类详解

这里当实现类无主构造函数,和存在主构造函数的情况。

  • 无主构造函数

    当实现类无主构造函数时,则每个辅助构造函数必须使用super关键字初始化基类型,或者委托给另一个构造函数。 请注意,在这种情况下,不同的辅助构造函数可以调用基类型的不同构造函数

例:这里举例在Android中常见的自定义View实现,我们熟知,当我们指定一个组件是,一般实现继承类(基类型)的三个构造函数。

class MyView : View(){

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
        : super(context, attrs, defStyleAttr)
}

可以看出,当实现类无主构造函数时,分别使用了super()去实现了基类的三个构造函数。

  • 存在主构造函数

    当存在主构造函数时,主构造函数一般实现基类型中参数最多的构造函数,参数少的构造函数则用this关键字引用即可了。这点在Kotlin——类详解这篇文章是讲解到的。

例:同样以自定义组件为例子

class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
    : View(context, attrs, defStyleAttr) {

    constructor(context: Context?) : this(context,null,0)

    constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}

2.3、函数的重载与重写

Kotlin中关于函数的重载重写,和Java中是几乎是一样的,但是这里还是举例来说明一下。

2.3.1、重写函数中的两点特殊用法

不管是Java还是Kotlin,重写基类型里面的方法,则称为重写,或者是覆盖基类型方法。不过这里介绍两点Kotlin一点特殊的地方

  1. 当基类中的函数,没有用open修饰符修饰的时候,实现类中出现的函数的函数名不能与基类中没有用open修饰符修饰的函数的函数名相同,不管实现类中的该函数有无override修饰符修饰。读着有点绕,直接看例子你就明白了。

例:

open class Demo{
    fun test(){}   // 注意,这个函数没有用open修饰符修饰
}

class DemoTest : Demo(){

    // 这里声明一个和基类型无open修饰符修饰的函数,且函数名一致的函数
    // fun test(){}   编辑器直接报红,根本无法运行程序
    // override fun test(){}   同样报红
}
  1. 当一个类不是用open修饰符修饰时,这个类默认是final的。即:
class A{}

等价于

final class A{}   // 注意,则的`final`修饰符在编辑器中是灰色的,因为Kotlin中默认的类默认是final的

那么当一个基类去继承另外一个基类时,第二个基类不想去覆盖掉第一个基类的方法时,第二个基类的该方法使用final修饰符修饰。

例:

open class A{
    open fun foo(){}
}

// B这个类继承类A,并且类B同样使用open修饰符修饰了的
open class B : Demo(){

    // 这里使用final修饰符修饰该方法,禁止覆盖掉类A的foo()函数
    final override fun foo(){}
}

2.3.2、方法重载

在文章的开头提到了多态这个特性,方法的重载其实主要体现在这个地方。即函数名相同,函数的参数不同的情况。这一点和Java是相同的

这一点在继承类中同样有效:

例:

open class Demo{
    open fun foo() = "foo"
}

class DemoTest : Demo(){

    fun foo(str: String) : String{
        return str
    }

    override fun foo(): String {
        return super.foo()
    }
}    

fun main(args: Array<String>) {
    println(DemoTest().foo())
    DemoTest().foo("foo的重载函数")
}

输出结果为:

foo
foo的重载函数

2.4、重写属性

  • 重写属性和重写方法其实大致是相同的,但是属性不能被重载。
  • 重写属性即指:在基类中声明的属性,然后在其基类的实现类中重写该属性,该属性必须以override关键字修饰,并且其属性具有和基类中属性一样的类型。且可以重写该属性的值(Getter

例:

open class Demo{
    open var num = 3
}

class DemoTest : Demo(){
    override var num: Int = 10
}

2.4.1、重写属性中,val与var的区别

这里可以看出重写了num这个属性,并且为这个属性重写了其值为10

但是,还有一点值得我们注意:当基类中属性的变量修饰符为val的使用,其实现类可以用重写属性可以用var去修饰。反之则不能。

例:

open class Demo{
    open val valStr = "我是用val修饰的属性"
}

class DemoTest : Demo(){

    /*
     * 这里用val、或者var重写都是可以的。
     * 不过当用val修饰的时候不能有setter()函数,编辑器直接会报红的
     */

    // override val valStr: String
    //   get() = super.valStr

    // override var valStr: String = ""
    //   get() = super.valStr

    // override val valStr: String = ""

    override var valStr: String = "abc"
        set(value){field = value}
}

fun main(arge: Array<String>>){
    println(DemoTest().valStr)

    val demo = DemoTest()
    demo.valStr = "1212121212"
    println(demo.valStr)
}

输出结果为:

abc
1212121212

2.4.2、Getter()函数慎用super关键字

在这里值得注意的是,在实际的项目中在重写属性的时候不用get() = super.xxx,因为这样的话,不管你是否重新为该属性赋了新值,还是支持setter(),在使用的时候都调用的是基类中的属性值。

例: 继上面中的例子

class DemoTest : Demo(){

    /*
     * 这里介绍重写属性是,getter()函数中使用`super`关键字的情况
     */

    override var valStr: String = "abc"、
        get() = super.valStr
        set(value){field = value}
}

fun main(arge: Array<String>>){
    println(DemoTest().valStr)

    val demo = DemoTest()
    demo.valStr = "1212121212"
    println(demo.valStr)
}

输出结果为:

我是用val修饰的属性
我是用val修饰的属性

切记:重写属性的时候慎用super关键字。不然就是上面例子的效果

2.4.3、在主构造函数中重写

这一点和其实在接口类的文章中讲解过了,不清楚的可以去参见Kotlin——枚举类(Enum)、接口类(Interface)详解

例:基类还是上面的例子

class DemoTest2(override var num: Int, override val valStr: String) : Demo()

fun main(args: Array<String>){
    val demo2 = DemoTest2(1,"构造函数中重写")
    println("num = ${demo2.num} \t valStr = ${demo2.valStr}")
}

输出结果为:

num = 1      valStr = 构造函数中重写

2.5、覆盖规则

这里的覆盖规则,是指实现类继承了一个基类,并且实现了一个接口类,当我的基类中的方法、属性和接口类中的函数重名的情况下,怎样去区分实现类到底实现哪一个中的属性或属性。

这一点和一个类同时实现两个接口类,而两个接口都用同样的属性或者函数的时候是一样的。在接口类这篇文章中已经讲解过,您可以参见Kotlin——枚举类(Enum)、接口类(Interface)详解

例:

open class A{
    open fun test1(){ println("基类A中的函数test1()") }

    open fun test2(){println("基类A中的函数test2()")}
}

interface B{
    fun test1(){ println("接口类B中的函数test1()") }

    fun test2(){println("接口类B中的函数test2()")}
}

class C : A(),B{
    override fun test1() {
        super<A>.test1()
        super<B>.test1()
    }

    override fun test2() {
        super<A>.test2()
        super<B>.test2()
    }
}

总结

对于Kotlin继承类这一个知识点,在项目中用到的地方是很常见的。当你认真的学习完上面的内容,我相信你可以能很轻易的用于项目中,不过对一个类来说,继承的代价较高,当实现一个功能不必用到太多的集成属性的时候,可以用对象表达式这一个高级功能去替代掉继承。

如果你有过其他面向对象语言的编程经验的话,你只要掌握其关键字、属性/函数重写、以及覆盖规则这三个知识点就可以了。

源代码

如果各位大佬看了之后感觉还阔以,就请各位大佬随便star一下,您的关注是我最大的动力。

我的个人博客Jetictors

GithubJteictors

掘金Jteictors

欢迎各位大佬进群共同研究、探索

QQ群号:497071402

原文地址:https://www.cnblogs.com/Jetictors/p/8647968.html

时间: 2024-08-02 09:53:49

Kotlin——从无到有系列之中级篇(四):面向对象的特征与类(class)继承详解的相关文章

Kotlin——最详细的数据类、密封类详解

在前面几个章节章节中,详细的讲解了Koltin中的接口类(Interface).枚举类(Enmu),还不甚了解的可以查看我的上一篇文章Kotlin--接口类.枚举类详解.当然,在Koltin中,除了接口类.枚举类之外,还有抽象类.内部类.数据类以及密封类.在今天的章节中,为大家详细讲解数据类和密封类.在下一章节中,再为大家奉上Kotlin中的抽象类以及内部类的知识.如果还对Kotlin中类的分类还不清楚的可以查看我的另一篇博文Koltin--类(class)详解. 目录 一.数据类 在Java中

第四课-第一讲04_01_Linux用户管理命令详解

第四课-第一讲04_01_Linux用户管理命令详解1.useradd [option] USERNAME-u UID(大于500且没使用过的)-c 用户说明,COMMENT-d 家目录 HOME-g GID 基本组ID-G GID,....附加值ID-s 默认shell,指定要用的shell的路径-m(常和-k一起用) 强制指定家目录-M 不创建用户家目录环境变量:PATHHISTSIZESHELL:保持当前用户的默认shell的路径/etc/shells:指定了当前系统可用的安全shell/

“全栈2019”Java第一百零四章:匿名内部类与外部成员互访详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第一百零四章:匿名内部类与外部成员互访详解 下一章 "全栈2019"Java第一百零五章:匿名内部类覆盖作用域成员详解 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"

python全栈开发【第十六篇】面向对象三大特性——多台和继承补充

一.回顾 面向对象 1.类:具有相同属性和方法 的一类事物 类名可以实例化一个对象 类名可以调用类属性,(静态属性 和(方法)动态属性) 2.对象:也就是实例    对象名:调用对象属性 调用方法 3.什么叫抽象? 从小到大的过程 4.组合-----什么有什么的关系(将一个类的对象当做另一个类的属性) 5.继承-----什么是什么的关系 从大范围到小范围的过程 继承的作用:减少代码的重用性 子类有的方法,就用子类的.不会调用父类的方法. 如果要在子类中调用父类的方法:super().类名() 6

5.2-全栈Java笔记:面向对象的特征(一)继承 | 下

上节我们聊到「Java面向对象的特征:继承」这节我们继续聊一下继承的应用. Object类 Object类基本特性 Object类是所有Java类的根基类,也就意味着所有的JAVA对象都拥有Object类的属性和方法.如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类. [示例1]Object类 public class Person { ... } //等价于: public class Person extends   Object { ... } toStrin

CSS系列(7)CSS类选择器Class详解

这一篇文章,以笔记形式写. 1,  CSS 类选择器详解 http://www.w3school.com.cn/css/css_selector_class.asp 知识点: (1)    使用类选择器的前提是给标签添加上类属性,比如<p class="important"></p> (2)    类选择器的语法为:*.important {color:red;},不过一般省略前面的通配符选择器,写成 .important {color:red;},这样就会给所

php面向对象4部曲(二)继承详解

类的继承 简单理解: 某个类A具有某些特征,另一个类B,也具有A类的所有特征,并且还可能具有自己的更多的一些特征,此时,我们就可以实现:B类使用A的特征信息并继续添加自己的一些特有特征信息. 基本概念 继承:一个类从另一个已有的类获得其特性,称为继承. 派生:从一个已有的类产生一个新的类,称为派生. 继承和派生,其实只是从不同的方向(角度)来表述,本质上就是一个事情. 父类/子类:已有类为父类,新建类为子类.父类也叫“基类”,子类也叫“派生类” 单继承:一个类只能从一个上级类继承其特性信息.PH

JavaScript继承详解(四)

在本章中,我们将分析Douglas Crockford关于JavaScript继承的一个实现 - Classical Inheritance in JavaScript. Crockford是JavaScript开发社区最知名的权威,是JSON.JSLint.JSMin和ADSafe之父,是<JavaScript: The Good Parts>的作者. 现在是Yahoo的资深JavaScript架构师,参与YUI的设计开发. 这里有一篇文章详细介绍了Crockford的生平和著作. 当然Cr

[AngularJS] AngularJS系列(4) 中级篇之指令

目录 API概览 使用Angular.UI.Bootstrap 自定义指令 scope link 我的指令 angular中的指令可谓是最复杂的一块 但是我们的上传组件就能这么写 效果图: API概览 先上一段伪代码: angular.module('moduleName', []).directive( 'namespaceDirectiveName', [ function() { return { restrict : '',// 描述指令在模版中的使用方式,包括元素E,属性A,CSS样式