Kotlin學習筆記之 5 類和對象

首發於公衆號: DSGtalk1989html

5.Kotlin 類和對象

  • 構造器java

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

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

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

    class Person constructor(s : String) {
          constructor(i : Int) : this("123"){
          }
    }
    
    //也能夠沒有主構造器,以下的方式就是直接起的次構造器
    //只有這種狀況下,次構造器不須要再調用主構造器,因此通常以下這種方式跟咱們的java習慣比較像
    class Person{
         constructor(){
         }
    }
    複製代碼
  • private,public,protected,internalide

    前面三個你們比較熟悉了,在java中都有,可是internal是kotlin中才引入的,叫作模塊內可見,即同一個module中可見。函數

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

    //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的方法不一樣。this

    private的話,就不會生成getset方法,由於對於這個參數來講,是外部不可訪問的。publicprotected就是相應的setget。而internal則是publicsetD$app_debuggetD$app_debug方法。咱們能夠認爲這兩個方法,在model中都是能夠被訪問的。spa

  • init關鍵字debug

    上面說到主構造器直接寫在類名以後是沒有方法體的,所以一旦咱們想要在構造函數中作一些初始化的操做,就須要挪到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++
              }
          })
      }
      複製代碼
相關文章
相關標籤/搜索