Kotlin基礎(chǔ)(二)類、對(duì)象和接口

修飾符

訪問修飾符
修飾符 相關(guān)成員 評(píng)注
final 不能被重寫 類中成員默認(rèn)使用
open 可被重寫 需要明確什么
abstract 必須被重寫 在抽象類中使用
override 重寫父類或接口中的成員 若沒有final聲明,重寫的成員默認(rèn)是open

Java中可以創(chuàng)建任意類的子類并重寫任意方法,除非顯示聲明final。對(duì)基類的修改會(huì)導(dǎo)致子類不正確的行為,即脆弱的基類問題。Effective Java建議“要么為繼承做好設(shè)計(jì)并記錄文檔,要么禁止。”Kotlin采用該思想哲學(xué),Java中類和方法默認(rèn)是open的,而Kotlin中類和方法默認(rèn)是final。

open class RichButtion:Clickable{
    //默認(rèn)是final不能被重寫
    fun disable(){}
    //open可重寫
    open fun animate(){}
    //override 方法默認(rèn)是open
    override fun click(){}
}

接口和抽象類默認(rèn)是open, 其抽象成員默認(rèn)是open

abstract class Animate{
    //默認(rèn)是open
    abstract fun animate()
    //非抽象方法默認(rèn)是final
    fun animateTwice(){}
}
可見性修飾符
修飾符 類成員 頂層聲明
public(默認(rèn)) 所有地方可見 所有地方可見
internal 模塊中可見 模塊中可見
protected 子類中可見 ——
private 類中可見 文件中可見

Java中默認(rèn)可見性——包私有,在kotlin中并沒有。Kotlin只把包作為命名空間里組織代碼的一種方式,并沒有將其用作可見性控制。作為替代方案,Koltin是使用新的修飾符internal,表示“只能在模塊內(nèi)可見?!?code>internal優(yōu)勢(shì)在于它對(duì)模塊實(shí)現(xiàn)細(xì)節(jié)提供真正的封裝。

接口

接口包含抽象方法的定義和非抽象方法的實(shí)現(xiàn),但是他們都不能包含任何狀態(tài)。

interface Clickable {
    //不支持backing-field,不能存儲(chǔ)值
    var clickable: Boolean
    //默認(rèn)open,可被重寫
    fun click()
    //默認(rèn)final,不能被重寫
    fun showOff() = println("I'm Clickable")
}

由于Koltin 1.0Java 6為目標(biāo)設(shè)計(jì),其并不支持接口中的默認(rèn)方法,因此會(huì)把每個(gè)默認(rèn)方法的接口編譯成一個(gè)普通接口和一個(gè)將方法體作為靜態(tài)函數(shù)的類的結(jié)合體,如上面的接口反編譯后看到:

public interface Clickable {
   boolean getClickable();
   void setClickable(boolean var1);
   void click();
   void showOff();
 
   public static final class DefaultImpls {
      public static void showOff(Clickable $this) {
         String var1 = "I'm Clickable";
         boolean var2 = false;
         System.out.println(var1);
      }
   }
}

構(gòu)造函數(shù)

Kotlin構(gòu)造函數(shù)相對(duì)于Java做了部分修改,區(qū)分主構(gòu)造函數(shù)從構(gòu)造函數(shù)。初始化塊中的代碼實(shí)際上會(huì)成為主構(gòu)造函數(shù)的?部分。委托給主構(gòu)造函數(shù)會(huì)作為次構(gòu)造函數(shù)的第?條語(yǔ)句,因此所有初始化塊中的代碼都會(huì)在次構(gòu)造函數(shù)體之前執(zhí)?。

class Person {
    init {
        println("Init block")
    }
    constructor(i: Int) {
        println("Constructor")
    }
}

在大多數(shù)場(chǎng)景中,類的構(gòu)造函數(shù)非常簡(jiǎn)明:要么沒有參數(shù),要么直接把參數(shù)于對(duì)應(yīng)的屬性關(guān)聯(lián)

class User(val nickname:String,val isSubscribed:Boolean=false)

如果類有主構(gòu)造函數(shù),每個(gè)從構(gòu)造函數(shù)需要委托主構(gòu)造函數(shù),可直接委托或者間接委托。

class User(val nickname: String) {
    var isSubscribed: Boolean?=null
    constructor(_nickname: String, _isSubscribed: Boolean) : this(_nickname) {
        this.isSubscribed = _isSubscribed
    }
}

如何該類有父類,應(yīng)該顯式的調(diào)用父類的構(gòu)造方法

//Clickable為接口,沒有構(gòu)造函數(shù)
class Buttion:Clickable{

}
//即便沒有任何參數(shù),也要顯示調(diào)用父類構(gòu)造函數(shù)
class RiseButton:Button(){
    
}
//如果有多級(jí)構(gòu)造函數(shù),可以super關(guān)鍵字調(diào)用父類構(gòu)造
class MyButton: View {
    constructor(ctx:Context):super(ctx)
    constructor(ctx: Context,attributes: AttributeSet?):super(ctx,attributes)
}
內(nèi)部類、嵌套類、密封類、數(shù)據(jù)類·
內(nèi)部類和嵌套類

Kotlin中嵌套類不能訪問外部類的實(shí)例,類似Java靜態(tài)內(nèi)部類;而Kotlin中的內(nèi)部類需要用inner關(guān)鍵字修飾才能訪問外部類的實(shí)例。

class Outer {
    private val bar: Int = 1
    //內(nèi)部類
    inner class Inner {
        fun foo() = bar
    }
}
class Outer2 {
    private val bar: Int = 1
    //嵌套類,不持有外部類的引用
    class Nested {
        fun foo() = 2
    }
}
val demo = Outer().Inner().foo() // == 1
val demo2 = Outer2.Nested().foo() // == 2
密封類

密封類?來表?受限的類繼承結(jié)構(gòu):當(dāng)?個(gè)值為有限集中的類型、?不能有任何其他類型時(shí)。在某種意義上,他們是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的,但每個(gè)枚舉常量只存在?個(gè)實(shí)例,?密封類的?個(gè)?類可以有可包含狀態(tài)的多個(gè)實(shí)例。

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

?個(gè)密封類是??抽象的,它不能直接實(shí)例化并可以有抽象(abstract)成員。
密封類不允許有?-private 構(gòu)造函數(shù)(其構(gòu)造函數(shù)默認(rèn)為 private)。
請(qǐng)注意,擴(kuò)展密封類?類的類(間接繼承者)可以放在任何位置,??需在同?個(gè)?件中。

數(shù)據(jù)類

創(chuàng)建?些只保存數(shù)據(jù)的類。 在這些類中,?些標(biāo)準(zhǔn)函數(shù)往往是從數(shù)據(jù)機(jī)械推導(dǎo)?來的。在
Kotlin 中,這叫做 數(shù)據(jù)類 并標(biāo)記為 data :

data class User(val name: String, val age: Int)

編譯器?動(dòng)從主構(gòu)造函數(shù)中聲明的所有屬性導(dǎo)出以下成員:

  • equals() / hashCode() 對(duì);
  • toString() 格式是 "User(name=John, age=42)" ;
  • componentN()函數(shù) 按聲明順序?qū)?yīng)于所有屬性;
  • copy() 函數(shù)。

為了確保?成的代碼的?致性以及有意義的?為,數(shù)據(jù)類必須滿?以下要求:

  • 主構(gòu)造函數(shù)需要?少有?個(gè)參數(shù);
  • 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var ;
  • 數(shù)據(jù)類不能是抽象、開放、密封或者內(nèi)部的;
屬性與字段

聲明一個(gè)屬性的完整語(yǔ)法為:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]

其初始器(initializer)、getter 和 setter 都是可選.

一個(gè)只讀屬性的語(yǔ)法和一個(gè)可變的屬性的語(yǔ)法有兩方面的不同:

  • 只讀屬性用val,而可變屬性用var聲明
  • 只讀屬性不允許有setter方法

默認(rèn)的屬性的聲明為:

var name: String = "Kotlin"
        get() = field
        set(value) {
            field = value
        }
Object關(guān)鍵字

