Kotlin入门(15)独门秘笈之特殊类
上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类。除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同的使用场景,极大地方便了开发者的编码工作,下面就来看看Kotlin究竟提供了哪些独门秘笈。
嵌套类
一个类可以在单独的代码文件中定义,也可以在另一个类内部定义,后一种情况叫做嵌套类,意即A类嵌套在B类之中。乍看过去,这个嵌套类的定义似乎与Java的嵌套类是一样的,但其实有所差别。Java的嵌套类允许访问外部类的成员,而Kotlin的嵌套类不允许访问外部类的成员。倘若Kotlin的嵌套类内部强行访问外部类的成员,则编译器会报错“Unresolved reference: ***”,意思是找不到这个东西。下面是Kotlin定义嵌套类的代码例子:
class Tree(var treeName:String) {
//在类内部再定义一个类,这个新类称作嵌套类
class Flower (var flowerName:String) {
fun getName():String {
return "这是一朵$flowerName"
//普通的嵌套类不能访问外部类的成员如treeName
//否则编译器报错“Unresolved reference: ***”
//return "这是${treeName}上的一朵$flowerName"
}
}
}
调用嵌套类时,得在嵌套类的类名前面添加外部类的类名,相当于把这个嵌套类作为外部类的静态对象使用。嵌套类的调用代码如下所示:
btn_class_nest.setOnClickListener {
//使用嵌套类时,只能引用外部类的类名,不能调用外部类的构造函数
val peachBlossom = Tree.Flower("桃花");
tv_class_secret.text = peachBlossom.getName()
}
内部类
既然Kotlin限制了嵌套类不能访问外部类的成员,那还有什么办法可以实现此功能呢?针对该问题,Kotlin另外增加了关键字inner表示内部,把inner加在嵌套类的class前面,于是嵌套类华丽丽转变为了内部类,这个内部类比起嵌套类的好处,便是能够访问外部类的成员。所以,Kotlin的内部类就相当于Java的嵌套类,而Kotlin的嵌套类则是加了访问限制的内部类。按照前面演示嵌套类的树木类Tree,也给它补充内部类的定义,代码如下所示:
class Tree(var treeName:String) {
//在类内部再定义一个类,这个新类称作嵌套类
class Flower (var flowerName:String) {
fun getName():String {
return "这是一朵$flowerName"
//普通的嵌套类不能访问外部类的成员如treeName
//否则编译器报错“Unresolved reference: ***”
//return "这是${treeName}上的一朵$flowerName"
}
} //嵌套类加上了inner前缀,就成为了内部类
inner class Fruit (var fruitName:String) {
fun getName():String {
//只有声明为内部类(添加了关键字inner),才能访问外部类的成员
return "这是${treeName}长出来的$fruitName"
}
}
}
调用内部类时,要先实例化外部类,再通过外部类的实例调用内部类的构造函数,也就是把内部类作为外部类的一个成员对象来使用,这与成员属性、成员方法的调用方法类似。内部类的调用代码如下所示:
btn_class_inner.setOnClickListener {
//使用内部类时,必须调用外部类的构造函数,否则编译器会报错
val peach = Tree("桃树").Fruit("桃花");
tv_class_secret.text = peach.getName()
}
枚举类
Java有一种枚举类型,它采用关键字enum来表达,其内部定义了一系列名称,通过有意义的名字比0/1/2这些数字能更有效地表达语义。下面是个Java定义枚举类型的代码例子:
enum Season { SPRING,SUMMER,AUTUMN,WINTER }
上面的枚举类型定义代码,看起来仿佛是一种新的数据类型,特别像枚举数组。可是枚举类型实际上是一种类,开发者在代码中创建enum类型时,编译器会自动生成一个对应的类,并且该类继承自java.lang.Enum。因此,Kotlin拨乱反正,摒弃了“枚举类型”那种模糊不清的说法,转而采取“枚举类”这种正本清源的提法。具体到编码上,则将enum作为关键字class都得修饰符,使之名正言顺地成为一个类——枚举类。按此思路将前面Java的枚举类型Season改写为Kotlin的枚举类,改写后的枚举类代码如下所示:
enum class SeasonType {
SPRING,SUMMER,AUTUMN,WINTER
}
枚举类内部的枚举变量,除了可以直接拿来赋值之外,还可以调用枚举值的几个属性获得对应的信息,例如ordinal属性用于获取该枚举值的序号,name属性用于获取该枚举值的名称。枚举变量本质上还是该类的一个实例,所以如果枚举类存在构造函数的话,枚举变量也必须调用对应的构造函数。这样做的好处是,每个枚举值不但携带唯一的名称,还可以拥有更加个性化的特征描述。比如下面的枚举类SeasonName代码,通过构造函数能够给枚举值赋予更加丰富的含义:
enum class SeasonName (val seasonName:String) {
SPRING("春天"),
SUMMER("夏天"),
AUTUMN("秋天"),
WINTER("冬天")
}
下面的代码演示了如何分别使用两个枚举类SeasonType和SeasonName:
btn_class_enum.setOnClickListener {
if (count%2 == 0) {
//ordinal表示枚举类型的序号,name表示枚举类型的名称
tv_class_secret.text = when (count++%4) {
SeasonType.SPRING.ordinal -> SeasonType.SPRING.name
SeasonType.SUMMER.ordinal -> SeasonType.SUMMER.name
SeasonType.AUTUMN.ordinal -> SeasonType.AUTUMN.name
SeasonType.WINTER.ordinal -> SeasonType.WINTER.name
else -> "未知"
}
} else {
tv_class_secret.text = when (count++%4) {
//使用自定义属性seasonName表示更个性化的描述
SeasonName.SPRING.ordinal -> SeasonName.SPRING.seasonName
SeasonName.SUMMER.ordinal -> SeasonName.SUMMER.seasonName
SeasonName.AUTUMN.ordinal -> SeasonName.AUTUMN.seasonName
SeasonName.WINTER.ordinal -> SeasonName.WINTER.seasonName
else -> "未知"
//枚举类的构造函数是给枚举类型使用的,外部不能直接调用枚举类的构造函数
//else -> SeasonName("未知").name
}
}
}
密封类
前面演示外部代码判断枚举值的时候,when语句末尾例行公事加了else分支。可是枚举类SeasonType内部一共只有四个枚举变量,when语句有四个分支就行了,最后的else分支纯粹是多此一举。出现此种情况的缘故是,when语句不晓得SeasonType只有四种枚举值,因此以防万一必须要有else分支,除非编译器认为现有的几个分支已经足够。
为解决枚举值判断的多余分支问题,Kotlin提出了“密封类”的概念,密封类就像是一种更加严格的枚举类,它内部有且仅有自身的实例对象,所以是一个有限的自身实例集合。或者说,密封类采用了嵌套类的手段,它的嵌套类全部由自身派生而来,仿佛一个家谱明明白白列出来某人有长子、次子、三子、幺子。定义密封类时使用关键字sealed标记,具体的密封类定义代码如下所示:
sealed class SeasonSealed {
//密封类内部的每个嵌套类都必须继承该类
class Spring (var name:String) : SeasonSealed()
class Summer (var name:String) : SeasonSealed()
class Autumn (var name:String) : SeasonSealed()
class Winter (var name:String) : SeasonSealed()
}
有了密封类,通过when语句便无需指定else分支了,下面是判断密封类对象的代码例子:
btn_class_sealed.setOnClickListener {
var season = when (count++%4) {
0 -> SeasonSealed.Spring("春天")
1 -> SeasonSealed.Summer("夏天")
2 -> SeasonSealed.Autumn("秋天")
else -> SeasonSealed.Winter("冬天")
}
//密封类是一种严格的枚举类,它的值是一个有限的集合。
//密封类确保条件分支覆盖了所有的枚举类型,因此不再需要else分支。
tv_class_secret.text = when (season) {
is SeasonSealed.Spring -> season.name
is SeasonSealed.Summer -> season.name
is SeasonSealed.Autumn -> season.name
is SeasonSealed.Winter -> season.name
}
}
数据类
在Android开发中,免不了经常定义一些存放数据的实体类,比如用户信息、商品信息等等,每逢定义实体类之时,开发者基本要手工完成以下编码工作:
1、定义实体类的每个字段,以及对字段进行初始赋值的构造函数;
2、定义每个字段的get/set方法;
3、在判断两个数据对象是否相等时,通常要每个字段都比较一遍;
4、在复制数据对象时,如果想修改某几个字段的值,得再加对应数量的赋值语句;
5、在调试程序时,为了解数据对象里保存的字段值,得手工把每个字段值都打印出来;
如此折腾一番,仅仅是定义一个实体类,开发者就必须完成这些繁琐的任务。然而这些任务其实毫无技术含量可言,如果每天都在周而复始地敲实体类的相关编码,毫无疑问跟工地上的搬砖民工差不多,活生生把程序员弄成一个拼时间拼体力的职业。有鉴于此,Kotlin再次不负众望推出了名为“数据类”的大兵器,直接戳中程序员事多、腰酸、睡眠少的痛点,极大程度上将程序员从无涯苦海中拯救出来。
数据类说神秘也不神秘,它的类定义代码极其简单,只要开发者在class前面增加关键字“data”,并声明入参完整的构造函数,即可无缝实现以下功能:
1、自动声明与构造入参同名的属性字段;
2、自动实现每个属性字段的get/set方法;
3、自动提供equals方法,用于比较两个数据对象是否相等;
4、自动提供copy方法,允许完整复制某个数据对象,也可在复制后单独修改某几个字段的值;
5、自动提供toString方法,用于打印数据对象中保存的所有字段值;
功能如此强大的数据类,犹如步枪界的AK47,持有该款自动步枪的战士无疑战斗力倍增。见识了数据类的深厚功力,再来看看它的类代码是怎么定义的:
//数据类必须有主构造函数,且至少有一个输入参数,
//并且要声明与输入参数同名的属性,即输入参数前面添加关键字val或者var,
//数据类不能是基类也不能是子类,不能是抽象类,也不能是内部类,更不能是密封类。
data class Plant(var name:String, var stem:String, var leaf:String, var flower:String, var fruit:String, var seed:String) {
}
想不到吧,原来数据类的实现代码竟然如此简单,当真是此时无招胜有招。当然,为了达到这个代码精简的效果,数据类也得遵循几个规则,或者说是约束条件,毕竟不以规矩不成方圆,正如类定义代码所注释的那样:
1、数据类必须有主构造函数,且至少有一个输入参数,因为它的属性字段要跟输入参数一一对应,如果没有属性字段,这个数据类保存不了数据也就失去存在的意义了;
2、主构造函数的输入参数前面必须添加关键字val或者var,这是保证每个入参都会自动声明同名的属性字段;
3、数据类有自己的一套行事规则,所以它只能是个独立的类,不能是其他类型的类,否则不同规则之间会爆发冲突;
现在利用上面定义的数据类——植物类Plant,演示看看外部如何操作数据类,具体调用代码如下所示:
var lotus = Plant("莲", "莲藕", "莲叶", "莲花", "莲蓬", "莲子")
//数据类的copy方法不带参数,表示复制一模一样的对象
var lotus2 = lotus.copy()
btn_class_data.setOnClickListener {
lotus2 = when (count++%2) {
//copy方法带参数,表示指定参数另外赋值
0 -> lotus.copy(flower="荷花")
else -> lotus.copy(flower="莲花")
}
//数据类自带equals方法,用于判断两个对象是否一样
var result = if (lotus2.equals(lotus)) "相等" else "不等"
tv_class_secret.text = "两个植物的比较结果是${result}\n" +
"第一个植物的描述是${lotus.toString()}\n" +
"第二个植物的描述是${lotus2.toString()}"
}
模板类
在前面的文章《Kotlin入门(11)江湖绝技之特殊函数》中,提到了泛型函数,当时把泛型函数作为全局函数定义,从而在别的地方也能调用它。那么如果某个泛型函数在类内部定义,即变成了这个类的成员方法,又该如何定义它呢?这个问题在Java中是通过模板类(也叫做泛型类)来解决的,例如常见的容器类ArrayList、HashMap均是模板类,Android开发中的异步任务AsyncTask也是模板类。
模板类的应用如此广泛,Kotlin自然而然保留了它,并且写法与Java类似,一样在类名后面补充形如“<T>”或者“<A, B>”的表达式,表示这里的类型待定,要等创建类实例时再确定具体的变量类型。待定的类型可以有一个,如ArrayList;可以有两个,如HashMap;也可以有三个或者更多,如AsyncTask。举个例子,森林里有一条小河,小河的长度可能以数字形式输入(包括Int、Long、Float、Double),也可能以字符串形式输入(String类型)。如果输入的是数字长度,则长度单位采取“m”;如果输入的是字符串长度,则长度单位采取“米”。按照以上需求编写名为River的模板类,具体的类定义代码如下:
//在类名后面添加“<T>”,表示这是一个模板类
class River<T> (var name:String, var length:T) {
fun getInfo():String {
var unit:String = when (length) {
is String -> "米"
//Int、Long、Float、Double都是数字类型Number
is Number -> "m"
else -> ""
}
return "${name}的长度是$length$unit。"
}
}
外部调用模板类构造函数的时候,要在类名后面补充“<参数类型>”,从而动态指定实际的参数类型。不过正如声明变量那样,如果编译器能够根据初始值判断该变量的类型,就无需显式指定该变量的类型;模板类也存在类似的偷懒写法,如果编译器根据输入参数就能知晓参数类型,则调用模板类的构造函数也不必显式指定参数类型。以下是外部使用模板类的代码例子:
btn_class_generic.setOnClickListener {
var river = when (count++%4) {
//模板类(泛型类)声明对象时,要在模板类的类名后面加上“<参数类型>”
0 -> River<Int>("小溪", 100)
//如果编译器根据输入参数就能知晓参数类型,也可直接省略“<参数类型>”
1 -> River("瀑布", 99.9f)
//当然保守起见,新手最好按规矩添加“<参数类型>”
2 -> River<Double>("山涧", 50.5)
//如果你已经是老手了,怎么方便怎么来,Kotlin的设计初衷就是偷懒
else -> River("大河", "一千")
}
tv_class_secret.text = river.getInfo()
}
总结一下,本文介绍了Kotlin的六种特殊函数,首先嵌套类和内部类都定义在某个外部类的内部,区别在于能否访问外部类的成员;其次枚举类和密封类都提供了有序的枚举值集合,区别在于密封类的定义更加严格;再次是帮助开发者摆脱搬砖命运的数据类;最后是解决未定参数类型的模板类(也叫泛型类)。
__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。
Kotlin入门(15)独门秘笈之特殊类的更多相关文章
- 让你的代码量减少3倍!使用kotlin开发Android(二) --秘笈!扩展函数
本文承接上一篇文章:让你的代码量减少3倍!使用kotlin开发Android(一) 创建Kotlin工程 本文同步自博主的私人博客wing的地方酒馆 上一节说到,kotlin可以省去getter,se ...
- Kotlin入门教程——目录索引
Kotlin是谷歌官方认可的Android开发语言,Android Studio从3.0版本开始就内置了Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初Android ...
- Kotlin入门(23)适配器的进阶表达
前面在介绍列表视图和网格视图时,它们的适配器代码都存在视图持有者ViewHolder,因为Android对列表类视图提供了回收机制,如果某些列表项在屏幕上看不到了,则系统会自动回收相应的视图对象.随着 ...
- 写给Android开发者的Kotlin入门
写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...
- 嵌入式linux GUI--DirectFB + GTK至尊秘笈
前言 数年前,曾经开发过一个嵌入式的产品,如今市场依然存在,但由于电子产品的升级换代很快,许多元器件都采购不到了,为了延续产品的生命周期,计划在linux平台上开发新的版本.而在linux上的GUI上 ...
- Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈与熟练的掌握Scala语言【大数据Spark实战高手之路】
Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈 大数据的概念与应用,正随着智能手机.平板电脑的快速流行而日渐普及,大数据中图的并行化处理一直是一个非常热门的话题.图计算正在被广泛地应用于社交 ...
- 网页游戏开发秘笈 PDF扫描版
精选10种常见的游戏类型,透过典型实例,深入剖析游戏引擎及工具的选用技巧,详细讲解每款游戏的制作过程,为快速掌握网页游戏开发提供系统而实用的指南. 网页游戏开发秘笈 目录: 译者序 前 言 导 言 ...
- Kotlin入门第二课:集合操作
测试项目Github地址: KotlinForJava 前文传送: Kotlin入门第一课:从对比Java开始 初次尝试用Kotlin实现Android项目 1. 介绍 作为Kotlin入门的第二课, ...
- Kotlin入门(32)网络接口访问
手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪.对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上.这个客户端与服务端之间的信息交互,基本使用HTTP协议进 ...
随机推荐
- 视频下载四大神器—如何下载优酷/爱奇艺/腾讯/B站超清无水印视频
视频下载四大神器—如何下载优酷/爱奇艺/腾讯/B站超清无水印视频 2018-07-11 | 标签»下载, 下载工具, 视频 又是视频下载,老生常谈的话题.阿刚同学已在乐软博客多次与大家分享推荐 ...
- 谈谈 JavaScript 的正则表达式
一.背景 最近在做 CMS 系统中不同身份登录用户的权限管理,涉及到对 api 路径的识别去判断是否放行.以前对正则表达式都是敬而远之,要用到的话都是直接复制粘贴现成网上的表达式,看也看不太懂,借这次 ...
- Spring Boot功能实战
添加web功能启动器 添加了Spring Boot基础依赖后,如要使用web mvc功能,只需要添加如下启动器即可,Spring Boot会自动装配web功能. <dependencies> ...
- 串口USB单一映射及重命名
本文针对在开发过程中有时会出现用到多个串口设备,usb端口号会发生变化,如设备的灯指示信号和其他控制器都是ttyUSB* .其序号与控制接入的顺序有关,对于写好的launch每次修改串口连接名很麻烦. ...
- 深入学习卷积神经网络(CNN)的原理知识
网上关于卷积神经网络的相关知识以及数不胜数,所以本文在学习了前人的博客和知乎,在别人博客的基础上整理的知识点,便于自己理解,以后复习也可以常看看,但是如果侵犯到哪位大神的权利,请联系小编,谢谢.好了下 ...
- SpringMVC教程3
SpringMVC教程2 一.文件上传 1.引入相关jar包 maven坐标 <!-- fileUpload 解析上传的文件用到的jar --> <dependency> &l ...
- C# 元数据描述
元数据概述:元数据是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件 (PE) 文件或存储在内存中的程序进行描述.将您的代码编译为 PE 文件时,便会将元数据插入到该文件的一部分中,而将代码 ...
- 动态规划法(三)子集和问题(Subset sum problem)
继续讲故事~~ 上次讲到我们的主人公丁丁,用神奇的动态规划法解决了杂货店老板的两个找零钱问题,得到了老板的肯定.之后,他就决心去大城市闯荡了,看一看外面更大的世界. 这天,丁丁刚回到家,他 ...
- break与continue,return结束循环区别
break是跳出一层循环,continue是结束一趟循环 ,return才是结束所有层循环! 如果有多层for循环,break会跳出当前这一层,去执行最外层循环(而不是退出所有层循环);而contin ...
- 《Visual C# 从零开始学》
书名 <Visual C# 从零开始学> 图片 时间 2017年4月-5月 学习 对c#的基础语法有了一个较为完全的了解,总体还算顺利没有遇到理解不了的,感觉最好上手的是做windos窗体 ...