Kotlin入门(14)继承的那些事儿

上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用。不过早在《Kotlin入门(12)类的概貌与构造》中,提到MainActivity继承自AppCompatActivity,而Kotlin对于类继承的写法是“class MainActivity : AppCompatActivity() {}”,这跟Java对比有明显差异,那么Kotlin究竟是如何定义基类并由基类派生出子类呢?为廓清这些迷雾,本篇文章就对类继承的相关用法进行深入探讨。

 

博文《Kotlin入门(13)类成员的众生相》在演示类成员时多次重写了WildAnimal类,这下你兴冲冲地准备按照MainActivity的继承方式,从WildAnimal派生出一个子类Tiger,写好构造函数的两个输入参数,补上基类的完整声明,敲了以下代码不禁窃喜这么快就大功告成了:

class Tiger(name:String="老虎", sex:Int = 0) : WildAnimal(name, sex) {
}

谁料编译器无情地蹦出错误提示“The type is final, so it cannot be inherited from”,意思是WildAnimal类是final类型,所以它不允许被继承。原来Java默认每个类都能被继承,除非加了关键字final表示终态,才不能被其它类继承。Kotlin恰恰相反,它默认每个类都不能被继承(相当于Java类被final修饰了),如果要让某个类成为基类,则需把该类开放出来,也就是添加关键字open作为修饰。因此,接下来还是按照Kotlin的规矩办事,重新写个采取open修饰的基类,下面即以鸟类Bird进行演示,改写后的基类代码框架如下:

open class Bird (var name:String, val sex:Int = 0) {//此处暂时省略基类内部的成员属性和方法
}

现在有了基类框架,还得往里面补充成员属性和成员方法,然后给这些成员添加开放性修饰符。就像大家在Java和C++世界中熟知的几个关键字,包括public、protected、private,分别表示公开、只对子类开放、私有。那么Kotlin体系参照Java世界也给出了四个开放性修饰符,按开放程度从高到低分别是:
public : 对所有人开放。Kotlin的类、函数、变量不加开放性修饰符的话,默认就是public类型。
internal : 只对本模块内部开放,这是Kotlin新增的关键字。对于App开发来说,本模块便是指App自身。
protected : 只对自己和子类开放。
private : 只对自己开放,即私有。
注意到这几个修饰符与open一样都加在类和函数前面,并且都包含“开放”的意思,乍看过去还真有点扑朔迷离,到底open跟四个开放性修饰符是什么关系?其实也不复杂,open不控制某个对象的访问权限,只决定该对象能否繁衍开来,说白了,就是公告这个家伙有没有资格生儿育女。只有头戴open帽子的类,才允许作为基类派生出子类来;而头戴open帽子的函数,表示它允许在子类中进行重写。
至于那四个开放性修饰符,则是用来限定允许访问某对象的外部范围,通俗地说,就是哪里的男人可以娶这个美女。头戴public的,表示全世界的男人都能娶她;头戴internal的,表示本国的男人可以娶她;头戴protected的,表示本单位以及下属单位的男人可以娶她;头戴private的,表示肥水不流外人田,只有本单位的帅哥才能娶这个美女噢。
因为private的限制太严厉了,只对自己开放,甚至都不允许子类染指,所以它跟关键字open势同水火。open表示这个对象可以被继承,或者可以被重载,然而private却坚决斩断该对象与其子类的任何关系,因此二者不能并存。倘若在代码中强行给某个方法同时加上open和private,编译器只能无奈地报错“Modifier 'open' is incompatible with 'private'”,意思是open与private不兼容。
按照以上的开放性相关说明,接下来分别给Bird类的类名、函数名、变量名加上修饰符,改写之后的基类代码是下面这样:

