一、类的构造
1.1 类的简单定义
首先来看看在Android中Java的MainActivity
public class MainActivity extends AppCompatActivity { ... }
再看看Kotlin中的MainActivity
class MainActivity : AppCompatActivity() { ... }
通过上述的代码比较,Kotlin对类的写法与Java之间有以下几点区别:
(1)Kotlin省略了关键字public,因为Kotlin默认类是开放的,所以不需要这个关键字。
(2)Kotlin用冒号“:”代替extends,也就是通过冒号表示继承关系。
(3)Kotlin进行继承时,父类后面多了括号“()”。
然后我们自己新建名为Animal的Kotlin类
步骤:
鼠标右键你的包名→New→Kotlin File/Class→创建的文件类型选择Class→OK(创建完成)
然后你就会看到这样的一个图
现在开始编写代码:
package com.llw.kotlinstart class Animal { //类的初始化函数 init { //Kotlin的println替换Java的System.out.println println("Animal:这是个动物类") } }
现在这个类已经创建好了,并且有了初始化函数,我们在MainActivity.kt中来实例化这个类,代码如下:
activity_main.xml代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:textColor="#000" android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:textColor="#000" android:id="@+id/tv_result" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <LinearLayout android:gravity="center" android:layout_marginTop="20dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btn_test" android:text="Test" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> </LinearLayout>
MainActivity.kt代码
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* import java.lang.Exception import java.text.SimpleDateFormat import java.util.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { //因为根据等号后面的构造函数已经明确知道这是个Animal的实例 //所以声明对象时可以不用指定它的类型 var animal = Animal() tv_result.text = "简单类的初始化结果见日志" } } }
运行效果图如下:
经过这一番操作,我们再与Java对比一下区别:
(1)Kotlin对类进行初始化的函数名称叫init,不像Java那样把雷鸣作为构造函数的名称。
(2)Kotlin打印日志使用类似C语言的println方法,而非Java的System.out.println
(3)Kotlin创建实例时省略了关键字new。
这里面,初始化函数init看似是Kotlin对类的构造函数,但它只是构造函数的一部分,并不完整,因为没有定义输入参数,那么怎么定义呢?谁来定义呢?
1.2 类的构造函数
入参的类定义代码如下:
//如果主构造函数没有带@符号的注解说明,类名后面的constructor就可以省略 class AnimalMain constructor(context:Context,name:String){ //class AnimalMain (context:Context,name:String){ init { context.toast("这是头$name") } }
一个类可能有多个构造函数,Java可以通过覆写带不同参数的构造函数来实现,那么Kotlin已经在类名后面指明了固定数量的入参,又该如何表示拥有其他参数的构造函数呢?针对这个问题,Kotlin引入了主构造函数与二级构造函数的概念,之前的代码演示的是主构造函数,分为两部分,跟在类名后面的参数是主构造函数的入参,同时init方法是主构造函数的内部代码,至于二级构造函数,则可以在类内部直接书写完整的函数表示式,新建一个名为AnimalMain的类,代码如下:
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中要以“super(context,name)”的形式写在函数体内部,在Kotlin中则以冒号开头补充到输入参数后面,这意味着二级构造函数实际上是从主构造函数派生出来的,也可以看作二级函数的返回值是主构造函数。
由此看来,二级构造函数从属于主构造函数,如果使用二级构造函数声明该类的实例,系统就会先调用主构造函数的init代码,再调用二级构造函数的自身代码,现在若想声明AnimalMain类的实例,既可通过主构造函数,也可通过二级构造函数,代码如下:
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* import java.lang.Exception import java.text.SimpleDateFormat import java.util.* class MainActivity : AppCompatActivity() { var animalName:String = "" var animalSex:Int = 0 var count:Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { setAnimalInfo() when(count%2){ 0 -> { var animal = AnimalMain(this,animalName) } else -> { var animal = AnimalMain(this,animalName,animalSex) } } count++ } } fun setAnimalInfo() { animalName = "牛" animalSex = 0 } }
上面代码在运作过程中,通过二级构造函数声明实例有一个问题,就是toast会弹窗两次,因为主构造函数的init方法已经弹窗,然后二级构造函数自身再次弹窗,那能不能不调用主构造函数呢?为了解决该问题,Kotlin设定了主构造函数时不是必需的,也就是说类可以把几个构造函数都放在类内部定义,从而都变成二级构造函数,如此就去掉了主构造函数,为了直观,重新建名为一个AnimalSeparate的类,代码如下
package com.llw.kotlinstart import android.content.Context import org.jetbrains.anko.toast 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}的") } }
这样写就没有主构造函数了,都是二级构造函数,直接使用即可,函数之间没有从属关系,不存在重复调用。
1.3 带默认参数的构造函数
说到默认参数,不知道你有没有想起之前的带默认参数的函数呢?上面的代码中,两个构造函数之间只有一个输入参数的区别,所以完全可以把二者合二为一,变成一个带默认参数的主构造函数,新的主构造函数既能输入两个参数,又能输入三个参数,新创建一个类AnimalDefault,代码如下:
package com.llw.kotlinstart import android.content.Context import org.jetbrains.anko.toast class AnimalDefault (context: Context,name:String,sex:Int = 0){ init { var sexName:String = if(sex == 0) "公" else "母" context.toast("这只${name}是${sexName}的") } }
运行效果类似,但是代码更加的简洁了。
二、类的成员
2.1成员属性
创建一个新的类WildAnimal,然后在构造函数中放两个参数,代码如下:
class WildAnimal(name:String,sex:Int = 0) { }
然后我们再声明对应的属性字段,用于保存入参的数值,加入按照Java的编码思路,下面的代码应该是这样的。
class WildAnimal(name: String, sex: Int = 0) { var name: String // 表示动物名称可以修改 val sex: Int //表示动物性别不能修改 init { this.name = name this.sex = sex } }
这上面的写法从Java的角度来看倒是没有问题,但如果时Kotlin呢,代码冗余了,
(1)属性字段跟构造函数的入参,二者名称一样,变量类型也一样。
(2)初始化函数中的属性字段赋值,为了区别同名的属性和入参,特意给属性字段添加了this。
那么Kotlin如何精简这个类的代码呢?代码如下:
class WildAnimal(var name: String,val sex: Int = 0) { }
你没有看错,就是这样,接下来使用一下吧。
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* import java.lang.Exception import java.text.SimpleDateFormat import java.util.* class MainActivity : AppCompatActivity() { var animalName: String = "" var animalSex: Int = 0 var count: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { setAnimalInfo() var animal = when (count % 2) { 0 -> { WildAnimal(animalName) } else -> { WildAnimal(animalName, animalSex) } } count++ tv_result.text = "这头${animal.name}是${if (animal.sex == 0) "公" else "母"}的" } } fun setAnimalInfo() { animalName = "牛" animalSex = 1 } }
再看看Java代码中怎么做的
package com.llw.kotlinstart; public class WildAnimal { private String name; private String sex; public WildAnimal(String name, String sex) { this.name = name; this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
很熟悉吧,因为基本上每一个实体都离不开这一步,
对比一下:
(1)冗余的同名属性声明语句。
(2)冗余的同名属性赋值语句。
(3)冗余的属性获取方法与设置方法。
Kotlin的代码真的精简了很多,鸟枪换炮,
如果某个字段并非入参的同名属性,就需要在类内部显示声明该属性字段,例如,前面WildAnimal类的性别只是一个整型的类型字段,而界面上展示的是性别的中文名称,所以应当给该类补充一个性别名称的属性字段,这样每次访问sexName字段即可获得该动物的性别名称,新建一个名为WildAnimalMember的类,代码如下:
package com.llw.kotlinstart class WildAnimalMember (val name:String,val sex:Int = 0) { //非空的成员属性必须在声明时赋值或者在构造函数中赋值,否则编译器会报错 var sexName:String init { sexName = if(sex == 0) "公" else "母" } }
然后再看一下怎么调用这个类:
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_main.* import java.lang.Exception import java.text.SimpleDateFormat import java.util.* class MainActivity : AppCompatActivity() { var animalName: String = "" var animalSex: Int = 0 var count: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { setAnimalInfo() var animal = when (count % 2) { 0 -> { WildAnimalMember(animalName) } else -> { WildAnimalMember(animalName, animalSex) } } count++ tv_result.text = "这头${animal.name}是${animal.sexName}的" } } fun setAnimalInfo() { animalName = "牛" animalSex = 1 } }
2.2 成员方法
类的成员除了成员属性还有成员方法,在类内部定义成员方法的过程和普通函数定义比较类似。下面增加一个获取动物描述信息的成员方法getDesc(),新创建一个名为WildAnimalFunction的类
package com.llw.kotlinstart class WildAnimalFunction(var name: String, val sex: Int = 0) { var sexName: String init { sexName = if (sex == 0) "公" else "母" } fun getDesc(tag: String): String { return "欢迎来到$tag:这头${name}是${sexName}的" } }
然后我们在MainActivity.kt中调用这个类的方法
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.llw.kotlinstart.custom_class.WildAnimalFunction import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { var animalName: String = "" var animalSex: Int = 0 var count: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { setAnimalInfo() var animal = when(count%2){ 0 -> WildAnimalFunction( animalName ) else -> WildAnimalFunction( animalName, animalSex ) } tv_result.text = animal.getDesc("动物园") count++ } } fun setAnimalInfo() { animalName = "牛" animalSex = 1 } }