Kotlin入门(13)类成员的众生相
上一篇文章介绍了类的简单定义及其构造方式,当时为了方便观察演示结果,在示例代码的构造函数中直接调用toast提示方法,但实际开发是不能这么干的。合理的做法是外部访问类的成员属性或者成员方法,从而获得处理之后的返回值,然后外部再根据返回信息判断对应的处理方式。有鉴于此,本篇文章就来谈谈Kotlin如何声明成员属性和成员方法,以及外部如何访问类成员。
接上篇文章动物类的例子,每只动物都有名称和性别两个属性,所以必然要输入这两个参数,对应的类代码如下所示:
class WildAnimal (name:String, sex:Int = 0) {
}
有了输入参数,还得声明对应的属性字段,用来保存入参的数值。按照Java和C++的思路,Kotlin给Animal类添加两个属性后的代码应该是下面这样的:
class WildAnimal (name:String, sex:Int = 0) {
var name:String //var表示动物名称可以修改
val sex:Int //val表示动物性别不可修改
init {
this.name = name
this.sex = sex
}
}
用惯了主流语言,可能觉得上面的写法理所当然,没有什么地方不妥。但你是否想过,以上代码至少有两个冗余之处?
1、属性字段跟构造函数的入参,不但名称一样,并且变量类型也是一样的;
2、初始化函数中给属性字段赋值,为了区别同名的属性与入参,特意给属性字段添加了前缀“this.”;
你一拍脑袋,嘀咕道:说的也是。啰嗦是啰嗦了一些,可大家都这么写,难不成Kotlin还有更短的写法?正所谓细微处见差别,这种看似平常的代码,无意中给程序员带来了不少的重复劳动。其实此处的代码逻辑很简单,仅仅是把构造函数的输入参数保存到类的属性中,不管输入参数有几个,该类都依样画瓢声明同样数量的属性字段并加以赋值。既然属性字段和构造函数的入参存在一一对应关系,那么可以通过某种机制让编译器自动对其命名与赋值,Kotlin正是遵循了类似的设计思路,且看Kotlin代码是怎样实现的:
class WildAnimal (var name:String, val sex:Int = 0) {
}
看到Kotlin的属性声明代码,会不会觉得很不可思议?与本文开头的类代码相比,只有两个改动之处:其一是给名称参数前面增加了关键字“var”,表示同时声明与该参数同名的可变属性并赋值;其二是给性别参数前面增加了关键字“val”,表示同时声明与该参数同名的不可变属性并赋值。而改动后的代码,其运行结果和手工添加属性声明并赋值的代码是一样的。比如下面的演示代码,只要声明了WildAnimal类的对象实例,即可直接访问该对象的名称和性别字段:
btn_member_default.setOnClickListener {
setAnimalInfo()
var animal = when (count%2) {
0 -> WildAnimal(animalName)
else -> WildAnimal(animalName, animalSex)
}
tv_class_member.text = "这只${animal.name}是${if (animal.sex==0) "公" else "母"}的"
}
倘若WildAnimal使用Java实现,按常规还得补充get***的属性获取方法,以及set***的属性设置方法,对应的Java完整实现代码如下所示:
public class WildAnimal {
private String name;
private int sex; public WildAnimal(String name, int sex) {
this.name = namme;
this.sex = sex;
} public String getName() {
return this.name;
} public void setName(String name) {
this.name = name;
} public int getSex() {
return this.sex;
} public void setSex(int sex) {
this.sex = sex;
}
}
不比不知道,比一比才发现原来Kotlin精简了大幅代码,包括:
1、冗余的同名属性声明;
2、冗余的同名属性赋值;
3、冗余的属性获取方法与设置方法;
看到这里,还有什么理由不掌握Kotlin呢?它既为程序员避免了大量的重复劳动,还有效增强了代码的可读性。
如果某个字段并非入参的同名属性,则需在类内部显示声明该属性字段。例如,前面WildAnimal类的性别只是个整型的类型字段,而界面上展示的是性别的中文名称,所以应当给该类补充一个性别名称的属性声明,这样每次访问sexName字段即可获得动物的展示性别。下面是补充了新属性之后的类代码:
class WildAnimalMember (var name:String, val sex:Int = 0) {
//非空的成员属性必须在声明时赋值或者在构造函数中赋值
//否则编译器会报错“Property must be initialized or be abstract”
var sexName:String
init {
sexName = if(sex==0) "公" else "母"
}
}
现在外部的调用代码可以直接访问字段animal.sexName了,对应的调用代码如下所示:
btn_member_custom.setOnClickListener {
setAnimalInfo()
var animal = when (count%2) {
0 -> WildAnimalMember(animalName)
else -> WildAnimalMember(animalName, animalSex)
}
tv_class_member.text = "这只${animal.name}是${animal.sexName}的"
}
在类内部定义成员方法,就是前几篇文章提到的定义普通函数,具体参见《Kotlin入门(9)函数的基本用法》和《Kotlin入门(10)七十二变的输入参数》,这里不再赘述。下面给出一个在动物类中定义成员方法的代码例子:
class WildAnimalFunction (var name:String, val sex:Int = 0) {
var sexName:String
init {
sexName = if(sex==0) "公" else "母"
} fun getDesc(tag:String):String {
return "欢迎来到$tag:这只${name}是${sexName}的。"
}
}
调用成员方法的过程,一样是先在外部声明该类的实例,然后通过“实例名称.方法名称(输入参数)”的形式进行调用,这跟Java没什么区别。具体的方法调用代码如下所示:
btn_member_function.setOnClickListener {
setAnimalInfo()
var animal = when (count%2) {
0 -> WildAnimalFunction(animalName)
else -> WildAnimalFunction(animalName, animalSex)
}
tv_class_member.text = animal.getDesc("动物园")
}
上面介绍了Kotlin对成员属性和成员方法的处理方式,外部不管访问成员属性还是访问成员方法,都得先声明类的实例,再通过实例访问类的成员。可是Java还有静态成员的概念,静态成员使用关键字static来修饰,且外部是通过“类名.静态成员名称”的形式访问静态成员(包括静态属性和静态方法)。
然而Kotlin取消了关键字static,也就无法运用静态成员的相关手段。为了弥补这方面的功能缺陷,Kotlin引入了伴生对象的概念,可以把它理解为“影子”,伴生对象之于它所在的类,仿佛是如影随形。打个比方,类的实例犹如这个类的儿子,一个类可以拥有很多个儿子;而影子只有一个,并且儿子需要繁衍而来,但影子天生就有、无需繁衍。利用伴生对象的技术,即可间接实现静态成员的功能,前面有个代码从性别类型获得性别名称,反过来也可以从性别名称获得性别类型,这个功能便可以在伴生对象中定义一个judgeSex方法判断性别类型。下面列出定义好的代码例子:
class WildAnimalCompanion (var name:String, val sex:Int = 0) {
var sexName:String
init {
sexName = if(sex==0) "公" else "母"
} fun getDesc(tag:String):String {
return "欢迎来到$tag:这只${name}是${sexName}的。"
} //在类加载时就运行伴生对象的代码块,其作用相当于Java里面的static { ... }代码块
//关键字companion表示伴随,object表示对象,WildAnimal表示伴生对象的名称
companion object WildAnimal{
fun judgeSex(sexName:String):Int {
var sex:Int = when (sexName) {
"公","雄" -> 0
"母","雌" -> 1
else -> -1
}
return sex
}
}
}
以上代码的judgeSex方法,在输入“公”或者“雄”时,将返回0;输入“母”或者“雌”时,将返回1。外部若要调用该方法,则可使用“WildAnimalCompanion.WildAnimal.judgeSex(名称)”,也可使用简化后的“WildAnimalCompanion.judgeSex(名称)”,后一种方式看起来就等同于Java的静态方法。以下是具体的示例调用代码:
val sexArray:Array<String> = arrayOf("公","母","雄","雌")
btn_companion_object.setOnClickListener {
var sexName:String = sexArray[count++%4]
//伴生对象的WildAnimal名称可以省略掉
//tv_class_member.text = "\"$sexName\"对应的类型是${WildAnimalCompanion.WildAnimal.judgeSex(sexName)}"
tv_class_member.text = "\"$sexName\"对应的类型是${WildAnimalCompanion.judgeSex(sexName)}"
}
以此类推,既然伴生对象能够实现静态函数,也能实现静态属性,只要在伴生对象内部声明几个变量就行。譬如judgeSex方法通过数字0表示雄性,通过数字1表示雌性,但是光光一个0或1,压根没法联想到是雄性还是雌性,只能凭开发者脑袋的记忆,不过记忆往往也会搞混掉。像这种特定含义的类型数值,更好的办法是采取有实际意义的常量名称,在Android中便存在Color.RED、Color.GREEN、Color.BLUE等等颜色常量,从它们的名称能够直接对应到颜色数值。所以这里表示雄性/雌性的0和1,也可通过静态常量的形式来表达,比如用整型常量MALE表示雄性的0,用FEMALE表示雌性的1。具体到编码上面,即是在伴生对象中增加这几个常量定义,代码示例如下所示:
class WildAnimalConstant(var name:String, val sex:Int = MALE) {
var sexName:String
init {
sexName = if(sex==MALE) "公" else "母"
} fun getDesc(tag:String):String {
return "欢迎来到$tag:这只${name}是${sexName}的。"
} companion object WildAnimal{
//静态常量的值是不可变的,所以要使用关键字val修饰
val MALE = 0
val FEMALE = 1
val UNKNOWN = -1
fun judgeSex(sexName:String):Int {
var sex:Int = when (sexName) {
"公","雄" -> MALE
"母","雌" -> FEMALE
else -> UNKNOWN
}
return sex
}
}
}
从上述代码看到,表示性别的数值0都被MALE代替,数值1被FEMALE代替,从而提高了代码的可读性。外部若想进行动物性别判断的话,就可以使用WildAnimalConstant.MALE表示雄性,使用WildAnimalConstant.FEMALE表示雌性了。
总结一下,Kotlin的类成员分为实例成员与静态成员两种,实例成员包括普通成员属性和成员方法,其中与入参同名的成员属性可以在构造函数中直接声明,外部必须通过类的实例才能调用实例成员。静态成员包括静态常量与静态方法,它们都在类的伴生对象中定义,外部可以通过类名直接使用静态成员。
__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。
Kotlin入门(13)类成员的众生相的更多相关文章
- Python 入门 之 类成员
Python 入门 之 类成员 1.类的私有成员: 私有: 只能自己拥有 以 __ 开头就是私有内容 对于每一个类的成员而言都有两种形式: - 公有成员,在任何地方都能访问 - 私有成员,只有在类的内 ...
- Kotlin入门(12)类的概貌与构造
上一篇文章提到泛型函数appendString是在类外面定义,这不免使人疑惑,类里面又该怎样定义成员函数呢?为解答这个疑问,接下来的几篇文章将好好描述一下Kotlin如何操作类及其对象,本篇文章先对类 ...
- C++入门经典-类成员的可访问性,继承后的可访问性
1:关键字public.private.protected说明类成员是共有的.私有的,还是保护的.这3个关键字将类划分为3个区域,在public区域的类成员可以在类作用域外被访问,而private区域 ...
- Kotlin入门教程——目录索引
Kotlin是谷歌官方认可的Android开发语言,Android Studio从3.0版本开始就内置了Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初Android ...
- Kotlin入门(14)继承的那些事儿
上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用.不过早在<Kotlin入门(12)类的概貌与构造>中,提到MainActivity继承自AppCompatAc ...
- C++入门经典-例7.1-对象之访问类成员
1:建立一个类CPerson. (1)在person.h文件中代码: class CPerson { public: //数据成员 int m_iIndex; ]; short m_shAge; do ...
- 写给Android开发者的Kotlin入门
写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...
- Kotlin入门(11)江湖绝技之特殊函数
上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数.内联函数.扩展函数.尾递归函数.高阶函数等等,因此本篇文章就 ...
- Kotlin入门(15)独门秘笈之特殊类
上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...
随机推荐
- Java匹马行天下——开篇
个人感言: 匹马行天下是我高中时候看过一部叫<九鼎记>的小说中的其中一个大章节标题,在整个这一章中,讲的是是主人公滕青山历经艰险,又心如磐石,一心修行,最后巅峰归来的故事.现在回想,依旧心 ...
- centos7配置apache服务
首先写下基本的步骤: 1.环境准备:关闭防火墙(不关闭无法访问,我这里只是简单的配置,实际部署项目应该会有具体的设置),关闭selinux(试过,不关闭也没事,一般都关闭) 配置 ip 2.安装软件包 ...
- JAVA中byte为负数处理
java中一定有人遇见过byte取值为负数情况,比如0xc0对应的值-64,其实应该是192,这里就需要我们转化处理 /** * Description: 负数byte转正int <BR> ...
- redis epoll 原理梗概
redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统. redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量.为什么 Redis 中要使用 I/O 多路复用这 ...
- Servlet-session简介及使用场景
- 测试工具之RobotFramework关键字和快捷键
RF中关键字很多,即使经常使用也有些关键字没有使用过,所以我们就需要记住一些常用的关键字,在使用中本人整理了部分关键字.快捷键和部分RF的常识 1.F5 如果只记得关键字部分,可以通过F5呼出关键字查 ...
- C# winform嵌入unity3D
最近做项目需要winform嵌入unity的功能,由于完全没接触过这类嵌入的于是在网上搜,有一种方法是UnityWebPlayer插件,也开始琢磨了一段时间,不过一会发现在5.4版本以后这个东西就被淘 ...
- shell中mail发邮件的问题
今天为了监控一下脚本,按照网上说的利用mail 发邮件,mail -s "error预警2" peien@1221.qq.com<'邮件内容',发现出现cc,不知道啥问题,也 ...
- 7z 程序打包 Demo
最近准备做一个用户端 异常收集的程序 需要收集用户机器的程序日志和相关信息 准备打包发回来 所以研究了一下7Z 文件压缩 做一个笔记吧 遇到的问题: 1:VS2008 遇到 loadlibrary ...
- C#效率优化(3)-- 使用foreach时避免装箱
Introduction: ※本文不是在描述旧版本Unity中mono编译器导致的foreach语句额外装箱错误 博主是一名Unity 3D游戏开发者,游戏使用C#+lua开发,最近在优化C#代码时, ...