//Kotlin的类默认是不能继承的(即final类型),如果需要继承某类,则该父类应当声明为open类型。
//否则编译器会报错“The type is final, so it cannot be inherited from”。
open class Bird (var name:String, val sex:Int = MALE) {//变量、方法、类默认都是public,所以一般都把public省略掉了//public var sexName:Stringvar sexName:Stringinit {sexName = getSexName(sex)}//私有的方法既不能被外部访问,也不能被子类继承,因此open与private不能共存//否则编译器会报错:Modifier 'open' is incompatible with 'private'//open private fun getSexName(sex:Int):String {open protected fun getSexName(sex:Int):String {return if(sex==MALE) "公" else "母"}fun getDesc(tag:String):String {return "欢迎来到$tag:这只${name}是${sexName}的。"}companion object BirdStatic{val MALE = 0val FEMALE = 1val UNKNOWN = -1fun judgeSex(sexName:String):Int {var sex:Int = when (sexName) {"公","雄" -> MALE"母","雌" -> FEMALEelse -> UNKNOWN}return sex}}
}

好不容易鼓捣出来一个正儿八经的鸟儿基类,再来声明一个它的子类试试,例如鸭子是鸟类的一种,于是下面有了鸭子的类定义代码:

//注意父类Bird已经在构造函数声明了属性,故而子类Duck无需重复声明属性
//也就是说,子类的构造函数,在输入参数前面不要再加val和var
class Duck(name:String="鸭子", sex:Int = Bird.MALE) : Bird(name, sex) {
}

子类也可以定义新的成员属性和成员方法,或者重写被声明为open的父类方法。比方说性别名称“公”和“母”一般用于家禽,像公鸡、母鸡、公鸭、母鸭等等,指代野生鸟类的性别则通常使用“雄”和“雌”,所以定义野生鸟类的时候,就得重写获取性别名称的getSexName方法,把“公”和“母”的返回值改为“雄”和“雌”。方法重写之后,定义了鸵鸟的类代码如下所示:

class Ostrich(name:String="鸵鸟", sex:Int = Bird.MALE) : Bird(name, sex) {//继承protected的方法,标准写法是“override protected”//override protected fun getSexName(sex:Int):String {//不过protected的方法继承过来默认就是protected,所以也可直接省略protected//override fun getSexName(sex:Int):String {//protected的方法继承之后允许将可见性升级为public,但不能降级为privateoverride public fun getSexName(sex:Int):String {return if(sex==MALE) "雄" else "雌"}
}

  

除了上面讲的普通类继承,Kotlin也存在与Java类似的抽象类,抽象类之所以存在,是因为其内部拥有被abstract修饰的抽象方法。抽象方法没有具体的函数体,故而外部无法直接声明抽象类的实例;只有在子类继承之时重写抽象方法,该子类方可正常声明对象实例。举个例子,鸡属于鸟类,可公鸡和母鸡的叫声是不一样的,公鸡是“喔喔喔”地叫,而母鸡是“咯咯咯”地叫;所以鸡这个类的叫唤方法callOut,发出什么声音并不确定,只能先声明为抽象方法,连带着鸡类Chicken也变成抽象类了。根据上述的抽象类方案,定义好的Chicken类代码示例如下:

//子类的构造函数,原来的输入参数不用加var和val,新增的输入参数必须加var或者val。
//因为抽象类不能直接使用,所以构造函数不必给默认参数赋值。
abstract class Chicken(name:String, sex:Int, var voice:String) : Bird(name, sex) {val numberArray:Array<String> = arrayOf("一","二","三","四","五","六","七","八","九","十");//抽象方法必须在子类进行重写,所以可以省略关键字open,因为abstract方法默认就是open类型//open abstract fun callOut(times:Int):Stringabstract fun callOut(times:Int):String
}

接着从Chicken类派生出公鸡类Cock,指定公鸡的声音为“喔喔喔”,同时还要重写callOut方法,明确公鸡的叫唤行为。具体的Cock类代码如下所示:

