Kotlin學習筆記之 5 類和對象

首發于公眾號: DSGtalk1989

5.Kotlin 類和對象

  • 構造器

    kotlin中一個類只能有一個主構造器和一個或多個次構造器。主構造器可以直接跟在class定義的類名后面但是沒有方法體,如下:

    class Person constructor(s : String) {
    }
    //也可以寫成這樣,記得,沒有空格
    class Person(s : String){
    }
    //一旦構造函數存在修飾符或者是注解的情況下,我們就不能省去constructor的方法名
    class Child public constructor(s : String){
    }
    

    以上的都是主構造函數,次構造函數定義在類中,并且kotlin強制規定,次構造函數必須要調用主構造函數,如下:

    class Person constructor(s : String) {
          constructor(i : Int) : this("123"){
          }
    }
    
    //也可以沒有主構造器,如下的方式就是直接起的次構造器
    //只有這種情況下,次構造器不需要再調用主構造器,所以一般如下這種方式跟我們的java習慣比較像
    class Person{
         constructor(){
         }
    }
    
  • private,public,protected,internal

    前面三個大家比較熟悉了,在java中都有,但是internal是kotlin中才引入的,叫做模塊內可見,即同一個module中可見。

    我們分別來看下,用這四個修飾符來描述屬性所帶來的編譯區別。

    //kt
    private var a = "a"
    public var b = "b"
    protected var c = "c"
    internal var d = "d"
    
    
    //decompiled
    private String a;
     @NotNull
     private String b;
     @NotNull
     private String c;
     @NotNull
     private String d;
    
     @NotNull
     public final String getB() {
        return this.b;
     }
    
     public final void setB(@NotNull String var1) {
        Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
        this.b = var1;
     }
    
     @NotNull
     protected final String getC() {
        return this.c;
     }
    
     protected final void setC(@NotNull String var1) {
        Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
        this.c = var1;
     }
    
     @NotNull
     public final String getD$app_debug() {
        return this.d;
     }
    
     public final void setD$app_debug(@NotNull String var1) {
        Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
        this.d = var1;
     }
    
    

    總結一下,就是不管是哪一個修飾符,最終經過編譯之后生成的在java中的參數描述都是private的,修飾符真正造成的區別是在編譯了自后的getset的方法不同。

    private的話,就不會生成getset方法,因為對于這個參數來說,是外部不可訪問的。publicprotected就是相應的setget。而internal則是publicsetD$app_debuggetD$app_debug方法。我們可以認為這兩個方法,在model中都是可以被訪問的。

  • init關鍵字

    上面說到主構造器直接寫在類名之后是沒有方法體的,因此一旦我們想要在構造函數中做一些初始化的操作,就需要挪到init中實現了。

    class Person constructor(firstName: String) {
          init {
              println("FirstName is $firstName")
          }
    }
    
  • getter和setter

    跟java差的有點多,首先屬性定義前面說過了,kotlin中getter和setter直接定義在屬性下方,由于kotlin的本身屬性的直接訪問性,只要你創建的是public的屬性,都可以直接獲取到屬性值即get方法和修改屬性值即set方法。

    所以免去了為了fastjson而專門去寫的settergetter,但是一旦你需要在gettersetter時做一些其他的操作,我們就需要去顯示的寫出getset

    var sex = "boy"
          get() {
              return "girl"
          }
          set(value) {
              when {
                  value.contains("girl") -> field = "boy"
              }
          }
    
    var age = 16
          get() = field + 10
          private set(value) = action(value)
          
    fun action(int: Int) {
    
    }      
          
    

    getset可以直接包含方法體,也可以直接通過等號的方式鏈到單行表達式或者方法。
    即我們認為,一旦觸發取值和賦值的時候會做相應的操作。其中field就是指的他自己。

    >>注:field的重要性<<

    在kotlin中,我們定義了一個參數name然后,只要去調用他,比如parent.name或者說去對他進行賦值name = "Tony"最終都會被編譯成使用了getset方法,如下

     //Observer.kt
     fun main(args : Array<String>){
          var observer = Observer()
          observer.name = "Tony"
          var newName = observer.name
      }
      
      //Observer.decompiled.java
      public static final void main(@NotNull String[] args) {
        Intrinsics.checkParameterIsNotNull(args, "args");
        Observer observer = new Observer();
        //調用了set
        observer.setName("Tony");
        //調用了get
        String newName = observer.getName();
     }
    

    所以我們在類中定義屬性的settergetter的時候,如果直接操作屬性本身,就會出現死循環。這就是field的用途

    //kt
    var no: Int = 100
      get() = no
      set(value) {
          if (value < 10) {       // 如果傳入的值小于 10 返回該值
              no = value
          } else {
              no = -1         // 如果傳入的值大于等于 10 返回 -1
          }
    }
    
    //decompiled
    int no = 100;
      public int getNo() {
          return getNo();// Kotlin中的get() = no語句中出來了變量no,直接被編譯器理解成“調用getter方法”
      }
      
      public void setNo(int value) {
          if (value < 10) {
              setNo(value);// Kotlin中出現“no =”這樣的字樣,直接被編譯器理解成“這里要調用setter方法”
          } else {
              setNo(-1);// 在setter方法中調用setter方法,這是不正確的
          }
      }
    

    很顯然,造成了死循環。

  • lateinit關鍵字

    我們都知道kotlin中,在方法中定義屬性時,我們必須進行初始化的操作。而類中本身我們一開始并不知道他到底是什么,所以會有希望晚一點再初始化的需求,這里就可以使用lateinit關鍵字來描述,那我們就可以不用給出具體的初始化值,但是kotlin會要求你必須給出屬性的類型。

    lateinit var game : String
    

    那么這個game我們可以之后再對其賦值。

    從kotlin 1.2開始已經支持全局和局部變量都是用lateinit, 并且我們可以通過isInitialized來判斷是否已經初始化過

  • 抽象類

    我們默認定義的class都是final的,無法被繼承的。所以一旦需要這個class能夠被繼承,我們需要加上open關鍵字。如果這是個抽象類,那我們需要添加abstract關鍵字。一旦被abstract描述,就無需再加上open了。

    open class Person(){
    }
    
    abstract class Parent{
    }
    

    緊接著,另外幾種場景

    • 方法是否可以被重寫

      默認方法都是final的,如果需要讓方法可以被重寫,需要在方法前再加上open

      所有我們平時在java中寫的一個單純的類實際上轉換成kotlin是如下這個樣子的:

      open class Person constructor(s: String) {
              open fun getName(){}
      }
      

      同樣的abstract也是這個意思,加載class前面只是形容類,跟方法和屬性什么的一點關系都沒有

    • 抽象屬性

      這是一個比較新的東西,因為java不支持抽象屬性。就是說,你要是繼承我,你就必須要初始化我所要求初始化的屬性。

      abstract class Parent(ame : String){
             abstract var ame : String
      }
      
      //兩種集成方式,一種是直接在構造函數中對抽象屬性進行復寫
      //由于父類構造需要傳一個字符串,所以在繼承時也需要直接傳入,此處傳入的是Child1自己的構造參數s
      class Child1 constructor(s: String, override var ame: String) : Parent(s) {
      }
      //一種是在類中對屬性進行復寫
      class Child2  constructor(s: String) : Parent(s) {
             override lateinit var ame: String
       }
       
       //如果子類沒有主構造函數,也可以通過次構造函數調用`super`方法實現
       class Child: Parent {
             constructor() : super("s")
             override lateinit var ame: String
         }
      
  • 嵌套類

    直接在class內部定義class,基本和java差不多

    class Outer {                  // 外部類
        private val bar: Int = 1
        class Nested {             // 嵌套類
            fun foo() = 2
        }
    }
    
    fun main(args: Array<String>) {
        val demo = Outer.Nested().foo() // 調用格式:外部類.嵌套類.嵌套類方法/屬性
        println(demo)    // == 2
    }
    
  • 內部類

    在剛才嵌套類的基礎上加上inner的關鍵字申明。

    class Outer {
         private val bar: Int = 1
         var v = "成員屬性"
         /**嵌套內部類**/
         inner class Inner {
             fun foo() = bar  // 訪問外部類成員
             fun innerTest() {
                 var o = this@Outer //獲取外部類的成員變量
                 println("內部類可以引用外部類的成員,例如:" + o.v)
             }
         }
     }
    

    唯一的區別在于內部類持有了外部類的引用,可以通過@外部類名的方式,來訪問外部類的成員變量。

  • 內部類和嵌套類的區別

    我們來看如下兩個嵌套類和內部類的以及讓門各自編譯成class文件的例子

    //Out.kt
    class Out{
         class Inner{
     
         }
     }
     
     //Out.decompiled.java
     public final class Out {
        public static final class Inner {
        }
     }
     
     //Out.kt
    class Out{
         inner class Inner{
     
         }
     }
     
     
     //Out.decompiled.java
     public final class Out {
        public final class Inner {
        }
     }
    

    已經很明顯了,inner之所以持有外部的引用,是因為他不是static的。也就是說kotlin的class默認就是static final的。

    調用嵌套類的方式與調用內部類的方式差別也只是一個括號而已

    fun main(args : Array<String>){
        //內部類調用
         Out().Inner().method()
         //嵌套類的調用
         Out1.Inner().method()
     }
    

    其實比較容易理解,嵌套類是static的直接可以通過類名來進行訪問嵌套類。

  • 匿名內部類

    一般用在接口層面的很多,我們通常知道的是傳參是個接口,方法中調用了接口方法的形式,如下:

    class Observer{
         fun getIt(listener: Listener){
             listener.onClick()
         }
     }
     
     interface Listener{
         fun onClick()
     }
     
     fun main(args : Array<String>){
         var observer = Observer()
         //注意,此處的object是kotlin獨有的關鍵字
         //不是隨便寫寫的,匿名內部類必須通過這個關鍵字來申明
         observer.getIt(object : Listener{
             override fun onClick() {
                 TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
             }
         })
     }
    

    通常我們也可以直接使用接口名 + lambda表達式的方式來生成匿名內部類,但條件是這個接口必須是函數式java接口,即只有一個抽象方法的java文件中定義的接口。

    比如我們基本碰到的所有的什么OnclickListener等等

    tv_case_id.setOnClickListener { View.OnClickListener{
    
     } }
    

    不過kotlin中定義的接口,我們就必須通過object的方式去實現了

    同時匿名內部類我們可以單獨的拿出來進行定義,實際上我們可以把object :理解成一個匿名的內部類實現了一個接口,也就是說我們還可以實現多個接口,比如:

    open class A(x: Int) {
         public open val y: Int = x
     }
     
     interface B { …… }
     
     val ab: A = object : A(1), B {
         override val y = 15
     }
    

    通常我們在java中是無法做到匿名內部類實現多個接口的,因為我們只能new一個接口出來。

更甚者說,我們很多時候甚至不需要這個object去實現或者是繼承什么,我們可以直接搞一個object出來

fun foo() {
     val adHoc = object {
         var x: Int = 0
         var y: Int = 0
     }
     print(adHoc.x + adHoc.y)
 }
  • 匿名對象最為函數的返回類型

    我們上面是將匿名對象賦值給了對象,我們還可以吧匿名對象直接賦值給方法,比如下面這個樣子。

    fun publicFoo() = object {
        val x: String = "x"
    }
    

    這里涉及到公有還是私有的問題。

    匿名對象我們一般只能用在私有域和本地。白話的說就是一旦變成了公有,那就說誰都可以去調用,由于匿名對象只在生命的本地和私有域起作用,導致公有調用拿到的對象只能是匿名對象的超類(即父類,比如上面的object : Listener就是Listener,如果沒有顯式的定義超類就是Any)那么這樣一來,就會導致匿名內部類中定義的屬性是拿不到的,比如上面的x,因為上面的object并沒有顯式的定義超類,所以他返回的是Any,而Any是沒有x屬性的.

  • 匿名對象訪問變量

    在java中匿名內部類想要訪問相應的屬性變量必須要final才行,但是在kotlin中,我們直接可以訪問包含匿名對象作用域中的所有變量。

    fun countClicks(window: JComponent) {
        var clickCount = 0
        var enterCount = 0
    
        window.addMouseListener(object : MouseAdapter() {
            override fun mouseClicked(e: MouseEvent) {
                clickCount++
            }
    
            override fun mouseEntered(e: MouseEvent) {
                enterCount++
            }
        })
    }
    

