上一篇文章提到泛型函数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. 前后端分离开发之前端自己的API(DB)---- (1)

    Creating demo APIs for Front-End Developer 心理准备 Tool-1 开发工具/编辑器:Visual Studio Code , 即 VSCode官网: htt ...

  2. 使用MultipartFile上传文件

    转载地址:https://www.cnblogs.com/lunaticcoder/p/9813483.html(具体的看这个这个大佬的博客) 依赖包: <!-- 上传文件依赖组件 --> ...

  3. C++primer笔记之关联容器

    在这一章中,有以下的几点收获: 1.pair类型的使用相当频繁,如果需要定义多个相同的pair类型对象,可考虑利用typedef简化其声明: typedef pair<string, strin ...

  4. kafka shutdown停止关闭很慢问题的解决方案

    kafka shutdown停止很慢问题 在数据量大的时候,consumer一次抓取数据的数据很多,进入到业务处理的数据可能有很多, 假设一次poll有1万条数据进入业务程序,而且业务程序是和poll ...

  5. Oracle 插入时间时 报错:ORA-01861: 文字与格式字符串不匹配 的解决办法

    一.写sql的方式插入到Oracle中 往oracle中插入时间  '2007-12-28 10:07:24'如果直接按照字符串方式,或者,直接使用to_date('2007-12-28 10:07: ...

  6. 使用配置文件自定义Ribbon配置

    1.application.yml——Ribbon配置文件 debug: false spring: application: name: mcc-ribbon-properties cloud: c ...

  7. bigdata-01-应用

    1, 基本概念 在互联网技术发展到现今阶段,大量日常.工作等事务产生的数据都已经信息化,人类产生的数据量相比以前有了爆炸式的增长,以前的传统的数据处理技术已经无法胜任,需求催生技术,一套用来处理海量数 ...

  8. visual studio code (vsc)中查看 php 数组的全部元素

    在 vsc 调试 php 时,如果数组元素过多,只能查看前面 30个左右的元素,如果需要看更多的元素,可以配置 xDebugSettings 修改项目中的 .vscode/launch.json 文件 ...

  9. 实验吧 貌似有点难 伪造ip

    解题链接: http://ctf5.shiyanbar.com/phpaudit/ 解答: 点击View the source code —>代码显示IP为1.1.1.1即可得到KEY—> ...

  10. 史上最详细nodejs版本管理器nvm的安装与使用(附注意事项和优化方案)

    使用场景 在Node版本快速更新迭代的今天,新老项目使用的node版本号可能已经不相同了,node版本更新越来越快,项目越做越多,node切换版本号的需求越来越迫切,传统卸载一个版本在安装另一个版本的 ...