一、類的構造
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
}
}
2.3 伴生對象
伴生對象這個是在Kotlin中有的,Java中沒有,什麼是伴生對象呢,你可以把它了解為“影子”,把類當做一個人,這個人可以有很多房子,但是人隻有一個,影子也隻有一個。你也可以把伴生對象替換掉靜态成員的作用,但它比靜态成員的功能要強大。我們之前通過性别類型來獲得性别名稱,那麼反推呢,我們使用伴生對象來實作這一功能,新建立一個名為WildAnimalCompanion的類
package com.llw.kotlinstart.custom_class
class WildAnimalCompanion (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}的"
}
//關鍵字companion表示伴随,object表示對象,WildAnimal表示伴生對象的名稱
companion object WildAnimal{
fun judgeSex(sexName:String):Int{
var sex:Int = when (sexName){
"公","雄" -> 0
"母","雌" -> 1
else -> -1
}
return sex
}
}
}
代碼應該沒有什麼好說的,一目了然,關鍵定義這個伴生對象和使用它,接下來看怎麼使用
代碼如下:
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.WildAnimalCompanion
import com.llw.kotlinstart.custom_class.WildAnimalFunction
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sexArray:Array<String> = arrayOf("公","母","雄","雌")
btn_test.setOnClickListener {
var sexName:String = sexArray[count++%4]
//伴生對象的WildAnimal名稱可以省略掉
//tv_result.text = "\"$sexName\"對應的類型是${WildAnimalCompanion.WildAnimal.judgeSex(sexName)}"
tv_result.text = "\"$sexName\"對應的類型是${WildAnimalCompanion.judgeSex(sexName)}"//雙引号 要加反斜杠 如同這樣 \"\"
}
}
}
這個是灰色的,我們省略掉也是可以的
有四種結果,我隻放兩個圖
2.4 靜态屬性
之前我們的伴生對象可以實作靜态函數,同樣也能實作靜态屬性,隻要在伴生對象内部增加幾個字段定義就行了,之前是用0和1表示動物的雄雌,接下來用整型常量MALE表示雄性的0,整型常量FEMALE表示雌性的1,建立一個名為WildAnimalConstant的類,代碼如下,
package com.llw.kotlinstart.custom_class
class WildAnimalConstant(var name: String, val sex: Int = MALE) {
var sexName: String
init {
sexName = if (sex == MALE) "公" else "母"
}
fun getDesc(tag: String): String {
return "歡迎來到$tag: 這隻${name}是${sexName}的。"
}
companion object WildAnimal {
//靜态常量的值是不可變得,是以要使用關鍵字val修飾
val MALE = 0
val FEMALE = 1
val UNKOWN = -1
fun judgeSex(sexName:String):Int{
var sex:Int = when(sexName){
"公","雄" -> MALE
"母","雌" -> FEMALE
else -> UNKOWN
}
return sex
}
}
}
然後再進行調用
val sexArray:Array<String> = arrayOf("公","母","雄","雌")
btn_test.setOnClickListener {
var sexName:String = sexArray[count++%4]
//伴生對象的WildAnimal名稱可以省略掉
//tv_result.text = "\"$sexName\"對應的類型是${WildAnimalCompanion.WildAnimal.judgeSex(sexName)}"
tv_result.text = "\"$sexName\"對應的類型是${WildAnimalConstant.judgeSex(sexName)}"//雙引号 要加反斜杠 如同這樣 \"\"
}
改一下類名就可以了,運作效果和之前的是一樣的,隻不過程式裡面就可以通過WildAnimalConstant.MALE和WildAnimalConstant.FEMALE來判斷公母了,不像之前通過0和1這種毫無意義的值來判斷。
Kotlin的類成員分為執行個體成員與靜态成員,執行個體成員包括成員屬性和成員方法,其中與入參同名的成員屬性可以在構造函數中直接聲明,外部必須通過類的執行個體才能通路類的成員屬性和成員方法,類的靜态成員包括靜态屬性與靜态方法,它們都在類的伴生對象中定義,外部可以通過類名直接通路該類的靜态成員。
三、類的繼承
我們一開始就提到了類的繼承,如class MainActivity : AppCompatActivity(),這和Java是不一樣的,那麼Kotlin怎麼定義基類并由基類派生出子類呢?
3.1 開放性修飾符
之前我們寫了好多個WildAnimal類,Java和Kotlin關于類的繼承還有差別,比如Java中預設每個類都能被繼承,除非加了final關鍵字,而Kotlin剛好相反,它預設每個類都不能被繼承(PS:這不是搞我心态嗎!!!),這個時候要想讓一個類成為基類,就要把該類開放出來,于是就用到了開放性修飾符open(PS:敲黑闆,重點來了,哪個),示範代碼如下:
open class Animal(var name:String,val sex:Int = 0){
}
在Java中有幾個熟悉的關鍵字,public、protected、private,分别表示公開、隻對子類開放、私有。那麼在Kotlin中也給出了4個開放性修飾符。
開放性修飾符 | 說明 |
public | 對所有人開放。Kotlin的類、函數、變量不加開放性修飾符的話,預設就是public類型 |
internal | 隻對本子產品内部開放,這是Kotlin新增的關鍵字。 |
protected | 隻對自己和子類開放 |
private | 隻對自己開放、即私有 |
注意到這幾個修飾符與open一樣都加在類和函數前面,并且都包含“開放”的意思,乍看起來還真有點迷,到底open跟這4個開放性修飾符是什麼關系呢?其實很簡單,open不控制某個對象的通路權限,隻決定該對象能否繁衍開來,說白了,就是公告這個叼毛有沒有資格繁衍下一代,隻有頭戴open帽子的類,才允許作為基類派生出子類來,而頭戴open帽子的函數,表示它允許在子類中進行重寫,如果沒戴open帽子,該類就隻好打光棍了,函數沒戴open帽子的話,類的孩子就沒有辦法修改它。
至于那4個開放性修飾符,則是用來限定允許通路某對象的外部範圍,通俗地說,就是哪裡的帥哥可以跟這個美女搞對象,頭戴public的,表示全世界的帥哥都能跟她處對象,頭戴internal的,表示隻有本國的帥哥可以,頭戴protected的,表示自由本機關以及下屬機關的可以,頭戴private,表示自己本機關可以。
3.2 普通類繼承
建立一個Poultry類,代碼如下:
package com.llw.kotlinstart.custom_class
//Kotlin的類型預設是不能繼承的(即 final類型),如果需要繼承某類,該父類就應當聲明open類型
open class Poultry (var name:String,val sex:Int = MALE){
//變量、方法、類預設都是public,是以一般都把public省略掉了
var sexName:String
init {
sexName = getSexName(sex)
}
//私有的方法既不能被外部通路,也不能被子類繼承,是以open與private不能共存,否則編譯器會報錯
open protected fun getSexName(sex:Int):String{
return if(sex == MALE) "公" else "母"
}
fun getDesc(tag:String):String{
return "歡迎來到$tag: 這隻${name}是${sexName}的。"
}
companion object BirdStatic{
val MALE = 0
val FEMALE = 1
val UNKOWN = -1
fun judgeSex(sexName:String):Int {
var sex:Int = when (sexName){
"公","雄" -> MALE
"母","雌" -> FEMALE
else -> UNKOWN
}
return sex
}
}
}
然後我們再建立一個名為Pig的子類,繼承Poultry,代碼如下:
package com.llw.kotlinstart.custom_class
//注意父類Bird已經在構造函數聲明了屬性,故而子類Pig無須重複聲明屬性
//也就是說,子類的構造函數在輸入參數前面不需要再加val和var
class Pig(name:String="豬",sex: Int= MALE) : Poultry(name, sex){
}
然後在Activity中調用:
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var sex:Int
btn_test.setOnClickListener {
var sexPoultry = if(count++%3==0) Poultry.MALE else Poultry.FEMALE
var pig = Pig(sex = sexPoultry)
tv_result.text = pig.getDesc("高老莊")
}
}
}
運作效果圖如下:
然後再來定義一個小狗類 Dog的代碼
package com.llw.kotlinstart.custom_class
class Dog (name:String = "哈士奇",sex:Int = MALE):Poultry(name, sex){
override public fun getSexName(sex: Int): String {
return if(sex == MALE) "雄" else "雌"
}
}
然後在Activity中調用
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var sex:Int
btn_test.setOnClickListener {
var sexPoultry = if(count++%3==0) Poultry.MALE else Poultry.FEMALE
var dog = Dog(sex = sexPoultry)
tv_result.text = dog.getDesc("狗狗流浪記")
}
}
}
運作效果圖如下:
3.3 抽象類
Kotlin中也存在與Java類似的抽象類,抽象類之是以存在,是因為其内部擁有被關鍵字abstract修飾的抽象方法。抽象方法沒有具體的函數體,故而外部無法直接聲明抽象類的執行個體,隻有在子類繼承時重寫方法,方可使用該子類正常聲明對象執行個體。
For Example ,雞屬于鳥類,可是公雞和母雞的叫聲是不一樣的,是以雞這個類的叫喚方法“callOut”發出什麼聲音并不确定,隻能先聲明為抽象方法,連帶着雞類“Chicken”也變成抽象類了。
現在定義一個抽象的Chicken類,代碼如下:
package com.llw.kotlinstart.custom_class
//子類的構造函數,原來的輸入參數不用加var和val,新增的輸入參數必須加var或者val
//因為抽象類不能直接使用,是以構造函數不必預設參數指派
abstract class Chicken (name:String,sex:Int,var voice:String):Poultry(name, sex){
val numberArray:Array<String> = arrayOf("一","二","三","四","五","六","七")
//抽象方法必須在子類進行重寫,是以可以省略關鍵字open,因為abstract方法預設就是open類型
//open abstract fun callOut(times:Int):String
abstract fun callOut(times:Int):String
}
然後我們從Chicken類派生出公雞類Cock,指定攻擊的叫聲為“喔喔喔”,同時還要重寫callOut方法,明确公雞的叫喚行為。具體代碼如下:
package com.llw.kotlinstart.custom_class
class Cock(name:String="雞",sex:Int = Poultry.MALE,voice:String="喔喔喔"): Chicken(name, sex, voice) {
override fun callOut(times: Int): String {
var count = when {
//when語句判斷大于和小于時,要把完整的判斷條件寫到每個分支中
times <=0 ->0
times >=7 -> 6
else -> times
}
return "$sexName$name${voice}叫了${numberArray[count]}聲,這是在報曉"
}
}
再派生出公雞類Hen,指定攻擊的叫聲為“咯咯咯”,再重寫callOut方法
package com.llw.kotlinstart.custom_class
class Hen(name:String="雞",sex:Int = Poultry.FEMALE,voice:String="咯咯咯"): Chicken(name, sex, voice) {
override fun callOut(times: Int): String {
var count = when {
//when語句判斷大于和小于時,要把完整的判斷條件寫到每個分支中
times <=0 ->0
times >=7 -> 6
else -> times
}
return "$sexName$name${voice}叫了${numberArray[count]}聲,這是在下蛋"
}
}
然後在Activity中調用不同的類
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
//調用公雞類
tv_result.text = Cock().callOut(count++ % 7)
}
}
}
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
//調用母雞類
tv_result.text = Hen().callOut(count++%7)
}
}
}
運作結果
3.4 接口
Kotlin的接口與Java一樣是為了間接實作多重繼承,由于直接繼承多個類可能存在方法沖突等問題,是以Kotlin在編譯階段就不允許某個類同
時繼承多個基類,否則會報錯,于是,隻能通過接口定義幾個抽象方法,然後在實作該接口的具體類中重寫這幾個方法,進而間接實作類似C++多重繼承的功能。
在Kotlin中定義接口需要注意以下幾點:
(1)接口不能定義構造函數,否則編譯器會報錯"An interface may not have a constructor"。
(2)接口的内部方法通常要被實作它的類進行重寫,是以這些方法預設為抽象類型。
(3)與Java不同的是,Kotlin允許在接口内部實作某個方法,而Java接口的所有内部方法都必須是抽象方法。
Android開發中最常見的接口是控件的點選監聽器View.OnClickListener,它内部的點選動作onClick,類似的還有長按監聽器、選中監聽器等等,它們無一例外都定義了某種行為的事件處理過程。我們可以用一個列子來表達這些,比如鳥兒的飛翔、遊泳、奔跑等,下面定義一個行為接口 Behavior。
package com.llw.kotlinstart.custom_class
//Kotlin與Java一樣不允許多重繼承,即不能同時繼承兩個及兩個以上類
//否則編譯器報錯"Only one class may appear in a supertype list"
//是以仍然需要接口interface 來間接實作多重繼承的功能
//接口不能帶構造函數(那樣就變成一個類了),否則編譯器報錯"An interface may not have a constructor"
interface Behavior {
//接口内部的方法預設就是抽象的,是以不加abstract 也可以,當然open也可以不加
open abstract fun fly():String
//比如下面這個swim方法就沒有加關鍵字abstract ,也無須在此實作方法
fun swim():String
//Kotlin的接口與Java的差別在于,Kotlin接口内部允許實作方法
//此時該方法不是抽象方法,就不能加上abstract
//不過該方法依然是open類型,接口内部的所有方法都預設是open類型
fun run():String{
return "大多數鳥兒跑得并不像樣,隻有鴕鳥、鸸鹋等少數鳥類才擅長奔跑。"
}
//Kotlin的接口允許聲明抽象屬性,實作該接口的類必須重載該屬性
//與接口内部方法一樣,抽象屬性前面的open和abstract 也可以省略掉
//open abstract var skiledSporte:String
var skiledSporte:String
}
在其他類實作這個接口時,跟類繼承一樣把接口名稱放在冒号後面,也就是說,Java的extends和implement這兩個關鍵字在Kotlin中都被冒号取代了。然後就想重寫抽象類的抽象方法一樣重寫接口的抽象方法,建立一個名為Goose的類,代碼如下:
package com.llw.kotlinstart.custom_class
class Goose(name: String = "鵝", sex: Int = Poultry.MALE) : Poultry(name, sex), Behavior {
override fun fly(): String {
return "鵝能飛一點點,但是飛不高,也飛不遠"
}
override fun swim(): String {
return "鵝是會遊泳的"
}
//因為接口已經實作了run方法,是以此處可以不用實作該方法,當你也可以實作它
override fun run(): String {
//super用來調用父類的屬性或方法,由于Kotlin的接口允許實作方法,是以super所指的對象也可以是interface
return super.run()
}
//重載了來自接口的抽象屬性
override var skiledSporte: String = "遊泳"
}
然後在Activity中使用
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
tv_result.text = when(count++%3){
0 -> Goose().fly()
1 -> Goose().swim()
else -> Goose().run()
}
}
}
}
運作效果如下圖:
調用fly方法
調用swim方法
調用run方法
3.5 接口代理
通過接口固然完成了相應行為,但是鳥類這個家族非常龐大,如果每種鳥都實作Behavior接口,工作量是非常大的,其實鳥類的行為并不多,可以分類為飛禽、水禽、走禽三個行為類
下面是飛禽的行為類代碼示例:
package com.llw.kotlinstart.custom_class
class BehaviorFly : Behavior {
override fun fly(): String {
return "翺翔天空"
}
override fun swim(): String {
return "落水鳳凰不如雞"
}
override fun run(): String {
return "能飛幹嘛還要走"
}
override var skiledSporte: String = "飛翔"
}
下面是水禽
package com.llw.kotlinstart.custom_class
class BehaviorSwim : Behavior {
override fun fly(): String {
return "看情況,大雁能展翅高飛,企鵝卻欲飛還休"
}
override fun swim(): String {
return "怡然戲水"
}
override fun run(): String {
return "趕鴨子上樹"
}
override var skiledSporte: String = "遊泳"
}
下面是走禽
package com.llw.kotlinstart.custom_class
class BehaviorRun : Behavior {
override fun fly(): String {
return "飛不起來"
}
override fun swim(): String {
return "望洋興歎"
}
override fun run(): String {
return super.run()
}
override var skiledSporte: String = "奔跑"
}
然後定義一個引用代理類的野禽基類,通過關鍵字by表示接口将由入參中的代理類實作,野禽基類WildFowl代碼如下:
package com.llw.kotlinstart.custom_class
//隻有接口才能使用關鍵字by進行代理操作
class WildFowl (name:String,sex:Int=MALE,behavior: Behavior):Poultry(name,sex),Behavior by behavior{
}
然後在Activity中使用
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
var fowl = when (count++ % 6) {
//把代理類作為輸入參數來建立執行個體
0 -> WildFowl("老鷹", Poultry.MALE, BehaviorFly())
//由于sex字段是個預設參數,是以可通過命名參數給behavior指派
1 -> WildFowl("鳳凰", behavior = BehaviorFly())
2 -> WildFowl("大雁", Poultry.FEMALE, BehaviorSwim())
3 -> WildFowl("企鵝", behavior = BehaviorSwim())
4 -> WildFowl("鴕鳥", Poultry.MALE, BehaviorRun())
else -> WildFowl("鹂鵑", behavior = BehaviorRun())
}
var action = when (count % 11) {
in 0..3 -> fowl.fly()
4, 7, 10 -> fowl.swim()
else -> fowl.run()
}
tv_result.text = "${fowl.name}: $action"
}
}
}
運作效果如下:
老鷹的飛翔行為:
鳳凰的遊泳行為:
大雁的飛翔行為:
企鵝的遊泳行為:
鴕鳥的飛翔行為:
鹂鵑的奔跑行為
通過一頓操作之後,總結出Kotlin的類繼承與Java相比有所不同,主要展現在以下幾點:
(1)Kotlin的類預設不可被繼承,若需繼承,則要添加open聲明,而Java的類預設是允許被繼承的,隻有添加final聲明才表示不能為繼承。
(2)Kotlin除了正常的三個開放性修飾符public、protected、private外,另外增加了修飾符internal,表示隻對本子產品開放。
(3)Java的類繼承關鍵字extends以及接口實作關鍵字implement在Kotlin中都被冒号所取代。
(4)Kotlin允許在接口内部實作某個方法,而Java接口的内部方法隻能是抽象方法。
(5)Kotlin引入了接口代理(類代理)的概念,而Java不存在代理的寫法。
四、特殊類
4.1 嵌套類
一個類可以在單獨的代碼檔案中定義,也可以在另一個類内部定義,後一種情況叫作嵌套類,即A類嵌套在B類之中,聽起來和Java的嵌套類是一樣的,但其實有所差别,Java的嵌套類允許通路外部類的成員,而Kotlin的嵌套類不允許通路外部類的成員,強行通路則會報錯。
下面是示例代碼:
package com.llw.kotlinstart.custom_class
class Tree(var treeName: String) {
//在類内部再定義一個類,這個新類稱作嵌套類
class Flower(var flowerName: String) {
fun getName(): String {
return "這是一朵$flowerName"
//普通的嵌套類不能通路外部類的成員,如treeName
//否則編譯器會報錯:" Unresolved reference: *** "
//return "這是${treeName}上的一朵$flowerName"
}
}
}
調用嵌套類時,得在嵌套類的類名前面添加外部類的類名,相當于把這個嵌套類作為外部類的靜态對象使用,在Activity中調用代碼如下:
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
//使用嵌套類時,隻能引用外部類的類名,不能調用外部類的構造函數
val peachBlossom = Tree.Flower("桃花")
tv_result.text = peachBlossom.getName()
}
}
}
因為嵌套類無法通路外部類的成員,是以其方法隻能傳回自身的資訊,運作效果圖如下:
4.2 内部類
Kotlin限制了嵌套類不能通路外部類的成員,那還有什麼方法可以實作此功能呢?針對該問題,Kotlin另外增加了關鍵字inner表示内部,把inner加在嵌套類的class前面,然後嵌套類就變成了内部類,是以Kotlin的内部類就相當于Java的嵌套類,而Kotlin的嵌套類則是加了通路限制的内部類。還是在之前的嵌套類Tree中,加一個内部類Fruit ,示例代碼如下,
package com.llw.kotlinstart.custom_class
class Tree(var treeName: String) {
//在類内部再定義一個類,這個新類稱作嵌套類
class Flower(var flowerName: String) {
fun getName(): String {
return "這是一朵$flowerName"
//普通的嵌套類不能通路外部類的成員,如treeName
//否則編譯器會報錯:" Unresolved reference: *** "
//return "這是${treeName}上的一朵$flowerName"
}
}
//嵌套類加上inner字首,就變成内部類
inner class Fruit(var fruitName:String){
fun getName():String{
//隻有聲明為内部類(添加了關鍵字inner,才能通路内外部類的成員)
return "這是${treeName}長出來的$fruitName"
}
}
}
然後在Activity中調用
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
//使用嵌套類時,隻能引用外部類的類名,不能調用外部類的構造函數
val peach = Tree("桃樹").Fruit("桃子")
tv_result.text = peach.getName()
}
}
}
運作效果圖:
4.3 枚舉類
Java有一種枚舉類型,它采用關鍵字enum來表達,其内部定義了一系列名稱,通過有意義的名字比0、1、2這些數字能夠更有效地表達語義,下面是一個Java定義枚舉類型的代碼示例:
package com.llw.kotlinstart.custom_class;
enum Season {SPRING, SUMMER, AUTUMN, WINTER}
再來看Kotlin的枚舉類
package com.llw.kotlinstart.custom_class
enum class SeasonType {
SPRING, SUMMER, AUTUMN, WINTER
}
雖然看上去隻比Java的枚舉類型多了一個class,但是Kotlin中枚舉類内部的枚舉變量除了可以直接拿來指派之外,還可以通過枚舉值的幾個屬性獲得對應的資訊,例如ordinal屬性用于擷取該枚舉值的序号,name屬性用于擷取該枚舉值的名稱。枚舉變量本質上還是該類的一個執行個體,是以如果枚舉類存在構造函數,枚舉變量也必須調用對應的構造函數,這樣做的好處是,每一個枚舉值不但攜帶唯一的名稱,還可以擁有更加個性化的特征描述。下面建立一個枚舉類來說明,代碼如下:
package com.llw.kotlinstart.custom_class
enum class SeasonName(val seasonName: String) {
SPRING("春天"),
SUMMER("夏天"),
AUTUMN("秋天"),
WINTER("冬天")
}
然後在Activity中使用枚舉類SeasonType和SeasonName:
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var count: Int = 0
btn_test.setOnClickListener {
if (count % 2 == 0) {
//ordinal表示枚舉類型的序号,name表示枚舉類型的名稱
tv_result.text = when (count++ % 4) {
SeasonType.SPRING.ordinal -> SeasonType.SPRING.name
SeasonType.SUMMER.ordinal -> SeasonType.SUMMER.name
SeasonType.AUTUMN.ordinal -> SeasonType.AUTUMN.name
SeasonType.WINTER.ordinal -> SeasonType.WINTER.name
else -> "未知"
}
} else {
tv_result.text = when(count++ % 4){
//使用自定義屬性seasonName表示個性化的描述
SeasonName.SPRING.ordinal -> SeasonName.SPRING.seasonName
SeasonName.SUMMER.ordinal -> SeasonName.SUMMER.seasonName
SeasonName.AUTUMN.ordinal -> SeasonName.AUTUMN.seasonName
SeasonName.WINTER.ordinal -> SeasonName.WINTER.seasonName
else -> "未知"
//枚舉類的構造函數是給枚舉類型使用的,外部不能直接調用枚舉類的構造函數
}
}
}
}
}
4.4 密封類
為了解決枚舉值判斷的多餘分支問題,Kotlin提出了“密封類”得概念,密封類就像是一種更加嚴格的枚舉類,它内部有且僅有自身的執行個體對象,是以是一個有限的自身執行個體集合,或者說,密封類采用了嵌套類的手段,它的嵌套類全部由自身派生而來,定義密封類的時候,需要在該類的class前面加上關鍵字sealed作為标記。定義一個密封類,代碼如下:
package com.llw.kotlinstart.custom_class
sealed class SeasonSealed {
//密封類内部的每個嵌套類都必須繼承該類
class Spring (var name:String) : SeasonSealed()
class Summer (var name:String) : SeasonSealed()
class Autumn (var name:String) : SeasonSealed()
class Winter (var name:String) : SeasonSealed()
}
這樣外部使用when語句便無須指定else分支了。下面是Activity中使用代碼
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var count: Int = 0
btn_test.setOnClickListener {
var season = when(count++%4){
0 -> SeasonSealed.Spring("春天")
1 -> SeasonSealed.Summer("夏天")
2 -> SeasonSealed.Autumn("秋天")
else -> SeasonSealed.Winter("冬天")
}
//密封類是一種嚴格的枚舉類,它的值是一個有限的集合
//密封類確定條件分支覆寫了所有的枚舉類型,是以不再需要else分支
tv_result.text = when(season){
is SeasonSealed.Spring -> season.name
is SeasonSealed.Summer -> season.name
is SeasonSealed.Autumn -> season.name
is SeasonSealed.Winter -> season.name
}
}
}
}
4.5 資料類
在Android實際開發中,我們經常需要定義一些實體類來存放傳回的資料,在Java中一個資料類的通常我完成以下工作:
(1)定義實體類的每個字段,以及對字段進行初始指派的構造函數。
(2)定義每個字段的get/set方法
(3)再判斷兩個資料對象是都相等時,通常每個字段都要比較一遍。
(4)在複制資料對象時,如果想另外修改某幾個字段值,得再補充對應數量的指派語句。
(5)在調試程式時,為獲知資料對象裡儲存的字段值,得手工把每個字段值都列印出來。
這對于開發者來說無疑于一個繁瑣的工作,而Kotlin鑒于此,推出了名為“資料類”這樣的騷操作,其實說起來也比較簡單,資料類的定義僅僅隻要在class前面增加關鍵字data,并聲明擁有完整輸入參數的構造函數,即可無縫實作以下功能:
(1)自動聲明與構造函數入參同名的屬性字段。
(2)自動實作每個屬性字段的get/set方法。
(3)自動提供equals方法,用于比較兩個資料對象是否相等。
(4)自動提供copy方法,允許完整指派某個資料對象,也可在複制後單獨修改某幾個字段的值。
(5)自動提供toString方法,用于列印資料對象中儲存的所有字段值。
說的這麼叼,也不知道是不是真的,來定義一個試一下吧。
代碼如下:
package com.llw.kotlinstart.custom_class
//資料類必須有主構造函數,且至少有一個輸入參數
//并且要聲明與輸入參數同名的屬性,即輸入參數前面添加關鍵字val或者var
//資料類不能是基類也不能是子類,不能是抽象類,也不能是内部類,更不能密封類
//我就是我,是顔色不一樣的煙火
data class Plant(
var name: String,
var stem: String,
var leaf: String,
var flower: String,
var fruit: String,
var seed: String
) {
}
然後在Activity中調用一下吧
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var count: Int = 0
var lotus = Plant("蓮","蓮藕","蓮葉","蓮花","蓮蓬","蓮子")
//資料類的copy方法不帶參數,表示複制一模一樣的的對象
var lotus2 = lotus.copy()
btn_test.setOnClickListener {
lotus2 = when(count++%2){
//copy方法帶參數,表示指定參數另外指派
0 -> lotus.copy(flower = "荷花")
else -> lotus.copy(flower = "蓮花")
}
//資料類自帶equals方法,用于判斷兩個對象是否一樣
var result = if(lotus2.equals(lotus)) "相等" else "不等"
tv_result.text = "兩個植物的比較結果是${result}\n"+
"第一個植物的描述是${lotus.toString()}\n"+
"第二個植物的描述是${lotus2.toString()}"
}
}
}
上述代碼調用了Plant對象的copy、equals、toString等方法,然後看一下運作的效果
4.6 模闆類
模闆類的應用相當廣泛,Kotlin中保留了它,而且寫法與Java類似,一樣在類名後面補充形如“”或者“<A,B>”這樣的表達式,表示此處的參數類型待定,要等建立類執行個體時再确定具體的參數類型,舉個例子,森林裡有一條小河,小河的長度可能以數字形式輸入(包括Int、Long、Float、Double),還有可能以字元串形式輸入。針對于這個需求編寫名為River的模闆類,代碼如下:
package com.llw.kotlinstart.custom_class
//在類名後面添加"<T>",表示這是一個模闆類
class River<T> (var name:String,var length:T) {
fun getInfo():String{
var unit:String = when(length){
is String -> "米"
//Int、Long、Float、Double都是數字類型Number
is Number -> "m"
else -> ""
}
return "${name}的長度是$length$unit"
}
}
然後在Activity中調用和這個模闆類
package com.llw.kotlinstart
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var count: Int = 0
btn_test.setOnClickListener {
var river = when(count++%4){
//模闆類(泛型類)聲明對象時,要在模闆類的類名後面加上"<參數類型>"
0 -> River<Int>("小溪",100)
//如果編譯器根據輸入參數就能知曉參數類型,也可直接省略"<參數類型>"
1 -> River("瀑布",99.9f)
2 -> River<Double>("山澗",50.5)
//如果你已經是老手了,那麼怎麼舒服怎麼來,Kotlin的設計初衷就是偷懶
else -> River("大河","一千")
}
tv_result.text = river.getInfo()
}
}
}
運作效果圖如下:
小溪的長度
瀑布的長度:
山澗的長度:
大河的長度: