上一篇文章提到泛型函数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. LeetCode手记-Add Binary

    问题描述 问题分析 分析题意,此题实际是求解两个二进制数的和,但是有两点要注意: 1.字符串的长度不限,所以相应十进制数值很可能会超过int的上限. 2.二进制的加法规则是自右向左进位,需要注意,以题 ...

  2. 纯JavaScript实现俄罗斯方块(详细注释,ES6)

    借鉴了慕课网的课程<基于websocket的火拼俄罗斯(单机版)>虽然改动比较多,但是还是核心部分没有改,加了一些不怎么好听的声音,和看起来并不好看的界面. CSS部分基本是瞎写的,因为对 ...

  3. js按钮 防重复提交

    给html 按钮加id属性   例: <button id="addBtn"  onclinck="check()">  </button&g ...

  4. PL/SQL Developer从11.0.6版本开始32/64为之区分

    PL/SQL Developer从11.0.6版本开始32/64为之区分 在PL/SQL Developer11.0.6版本之前,64位Windows操作系统在使用PL/SQL Developer都未 ...

  5. PyCharm的基本快捷键和配置简介

    快捷键 1.编辑(Editing)Ctrl + Space 基本的代码完成(类.方法.属性)Ctrl + Alt + Space 快速导入任意类Ctrl + Shift + Enter 语句完成Ctr ...

  6. 在线画UML图的工具

    工作需要在线画各种UML图,类图.协作图.用例图等等,调查了一些在线画UML图的工具,有的做的很好但要收费,例如:http://www.gliffy.com/,发现现在免费好用的是ProcessOn: ...

  7. SpringBoot的幕后推手...

    一.背景 ​ 这两年随着微服务的盛行,SpringBoot框架水到渠成的得到了高曝光,作为程序猿的我们,现在要是不知道一点SpringBoot相关的东西,貌似出去找工作都会被深深地鄙视,不过在我们开始 ...

  8. PHP 正则表达式资料

    正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串.将匹配的子串做替换或者从某个串中取出符合某个条件的子串等. 举例来说,正则表达式的一个 ...

  9. 完整的一次 HTTP 请求响应过程(一)

    因特网无疑是人类有史以来最伟大的设计,它互联了全球数亿台计算机.通讯设备,即便位于地球两端的用户也可在顷刻间完成通讯. 可以说『协议』是支撑这么一个庞大而复杂的系统有条不紊运作的核心,而所谓『协议』就 ...

  10. JavaWeb学习 (十九)————JavaBean

    一.什么是JavaBean JavaBean是一个遵循特定写法的Java类,它通常具有如下特点: 这个Java类必须具有一个无参的构造函数 属性必须私有化. 私有化的属性必须通过public类型的方法 ...