Kotlin學習筆記之 1 基礎語法

Kotlin學習筆記之 2 基本數據類型

Kotlin學習筆記之 3 條件控制

Kotlin學習筆記之 4 循環控制

Kotlin學習筆記之 5 類和對象

Kotlin學習筆記之 6 繼承

Kotlin學習筆記之 7 接口

Kotlin學習筆記之 8 擴展

Kotlin學習筆記之 9 數據類與密封類

Kotlin學習筆記之 10 泛型

Kotlin學習筆記之 11 枚舉類

Kotlin學習筆記之 12 對象表達式和對象聲明

Kotlin學習筆記之 13 基礎操作符run、with、let、also、apply

Kotlin學習筆記之 14 包與導入

Kotlin學習筆記之 15 伴生對象

Kotlin學習筆記之 16 委托

Kotlin學習筆記之 17 可觀察屬性

Kotlin學習筆記之 18 函數

Kotlin學習筆記之 19 高階函數與 lambda 表達式

Kotlin學習筆記之 20 內聯函數

Kotlin學習筆記之 21 解構聲明

Kotlin學習筆記之 22 集合

Kotlin學習筆記之 23 相等判斷

Kotlin學習筆記之 24 操作符重載

Kotlin學習筆記之 25 異常捕捉

Kotlin學習筆記之 26 反射

Kotlin學習筆記之 27 類型別名

Kotlin學習筆記之 28 協程基礎

Kotlin學習筆記之 29 上下文與調度器

Kotlin學習筆記之 30 協程取消與超時

Kotlin學習筆記之 31 協程掛起函數的組合

Kotlin學習筆記之 32 協程異常處理

Kotlin學習筆記之 33 協程 & Retrofit

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380