Object關(guān)鍵字定義一個(gè)類并同時(shí)創(chuàng)建一個(gè)實(shí)體:

  • 對(duì)象聲明:定義單例的方式
  • 伴生對(duì)象:可持有工廠方法及其他與類相關(guān)
  • 對(duì)象表達(dá)式:代替Java的匿名內(nèi)部類

對(duì)象表達(dá)式和對(duì)象聲明之間有?個(gè)重要的語(yǔ)義差別:

  • 對(duì)象表達(dá)式是在使?他們的地??即執(zhí)?(及初始化)的;
  • 對(duì)象聲明是在第?次被訪問到時(shí)延遲初始 化的;
  • 伴?對(duì)象的初始化是在相應(yīng)的類被加載(解析)時(shí),與 Java 靜態(tài)初始化器的語(yǔ)義相匹配。
對(duì)象聲明

對(duì)象聲明將類的聲明與該類的單一實(shí)例聲明結(jié)合在一起。與普通類的實(shí)例不同,對(duì)象聲明在定義的時(shí)候就創(chuàng)建了實(shí)例。

object PayRoll {
    val allEmployees = arrayListOf<Person>()

    fun calculateSalary(){  
    }
}

可以反編譯看到:

對(duì)象聲明被編譯成通過靜態(tài)字段來持有它的單一實(shí)例的類,字段名始終為INSTANCE

public final class PayRoll {
   @NotNull
   private static final ArrayList allEmployees;
   public static final PayRoll INSTANCE;

   @NotNull
   public final ArrayList getAllEmployees() {
      return allEmployees;
   }

   public final void calculateSalary() {
   }
    //構(gòu)造函數(shù)私有
   private PayRoll() {
   }

   static {
      PayRoll var0 = new PayRoll();
       //靜態(tài)代碼塊初始化化實(shí)例對(duì)象
      INSTANCE = var0;
      boolean var1 = false;
      allEmployees = new ArrayList();
   }
}
伴生對(duì)象

Javastatic關(guān)鍵字并不是kotlin的一部分,作為替代,kotlin依賴包級(jí)別的函數(shù)和對(duì)象聲明,但是頂層函數(shù)不能訪問類的私有成員, 需要寫一個(gè)沒有類實(shí)例情況下調(diào)用但需要訪問類內(nèi)部的函數(shù),可以將其寫為類中的對(duì)象聲明的成員。

fun getFacebookName(accountId: Int) = "fb:$accountId"

class User private constructor(val nickname: String) {
    companion object {
        fun newSubscribingUser(email: String) =
            User(email.substringBefore('@'))

        fun newFacebookUser(accountId: Int) =
            User(getFacebookName(accountId))
    }
}

fun main(args: Array<String>) {
    val subscribingUser = User.newSubscribingUser("bob@gmail.com")
    val facebookUser = User.newFacebookUser(4)
    println(subscribingUser.nickname)
}

伴生對(duì)象作為普通對(duì)象,一樣可以實(shí)現(xiàn)接口和擴(kuò)展函數(shù)和屬性

data class Person(val name: String) {
    object NameComparator : Comparator<Person> {
        override fun compare(p1: Person, p2: Person): Int =
            p1.name.compareTo(p2.name)
    }
}

class Person(val firstname:String,val lastname:String){
    companion object{
        //...可空,但不能省略
    }
}
fun Person.Companion.fromJson(json:String):String{
    return json.substring(4)
}
對(duì)象表達(dá)式

object不僅可用來聲明單例對(duì)象,還可以聲明匿名對(duì)象,替代java內(nèi)部類的用法

fab.setOnClickListener(
     object : View.OnClickListener {
        override fun onClick(view: View?) {
         //....
        }
      })

當(dāng)然,也可以將其存儲(chǔ)到一個(gè)變量中:

val listener = object : View.OnClickListener {
    override fun onClick(p0: View?) {
       //....
    }
}

Java匿名內(nèi)部類只能擴(kuò)展一類或者實(shí)現(xiàn)一個(gè)接口,kotlin的匿名對(duì)象可以實(shí)現(xiàn)多個(gè)接口或者實(shí)現(xiàn)不同的接口。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容