上一篇文章提到泛型函数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)类的概貌与构造的更多相关文章

  1. Kotlin入门(13)类成员的众生相

    上一篇文章介绍了类的简单定义及其构造方式,当时为了方便观察演示结果,在示例代码的构造函数中直接调用toast提示方法,但实际开发是不能这么干的.合理的做法是外部访问类的成员属性或者成员方法,从而获得处 ...

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

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

  3. Kotlin入门(14)继承的那些事儿

    上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用.不过早在<Kotlin入门(12)类的概貌与构造>中,提到MainActivity继承自AppCompatAc ...

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

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

  5. Kotlin入门(11)江湖绝技之特殊函数

    上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数.内联函数.扩展函数.尾递归函数.高阶函数等等,因此本篇文章就 ...

  6. Kotlin入门(18)利用单例对象获取时间

    前面介绍了,使用扩展函数可以很方便地扩充数组Array的处理功能,例如交换两个数组元素.求数组的最大元素等等.那么除了数组之外,日期和时间的相关操作,也是很常见的,比如获取当前日期,获取当前时间.获取 ...

  7. Kotlin入门(15)独门秘笈之特殊类

    上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...

  8. Kotlin入门第一课:从对比Java开始

    1. 介绍 今年初,甲骨文再次对谷歌所谓的安卓侵权使用Java提起诉讼,要求后者赔偿高达90亿美元.随后便传出谷歌因此计划将主力语言切换到苹果主导的Swift,不过这事后来没了跟进. 但谷歌在这两天的 ...

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

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

随机推荐

  1. WCF绑定netTcpBinding寄宿到IIS

    继续沿用上一篇随笔中WCF服务类库 Wettery.WcfContract.Services WCF绑定netTcpBinding寄宿到控制台应用程序 服务端 添加WCF服务应用程序 Wettery. ...

  2. 撰写一篇博客要求讲述四则运算2的设计思想,源程序代码、运行结果截图、编程总结分析,并按照PSP0级的要求记录开发过程中的时间记录日志。

    一.撰写一篇博客要求讲述四则运算2的设计思想,源程序代码.运行结果截图.编程总结分析,并按照PSP0级的要求记录开发过程中的时间记录日志. 1.设计思想: ①创建test.jsp建立第一个前端界面,提 ...

  3. spring cloud(断路器——初学四)

    在分布式架构中,当某个服务单元发生故障后,能通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待. Netflix Hystrix 在Spring Cloud中使用了Hystrix 来实 ...

  4. Rpc简单入门

    RPC这个概念大家都应该很熟悉了,这里不在累述了:使用场景可以参考这篇,本篇主要分享下Thrift和Grpc在.Net Core环境下使用入门.Thirft或者Grps 都支持跨语言.跨平台的Rpc框 ...

  5. 第四章:Android架构

    我们对android有了个大致的了解,知道如何搭建android的环境及简单地写一个HelloWorld程序,而且知道一个android项目包括哪些文件夹和文件及相应的作用.本篇将站在顶级的高度——架 ...

  6. tensorflow 导入gfile模型文件

    with tf.gfile.GFile(os.path.join(self.model_dir, 'ner_model.pb'), 'rb') as f: graph_def = self.tf.Gr ...

  7. Java并发编程笔记之AbstractQueuedSynchronizer源码分析

    为什么要说AbstractQueuedSynchronizer呢? 因为AbstractQueuedSynchronizer是JUC并发包中锁的底层支持,AbstractQueuedSynchroni ...

  8. kubeadm部署kubernetes-1.12.0 HA集群-ipvs

    一.概述 主要介绍搭建流程及使用注意事项,如果线上使用的话,请务必做好相关测试及压测. 1.基础环境准备 系统:ubuntu TLS 16.04  5台 docker-ce:17.06.2 kubea ...

  9. 《深入理解Java虚拟机》(四)虚拟机性能监控与故障处理工具

    虚拟机性能监控与故障处理工具 详解 4.1 概述 本文参考的是周志明的 <深入理解Java虚拟机> 第四章 ,为了整理思路,简单记录一下,方便后期查阅. JDK本身提供了很多方便的JVM性 ...

  10. [android] 手机卫士欢迎细节和主界面

    splash界面的细节 ctrl + O 搜索 在去标题的时候,对话框主题被去掉了,有点丑,现在既要有新版本的对话框又不显示标题 把清单文件中activity节点的主题去掉 进入到applicatio ...