• 资讯

招商分类

推荐文章

当前位置 | 首页 | 行业资讯 | 观点

Kotlin入门(12)类的概貌与构造

2023-09-18 10:06:07 90

上一篇文章提到泛型函数appendString是在类外面定义,这不免使人疑惑,类里面又该怎样定义成员函数呢?为解答这个疑问,接下来的几篇文章将好好描述一下Kotlin如何操作类及其对象,本篇文章先对类的定义进行说明并加以运用。之前我们已经多次见过的类MainActivity,在Java代码中该类的写法如下所示:public class MainActiv

上一篇文章提到泛型函数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的构造函数也支持默认参数,从而避免了冗余的构造函数定义。

__________________________________________________________________________

本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

aHR0cDovL3dlaXhpbi5xcS5jb20vci9lRGdwTVlmRV9kcUdyY05TOTIxcQ== (二维码自动识别)

联系电话:400-888-9999 联系传真:400-888-9999 E-mail:xxxx@admin.com
(c)2008-2020 DESTOON B2B SYSTEM All Rights Reserved