class Cock(name:String="鸡", sex:Int = Bird.MALE, voice:String="喔喔喔") : Chicken(name, sex, voice) {override fun callOut(times: Int): String {var count = when {//when语句判断大于和小于时,要把完整的判断条件写到每个分支中times<=0 -> 0times>=10 -> 9else -> times}return "$sexName$name${voice}叫了${numberArray[count]}声,原来它在报晓呀。"}
}

同样派生而来的母鸡类Hen,也需指定母鸡的声音“咯咯咯”,并重写callOut叫唤方法,具体的Hen类代码如下所示:

class Hen(name:String="鸡", sex:Int = Bird.FEMALE, voice:String="咯咯咯") : Chicken(name, sex, voice) {override fun callOut(times: Int): String {var count = when {times<=0 -> 0times>=10 -> 9else -> times}return "$sexName$name${voice}叫了${numberArray[count]}声,原来它下蛋了呀。"}
}

定义好了callOut方法,外部即可调用Cock类和Hen类的该方法了,调用代码示例如下:

    //调用公鸡类的叫唤方法tv_class_inherit.text = Cock().callOut(count++%10)//调用母鸡类的叫唤方法tv_class_inherit.text = Hen().callOut(count++%10)

  

既然提到了抽象类,就不得不提接口interface。Kotlin的接口与Java一样是为了间接实现多重继承,由于直接继承多个类可能存在方法冲突等问题,因此Kotlin在编译阶段就不允许某个类同时继承多个基类,否则会报错“Only one class may appear in a supertype list”。于是乎,通过接口定义几个抽象方法,然后在实现该接口的具体类中重写这几个方法,从而间接实现C++多重继承的功能。
在Kotlin中定义接口需要注意以下几点:
1、接口不能定义构造函数,否则编译器会报错“An interface may not have a constructor”;
2、接口的内部方法通常要被实现它的类进行重写,所以这些方法默认为抽象类型;
3、与Java不同的是,Kotlin允许在接口内部实现某个方法,而Java接口的所有内部方法都必须是抽象方法;
Android开发最常见的接口是控件的点击监听器View.OnClickListener,其内部定义了控件的点击动作onClick,类似的还有长按监听器View.OnLongClickListener、选择监听器CompoundButton.OnCheckedChangeListener等等,它们无一例外都定义了某种行为的事件处理过程。对于本文的鸟类例子而言,也可通过一个接口定义鸟儿的常见动作行为,譬如鸟儿除了叫唤动作,还有飞翔、游泳、奔跑等等动作,有的鸟类擅长飞翔(如大雁、老鹰),有的鸟类擅长游泳(如鸳鸯、鸬鹚),有的鸟类擅长奔跑(如鸵鸟、鸸鹋)。因此针对鸟类的飞翔、游泳、奔跑等动作,即可声明Behavior接口,在该接口中定义几个行为方法如fly、swim、run,下面是一个定义好的行为接口代码例子:

//Kotlin与Java一样不允许多重继承,即不能同时继承两个类(及以上类),
//否则编译器报错“Only one class may appear in a supertype list”,
//所以仍然需要接口interface来间接实现多重继承的功能。
//接口不能带构造函数(那样就变成一个类了),否则编译器报错“An interface may not have a constructor”
//interface Behavior(val action:String) {
interface Behavior {//接口内部的方法默认就是抽象的,所以不加abstract也可以,当然open也可以不加open abstract fun fly():String//比如下面这个swim方法就没加关键字abstract,也无需在此处实现方法fun swim():String//Kotlin的接口与Java的区别在于,Kotlin接口内部允许实现方法,//此时该方法不是抽象方法,就不能加上abstract,//不过该方法依然是open类型,接口内部的所有方法都默认是open类型fun run():String {return "大多数鸟儿跑得并不像样,只有鸵鸟、鸸鹋等少数鸟类才擅长奔跑。"}//Kotlin的接口允许声明抽象属性,实现该接口的类必须重载该属性,//与接口内部方法一样,抽象属性前面的open和abstract也可省略掉//open abstract var skilledSports:Stringvar skilledSports:String
}

那么其他类实现Behavior接口时,跟类继承一样把接口名称放在冒号后面,也就是说,Java的extends和implement这两个关键字在Kotlin中都被冒号取代了。然后就像重写抽象类的抽象方法一样,重写该接口的抽象方法,以鹅的Goose类为例,重写接口方法之后的代码如下所示:

class Goose(name:String="鹅", sex:Int = Bird.MALE) : Bird(name, sex), Behavior {override fun fly():String {return "鹅能飞一点点,但飞不高,也飞不远。"}override fun swim():String {return "鹅,鹅,鹅,曲项向天歌。白毛浮绿水,红掌拨清波。"}//因为接口已经实现了run方法,所以此处可以不用实现该方法,当然你要实现它也行。override fun run():String {//super用来调用父类的属性或方法,由于Kotlin的接口允许实现方法,因此super所指的对象也可以是interfacereturn super.run()}//重载了来自接口的抽象属性override var skilledSports:String = "游泳"
}

这下大功告成,Goose类声明的群鹅不但具备鸟类的基本功能,而且能飞、能游、能跑,活脱脱一只栩栩如生的大白鹅呀:

    btn_interface_behavior.setOnClickListener {tv_class_inherit.text = when (count++%3) {0 -> Goose().fly()1 -> Goose().swim()else -> Goose().run()}}

  

总结一下,Kotlin的类继承与Java相比有所不同,首先Kotlin的类默认不可被继承,如需继承则要添加open声明;而Java的类默认是允许被继承的,只有添加final声明才表示不能被继承。其次,Kotlin除了常规的三个开放性修饰符public、protected、private,另外增加了修饰符internal表示只对本模块开放。再次,Java的类继承关键字extends,以及接口实现关键字implement,在Kotlin中都被冒号所取代。最后,Kotlin允许在接口内部实现某个方法,而Java接口的内部方法只能是抽象方法。

__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

 

转载于:https://www.cnblogs.com/aqi00/p/7380070.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/540737.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在Scala中列出| 关于Scala列表的完整教程

Scala | 清单 (Scala | List) List in Scala is a collection that stores data in the form of a liked-list. The list is an immutable collection which means the elements of a list cannot be altered after the list is created. Lists are flexible with data types, …

MySQL单机多实例部署详解之------多实例分别定义不同的配置文件

mysql多实例安装---分别定义不同的配置文件1.安装MySQL需要的依赖包[rootMySQL ~]# yum install ncurses-devel libaio-devel cmake -y[rootMySQL ~]# rpm -qa ncurses-devel libaio-develncurses-devel-5.7-4.20090207.el6.x86_64libaio-devel-0.3.107-10.el6.x86_642.安装编译…

javascript模块_JavaScript中的模块

javascript模块JavaScript模块 (JavaScript Modules) One of the key features of programming fundamentals is breaking down your code into fragments. These fragments depending on its functionality have been coined various terms like functions, components, modul…

在mac上安装Docker

1.进入一下地址进行下载docker https://download.docker.com/mac/stable/Docker.dmg 进入后进行下载后进行安装 2.将其拖动到Appliaction中即可 3.第一打开会有一个这样的欢迎页面 3.检查是否安装完成 出现上图所示标示安装完成了

composer 检查镜像_检查N元树中的镜像

composer 检查镜像Problem statement: 问题陈述&#xff1a; Given two n-ary trees, the task is to check if they are mirrors of each other or not. 给定两个n元树&#xff0c;任务是检查它们是否互为镜像。 Note: you may assume that root of both the given tree as …

浪潮各机型前面板指示灯含义

NF560D2 NF3020M2 NF5020M3 NF5140M3 NF5212H2 NF5220 NF5224L2 NF5240M3 NF5270M3 NF5280M2 NF5280M3 NF5540M3 NF5580M3 NF8420M3 NF8520 NF8560M2 说明&#xff1a;转浪潮官网。

python dll 混合_Python | 条线混合图

python dll 混合In some of the cases, we need to plot a bar-line hybrid plot. This plot helps in a better understanding of dynamics as well as the relative magnitude of each point in the plot. Bar-Line Hybrid Plots are mostly used for the representation of …

测试八 赛后感受

测试八 当我打开T1的时候&#xff0c;就没有往下看题目了&#xff0c;主要是发现T1就是之前做过&#xff0c;而且我也看过题解的题目&#xff0c;接着就开始钻研&#xff0c;当然&#xff0c;也没什么好钻研的&#xff0c;大概思路还是知道的&#xff0c;再写写数据就已经很清晰…

推荐五个免费的网络安全工具

导读&#xff1a; 在一个完美的世界里&#xff0c;信息安全从业人员有无限的安全预算去做排除故障和修复安全漏洞的工作。但是&#xff0c;正如你将要学到的那样&#xff0c;你不需要无限的预算取得到高质量的产品。这里有SearchSecurity.com网站专家Michael Cobb推荐的五个免费…

bios部署模式审核模式_BIOS的完整形式是什么?

bios部署模式审核模式BIOS&#xff1a;基本输入输出系统 (BIOS: Basic Input Output System) BIOS is an abbreviation of the Basic Input Output System. In the beginning, when you first set on your computer, the first software which starts run by the computer is &…

day04-装饰器

一、装饰器定义 1&#xff09;装饰器&#xff1a;本质是函数。 2&#xff09;功能&#xff1a;用来装饰其他函数&#xff0c;顾名思义就是&#xff0c;为其他的函数添加附件功能的。 二、原则 1&#xff09;不能修改被装饰函数的源代码 2&#xff09;不能修改被装饰函数的调用方…

c 语言bool 类型数据_C ++中的bool数据类型

c 语言bool 类型数据In C programming language, to deal with the Boolean values – C added the feature of the bool data type. A bool variable stores either true (1) or false (0) values. 在C 编程语言中&#xff0c;为了处理布尔值– C 添加了bool数据类型的功能 。…

C ++中的std :: binary_search()

binary_search()作为STL函数 (binary_search() as a STL function) Syntax: 句法&#xff1a; bool binary_search (ForwardIterator first, ForwardIterator last, const T& value);Where, 哪里&#xff0c; ForwardIterator first iterator to start of the range For…

HNUSTOJ-1437 无题

1437: 无题 时间限制: 1 Sec 内存限制: 128 MB提交: 268 解决: 45[提交][状态][讨论版]题目描述 tc在玩一个很无聊的游戏&#xff1a;每一次电脑都会给一个长度不超过10^5的字符串&#xff0c;tc每次都从第一个字符开始&#xff0c;如果找到两个相邻相一样的字符&#xff0c;…

凯撒密码pythin密码_凯撒密码术

凯撒密码pythin密码Caesar cipher is one of the well-known techniques used for encrypting the data. Although not widely used due to its simplicity and being more prone to be cracked by any outsider, still this cipher holds much value as it is amongst the fir…

MultiQC使用指导

MultiQC使用指导 官网资料文献&#xff1a;MultiQC --- summarize analysis results for multiple tools and samples in a single report参考资料一&#xff1a; 整合 fastq 质控结果的工具 简介 MultiQC 是一个基于Python的模块, 用于整合其它软件的报告结果, 目前支持以下软…

FYFG的完整形式是什么?

FYFG&#xff1a;对您的未来指导 (FYFG: For Your Future Guidance) FYFG is an abbreviation of "For Your Future Guidance". FYFG是“ For your Future Guidance”的缩写 。 It is an expression, which is commonly used in the Gmail platform. It is also wr…

WorkerMan 入门学习之(二)基础教程-Connection类的使用

一、TcpConnection类 的使用 1、简单的TCP测试 Server.php <?php require_once __DIR__./Workerman/Autoloader.php; use Workerman\Worker; $worker new Worker(websocket://0.0.0.0:80);// 连接回调 $worker->onConnect function ($connection){echo "connecti…

kotlin获取属性_Kotlin程序获取系统名称

kotlin获取属性The task is to get the system name. 任务是获取系统名称。 package com.includehelpimport java.net.InetAddress/*** Function for System Name*/fun getSystemName(): String? {return try {InetAddress.getLocalHost().hostName} catch (E: Exception) {S…

71文件类型

1.kit类型 标准的SeaJs模块文件类型&#xff0c;直接对外暴露方法。 2.units类型 依赖pageJob&#xff0c;对外暴露一个名字&#xff0c;pageJob依赖暴露的名字对模块进行初始化&#xff0c;在pageJob内部逻辑自动执行init方法&#xff1b; 由于没有对外暴露方法&#xff0c;只…