Kotlin入门(12)类的概貌与构造
上一篇文章提到泛型函数appendString是在类外面定义,这不免使人疑惑,类里面又该怎样定义成员函数呢?为解答这个疑问,接下来的几篇文章将好好描述一下Kotlin如何操作类及其对象,本篇文章先对类的定义进行说明并加以运用。
之前我们已经多次见过的类MainActivity,在Java代码中该类的写法如下所示:
public class MainActivity extends AppCompatActivity {
}
而对应的Kotlin代码是下面这样的:
class MainActivity : AppCompatActivity() {
}
根据上述代码简单地比较,Kotlin对类的写法与Java之间有以下几点区别:
1、Kotlin省略了关键字public;
2、Kotlin用冒号“:”代替extends,也就是通过冒号表示继承关系;
3、Kotlin进行继承时,父类后面多了括号“()”;
表面上二者区别不大,其实类这部分大有玄机,真正用Kotlin实现的话让人出乎意料,接下来且待笔者细细道来。
从最简单的类定义开始,下面是名为Animal的动物类定义代码:
class Animal {
//类的初始化函数
init {
//Kotlin使用println替换Java的System.out.println
println("Animal:这是个动物的类")
}
}
对应为Animal类创建实例的代码如下:
btn_class_simple.setOnClickListener {
//var animal: Animal = Animal()
//因为根据等号后面的构造函数已经明确知道这是个Animal的实例
//所以声明对象时可以不用指定它的类型
var animal = Animal()
tv_class_init.text = "简单类的初始化结果见日志"
}
然后继续给Kotlin找茬,不费多少功夫又发现了它跟Java的三点不同之处:
1、Kotlin初始化函数(看似构造函数?)的名字叫init,不像Java那样把类名作为构造函数的名称;
2、Kotlin打印日志使用了类似C语言的println方法,而非Java的System.out.println;
3、Kotlin创建实例时省略了关键字new;
既然Kotlin把init当作初始化函数,那么是否意味着,构造函数的参数应该添加在init名称后面?可事情往往不是你想的那样,Kotlin作为新时代的编程语言,它的设计总是突破常规。前面介绍函数的时候,提到Kotlin把函数看成是一种特殊的变量,至于类某种意义上算是一种特殊的函数。所以构造函数的输入参数得直接加到类名后面,而init函数仅仅表示创建类实例之时的初始化动作,下面是添加了入参的类定义代码:
//如果主构造函数没有注解说明,则类名后面的constructor可以省略
//class AnimalMain (context:Context, name:String) {
class AnimalMain constructor(context:Context, name:String) {
init {
context.toast("这是只$name");
}
}
然而以上代码似乎存在着问题,因为一个类可能会有多个构造函数,像自定义视图常常需要定义三个构造函数,下面便是某个自定义视图的Java代码例子:
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context,AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
对于上述这种存在多个构造函数的情况,Java可以通过覆写带不同参数的构造函数来实现,那么Kotlin已经在类名后面指明了固定数量的入参,又该如何表示拥有其它参数的构造函数?针对这个疑点,Kotlin引入了主构造函数与二级构造函数的概念,上面演示的只是主构造函数,它分为两部分:跟在类名后面的参数是主构造函数的入参,同时init方法是主构造函数的内部代码。至于二级构造函数,则可以在类内部直接书写完整的函数表达式,为了让读者有更直观的认识,下面先贴出一段包含二级构造函数的Kotlin类定义代码:
class AnimalMain constructor(context:Context, name:String) {
init {
context.toast("这是只$name");
}
constructor(context:Context, name:String, sex:Int) : this(context, name) {
var sexName:String = if(sex==0) "公" else "母"
context.toast("这只${name}是${sexName}的");
}
}
从上可以看出,二级构造函数和普通函数相比有两个区别:
1、二级构造函数没有函数名称,只用关键字constructor表示这是个构造函数。
2、二级构造函数需要调用主构造函数,“this(context, name)”这句代码在Java中要写在函数体内部,在Kotlin中则以冒号开头补充到输入参数后面,这意味着二级构造函数实际上是从主构造函数扩展而来,冒号表示前边属于后边的类型,犹如“var count:Int”一般。
由此看来,因为二级构造函数从属于主构造函数,于是如果使用二级构造函数声明该类的实例,则系统会先调用主构造函数的init代码,再调用二级构造函数的自身代码。现在若想声明AnimalMain类的实例,即可通过主构造函数声明,也可通过二级构造函数声明,具体的声明代码如下所示:
btn_class_main.setOnClickListener {
setAnimalInfo()
when (count%2) {
0 -> { var animal = AnimalMain(this, animalName) }
else -> { var animal = AnimalMain(this, animalName, animalSex) }
}
}
不过在测试过程中发现,通过二级构造函数声明实例有个问题,就是toast会弹窗两次。原因是主构造函数的init方法已经弹窗,然后二级构造函数自身再次弹窗,看来这么做并不完美,能否不要强制调用主构造函数呢?为了解决该问题,Kotlin设定了主构造函数不是必需的,也就是说,某个类可以把几个构造函数都放在类内部定义,就去掉了主构造函数,据此修改之后的类代码如下:
class AnimalSeparate {
constructor(context:Context, name:String) {
context.toast("这是只$name");
}
constructor(context: Context, name:String, sex:Int) {
var sexName:String = if(sex==0) "公" else "母"
context.toast("这只${name}是${sexName}的");
}
}
这样一来,新类AnimalSeparate便不存在主构造函数了,它的两个二级构造函数之间没有从属关系,它们各自的函数代码是互相独立的。无论通过哪个构造函数声明类的实例,都只会调用这个构造函数的代码,而不会像之前那样去调用主构造函数的代码了。
未料如此折腾一番,隐隐感觉哪里不对劲,猛然发现改来改去,AnimalSeparate类依旧完整写着两个构造函数,这么做跟Java的构造函数写法又有什么区别呢?无非是把类名换成了关键字constructor,其它地方仍然换汤不换药。Kotlin的宗旨是化繁为简,没想到结果却返璞归真了,真是令人吓出一身冷汗。客官莫急,倘若Kotlin黔驴技穷,那么它根本没资格挑战Java,所以肯定是有办法的。不知读者是否还记得前面介绍函数时说到的默认参数?类的构造函数同样也能添加默认参数。
注意到AnimalSeparate类的两个构造函数只是相差一个输入参数,所以完全可以把它们合并成一个带默认参数的主构造函数,新的主构造函数既可以输入两个参数,又可以输入三个参数。如果利用带两个入参的主构造函数创建实例,则形同调用了原来的第一个构造函数“constructor(context:Context, name:String)”;如果利用带三个入参的主构造函数创建实例,则形同调用了原来的第二个构造函数“constructor(context: Context, name:String, sex:Int)”。下面即为采取默认参数的类定义代码:
//类的主构造函数使用了默认参数
class AnimalDefault (context: Context, name:String, sex:Int = 0) {
init {
var sexName:String = if(sex==0) "公" else "母"
context.toast("这只${name}是${sexName}的");
}
}
这下看起来简洁了许多,新类AnimalDefault用起来也毫不费事,之前的实例创建代码只消换个类名就好,完全无缝对接。具体的调用代码如下所示:
btn_class_default.setOnClickListener {
setAnimalInfo()
when (count%2) {
0 -> { var animal = AnimalDefault(this, animalName) }
else -> { var animal = AnimalDefault(this, animalName, animalSex) }
}
}
总结一下,Kotlin给类的构造函数引进了关键字constructor,并且区分了主构造函数和二级构造函数。主构造函数的入参在类名后面声明,函数体则位于init方法中;二级构造函数从属于主构造函数,它不但由主构造函数扩展而来,而且必定先调用主构造函数的实现代码。另外,Kotlin的构造函数也支持默认参数,从而避免了冗余的构造函数定义。
__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

