上一篇文章介绍了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)独门秘笈之特殊类的更多相关文章

  1. 让你的代码量减少3倍!使用kotlin开发Android(二) --秘笈!扩展函数

    本文承接上一篇文章:让你的代码量减少3倍!使用kotlin开发Android(一) 创建Kotlin工程 本文同步自博主的私人博客wing的地方酒馆 上一节说到,kotlin可以省去getter,se ...

  2. Kotlin入门教程——目录索引

    Kotlin是谷歌官方认可的Android开发语言,Android Studio从3.0版本开始就内置了Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初Android ...

  3. Kotlin入门(23)适配器的进阶表达

    前面在介绍列表视图和网格视图时,它们的适配器代码都存在视图持有者ViewHolder,因为Android对列表类视图提供了回收机制,如果某些列表项在屏幕上看不到了,则系统会自动回收相应的视图对象.随着 ...

  4. 写给Android开发者的Kotlin入门

    写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...

  5. 嵌入式linux GUI--DirectFB + GTK至尊秘笈

    前言 数年前,曾经开发过一个嵌入式的产品,如今市场依然存在,但由于电子产品的升级换代很快,许多元器件都采购不到了,为了延续产品的生命周期,计划在linux平台上开发新的版本.而在linux上的GUI上 ...

  6. Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈与熟练的掌握Scala语言【大数据Spark实战高手之路】

    Spark GraphX宝刀出鞘,图文并茂研习图计算秘笈 大数据的概念与应用,正随着智能手机.平板电脑的快速流行而日渐普及,大数据中图的并行化处理一直是一个非常热门的话题.图计算正在被广泛地应用于社交 ...

  7. 网页游戏开发秘笈 PDF扫描版

    精选10种常见的游戏类型,透过典型实例,深入剖析游戏引擎及工具的选用技巧,详细讲解每款游戏的制作过程,为快速掌握网页游戏开发提供系统而实用的指南. 网页游戏开发秘笈 目录: 译者序  前 言  导 言 ...

  8. Kotlin入门第二课:集合操作

    测试项目Github地址: KotlinForJava 前文传送: Kotlin入门第一课:从对比Java开始 初次尝试用Kotlin实现Android项目 1. 介绍 作为Kotlin入门的第二课, ...

  9. Kotlin入门(32)网络接口访问

    手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪.对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上.这个客户端与服务端之间的信息交互,基本使用HTTP协议进 ...

随机推荐

  1. Shell-16--函数

    函数的定义和调用放在一起(同一个shell)的好处是不会存在路径的问题:如果功能复杂,则应分开

  2. docker:(2)通过Dockerfile构建镜像并发布web项目

    上一篇讲解了docker的基本使用 http://www.cnblogs.com/xiaochangwei/p/8204511.html 虽然通过修改获取到的镜像可以达到使用目的,但是多操作几次就会发 ...

  3. css 如何“画”一个抽奖转盘

    主要描述的是如何运用 css 绘制一个抽奖转盘,并运用原生 js 实现转盘抽奖效果. 先来张效果图: 布局 一般来说,转盘一般有四个部分组成:外层闪烁的灯.内层旋转的圆盘.圆盘上的中奖结果.指针. 所 ...

  4. centos安装守护进程工具supervisor

    安装命令 yum install supervisor 启动守护进程 supervisord -c /etc/supervisord.conf 切换至/etc/supervisord.d目录下 写一个 ...

  5. ffmpeg 处理视频项目中用到的一些命令

    多媒体视频处理工具FFmpeg有非常强大的功能包括视频采集功能.视频格式转换.视频抓图.给视频加水印等. 目前仅接触到了一些初级命令,今天进行了简单整理. 分辨率 //智能1:1缩放 -i : -vf ...

  6. [视频]K8飞刀 shellcode loader演示教程

    [视频]K8飞刀 shellcode loader演示教程 https://pan.baidu.com/s/1eQ77lPw

  7. win10 store 无法连接网络(原创)

    当你试过所有的解决攻略 都无效时,那么使用这个教程 关闭以下的蓝色框里的

  8. vue父子组件传递参数之props

    vue中父组件通过props传递数据给子组件, props有两种传递方式 1.props:['msg']2.props: { msg:{ type:String, default:"&quo ...

  9. Python sqlalchemy的基本使用

    示例代码 from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base fr ...

  10. springboot+cloud 学习(四)Zuul整合Swagger2

    前言 在微服务架构下,服务是分散的,怎么把所有服务接口整合到一起是我们需要关注的. 下面举例用zuul作为分布式系统的网关,同时使用swagger生成文档,想把整个系统的文档整合在同一个页面上来说明. ...