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,一经查实,立即删除!

相关文章

在mac上安装Docker

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

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

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 …

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

导读&#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 &…

MultiQC使用指导

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

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…

71文件类型

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

七牛大数据平台的演进与大数据分析实践--转

原文地址&#xff1a;http://www.infoq.com/cn/articles/qiniu-big-data-platform-evolution-and-analysis?utm_sourceinfoq&utm_mediumpopular_widget&utm_campaignpopular_content_list&utm_contenthomepage 七牛大数据平台的演进与大数据分析实践 (点击放大图像…

最大化切割段

Description: 描述&#xff1a; In this article we are going to review classic dynamic programing problem which has been featured in interview rounds of amazon. 在本文中&#xff0c;我们将回顾在亚马逊的采访轮次中已经介绍的经典动态编程问题。 Problem statemen…

响应数据传出(springMVC)

1. SpringMVC 输出模型数据概述 提供了以下几种途径输出模型数据&#xff1a; ModelAndView: 处理方法返回值类型为 ModelAndView 时, 方法体即可通过该对象添加模型数据 Map 及 Model: 入参为 org.springframework.ui.Model、 org.springframework.ui.ModelMap 或 java.uti…

微信网页扫码登录的实现

为了让用户登录网站的门槛更低&#xff0c;微信扫一扫登录变得越来越广泛&#xff0c;所以最近加紧赶制的项目中有用到这个功能&#xff0c;此篇文字的出发点基于微信开放平台已经配置好域名&#xff08;80端口&#xff09;并且认证成功获得app_id和secret并有权限调用微信的接…

希尔密码_希尔密码| 网络安全

希尔密码Now, Hill Cipher is a very basic cryptographic technique which is used to convert a string into ciphertext. This technique was invented by an American Mathematician "Lester Sanders Hill". This is a polygraphic substitution cipher because …

Android 那些年,处理getActivity()为null的日子

在日常开发中的时候&#xff0c;我们经常会使用ViewPagerFragment进行视图滑动&#xff0c;在某些部分逻辑也许我们需要利用上下文Context&#xff08;例如基本的Toast&#xff09;&#xff0c;但是由于Fragment只是衣服在Activity容器的一个试图&#xff0c;如果需要拿到当前的…

设计模式状态模式uml_UML的完整形式是什么?

设计模式状态模式umlUML&#xff1a;统一建模语言 (UML: Unified Modeling Language) UML is an abbreviation of Unified Modeling Language. In the field of software engineering, it is a visual modeling language that is standard in quality. It makes it available t…

vqa mcb_MCB的完整形式是什么?

vqa mcbMCB&#xff1a;微型断路器 (MCB: Miniature Circuit Breaker) MCB is an abbreviation of "Miniature Circuit Breaker". MCB是“微型断路器”的缩写 。 It is an automatically operated electronics switch. It is designed to detect the fault in the e…

CentOS忘记普通用户密码解决办法

普通用户忘记密码 1.使用root用户登录系统&#xff0c;找到/etc/shadow文件。 2.找到用户名开头的那一行&#xff0c;例如我的用户名为pds,&#xff0c;以冒号为分割符&#xff0c;红色部分是密码加密部分 pds:$1$CivopRgF$ajWQ54W1XJbifFjm05Jk/1:15353:0:99999:7::: 3.pds是我…

esp32的GPIO操作

对于任何一款芯片&#xff0c;GPIO接口是其最基本的组成部分&#xff0c;也是一款芯片入门的最基本操作&#xff0c;下面论述下 关于esp32开发版的GPIO操作&#xff0c;本文中重点讲解下 关于如何创建eclipse工程&#xff0c;并通过eclipse下载到esp32中去&#xff08;本文的工…

聚焦数据的力量——全球领先安全技术分享会在京召开

ZD至顶网安全频道 04月21日 综合消息&#xff1a; 由中国网络安全与信息化产业联盟、360共同主办的“数据的力量——全球领先安全技术分享会“今日在北京成功召开。来自政府、企业、教育、投资机构和产业联盟的300多位嘉宾参加了本次技术分享会&#xff0c;共同就安全产业发展趋…

如何设置Fedora默认从命令行启动?

2019独角兽企业重金招聘Python工程师标准>>> Sumary:因为在Fedora中没有/etc/initab文件我们不方便从这里设置它的runlevel target&#xff0c;但是Linux又给我们提供了一个强悍的工具systemd,我们可以用system来链接默认的启动级别&#xff0c;所以开始吧&#xff…