Kotlin入门(12)类的概貌与构造的更多相关文章
- Kotlin入门(13)类成员的众生相
上一篇文章介绍了类的简单定义及其构造方式,当时为了方便观察演示结果,在示例代码的构造函数中直接调用toast提示方法,但实际开发是不能这么干的.合理的做法是外部访问类的成员属性或者成员方法,从而获得处 ...
- Kotlin入门教程——目录索引
Kotlin是谷歌官方认可的Android开发语言,Android Studio从3.0版本开始就内置了Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初Android ...
- Kotlin入门(14)继承的那些事儿
上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用.不过早在<Kotlin入门(12)类的概貌与构造>中,提到MainActivity继承自AppCompatAc ...
- 写给Android开发者的Kotlin入门
写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...
- Kotlin入门(11)江湖绝技之特殊函数
上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数.内联函数.扩展函数.尾递归函数.高阶函数等等,因此本篇文章就 ...
- Kotlin入门(18)利用单例对象获取时间
前面介绍了,使用扩展函数可以很方便地扩充数组Array的处理功能,例如交换两个数组元素.求数组的最大元素等等.那么除了数组之外,日期和时间的相关操作,也是很常见的,比如获取当前日期,获取当前时间.获取 ...
- Kotlin入门(15)独门秘笈之特殊类
上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...
- Kotlin入门第一课:从对比Java开始
1. 介绍 今年初,甲骨文再次对谷歌所谓的安卓侵权使用Java提起诉讼,要求后者赔偿高达90亿美元.随后便传出谷歌因此计划将主力语言切换到苹果主导的Swift,不过这事后来没了跟进. 但谷歌在这两天的 ...
- Kotlin入门(32)网络接口访问
手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪.对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上.这个客户端与服务端之间的信息交互,基本使用HTTP协议进 ...
随机推荐
- MySQL 字符集utf8和utf-8的关系
目录 什么是字符集(character set) 校对规则(collation) ASCII码 Unicode国际化支持 UTF-8 utf8 utf8与utf8mb4的关系 超集 字符集设置 什么是 ...
- Testing - 软件测试知识梳理 - 比较质量保证(QA)与质量控制(QC)
QA QC QM 概念 Quality Assurance (质量保证) Quality Control (质量控制) Quality Manage (质量管理) 定义 为达到质量要求所采取的作业技术 ...
- Linux - history命令的常用方法
history命令 打印所有命令记录:history 打印最近10条记录:history 10 执行第123条命令记录:!123 重复执行上一条命令:!! 执行最后一次以ls开头的命令:!ls 逐屏列 ...
- Python又把GUI界面攻下了,今天就告诉你怎么玩
0.引言 学Python这么久了,一直想做个界面出来,最近发现Python有个内置库tkinter,利用它可以很轻松做出一些简易的UI界面,首先来看Python官方对Tkinter的说明: The t ...
- ajax接口和后台交互
//定义一个公众处理ajax的方法 function handelAjax(url,method,parm,callback) { $.ajax({ url:url, type:method, dat ...
- ThinkNet终于见面了
经过一段时间的DDD学习,第一个开源框架终于初步完成了,我为他命名为ThinkNet.之前或许你听过ThinkPHP,没错,虽然我对php没有过多的掌握,但是借助thinkphp,我也能开发一个web ...
- k8s升级,HA集群1.12.0~HA集群1.13.2
k8s升级,此次升级是1.12.0 至1.13.2 准备 # 首先升级master节点的基础组件kubeadm.kubelet.kubectl apt policy kubeadm 找到相应的版本,如 ...
- [Vijos 1676] 陶陶吃苹果
Description curimit知道陶陶很喜欢吃苹果.于是curimit准备在陶陶生日的时候送给他一棵苹果树. curimit准备了一棵这样的苹果树作为生日礼物:这棵苹果树有n个节点,每个节点上 ...
- Vim 多行剪切、复制和删除
剪切 快捷键方式: dd:剪切光标所处当前行 n + dd:剪切光标所在行及以下共 n 行 按 p 粘贴在光标所在行 命令行方式: 例如剪切1到10行,并粘贴在12行处: 1,10 m 12 复制 快 ...
- centos7指定yum安装软件路径
网上的命令都是垃圾 yum -c /etc/yum.conf --installroot=/opt/all_venv/ --releasever=/ install nginx 该命令简单解释如下: ...