iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 13
1
自我挑戰組

Android 菜鳥村-開發基礎 30篇系列 第 15

[Day 13 ] Object declarations (單例模式) / Object expressions (匿名內部類)/ Companion Objects

  • 分享至 

  • xImage
  •  

Object declarations (單例模式)

1. Object declarations Init

當我們需要使用 Singleton Pattern , 例如某個類別,只需要實例化一次 , 例如我們要接Api , 或存取一些數據 ,這時候我們就可以 聲明 object 產生唯一的實例。

object 聲明的 Object 跟 一般 Object 不同的地方 , 在於首次訪問 (access) 這個 object 聲明的 class , Classloader 只會載入一次 static 的 init 區塊,所以達到 Singleton 的效果。 因為object不能包含 constructor, 所以也不能被其他人建立(new)。


object Square   {

    private var width = 0

    fun setwidth (parameter:Int) {

        width = parameter
    }

    fun getwidth ():Int {

        return width
    }


}


反編譯之後的結果 , Static Initializer 初始化 這個 class 的靜態實例 ,也是這類的唯一的實例


public final class Square   {
  private static int width;
  
  public static final Square INSTANCE;
  
  static {
    Square square = new Square();
  }
  
  public final void setwidth(int parameter) {
    width = parameter;
  }
  
  public final int getwidth() {
    return width;
  }
}

2. 寫法


object Square {

    private var width = 0

    fun setwidth (parameter:Int) {

        width = parameter
    }

    fun getwidth ():Int {

        return width
    }

}

聲明和一般 class 的 聲明 雷同,需要 class Name ,可以包含 property ,function等。但是,不允許它們具有構造函數 Constructor , 所以也不能被其他人建立(new)。

與一般class 一樣可以繼承於其他 class


open class MouseAdapter {

    open fun mouseClicked(e: MouseEvent) {


    }

    open fun mouseEntered(e: MouseEvent) {


    }

}


object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
}

不能放在local (即直接放在function 內部),但可以放在到其他 Object expressions 或 非 inner class 中 。

我們可以這樣寫:


class outer (){


 object inner {



 }
}


object outer (){


 object inner {



 }
}


我們不可以這樣寫:


inner class outer  {

 object inner {



 }


}


fun addInstant  (){

object InstantFactory {


}

 
}


Object expressions (匿名內部類)

當有些 Object 需要 extend superType , 但又只會用到一次 , 最常見的例子是 OnClickListener
每個 view 要實作 listener 一定都不一樣 , 不可能為了要實作一個 listener , 就寫一個新的 class
這樣不符合效益 , 這時候就會派上用場


btn.setOnClickListener (object: View.OnClickListener {
     
           override fun onClick(v: View?) {
               permission ()
           }


       })

1. 寫法

我們可以這樣聲明 , 在 java 中我們會 new 一個 匿名類別 的 object
但我們還需要用一個變數指向他 , 否則 它無法使用



val shape1 = object {

    private var width = 0

    fun setwidth (parameter:Int) {

        width = parameter
    }

    fun getwidth ():Int {

        return width
    }

}

 

2. 可以跟一般 的 class 一樣 ,extend class 或 implete interface

上面我們 只是 實例一個 Object , 如果需要有任何的 supertype (繼承) , 或 implete 其他 interface
我們可以寫成下面這樣,跟一般 的 class 一樣

要 繼承 的 class


open class Square (){

    private var width = 0

    fun setwidth (parameter:Int) {

        width = parameter
    }

    fun getwidth ():Int {

        return width
    }

}

我們可以宣告一個 匿名 class 所產生的 object 繼承 上面的 類別 (class)


fun main (){

    val square = object:Square () {}

    square.setwidth(500)

    println(square.getwidth())

}

3. 咦? 怎麼找不這個 object 的 function

讓我們來看看官方文件怎麼說

Note that anonymous objects can be used as types only in local and private declarations. If you use an anonymous object as a return type of a public function or the type of a public property, the actual type of that function or property will be the declared supertype of the anonymous object, or Any if you didn't declare any supertype. Members added in the anonymous object will not be accessible.

意思是 匿名類別 所 產生 的 object 如果是被 public 變數 指向 或者 為 public 函數 所返回的內容,則該函數返回的型別或變數的型別 會是這個 object 所繼承的 supertype,或者如果 未繼承任何 supertype,則為Any 。這樣自然無法取用這個object 內的成員變數 或 成員函數。只能有在 local (同一個 function 範圍內)和 private 聲明 (加上private keyword) 中 , 才會知道他明確型別。

像下面的例子


class Example4{

    // 1.
   //   public property

    val publicObj = object{
        val x = 1
    }
    // 2.
   // private property

    private val privateObj = object{
        val x = 2
    }
    
     // 3.
     // Private function, so the return type is 
    // the anonymous object type
    private fun privateFunction() = object {
        val x: String = "x"
    }
   // 4.
    // Public function, so the return type is Any
    fun publicFunction() = object {
        val x: String = "x"
    }
    
    

    fun showcase(){
    // 5.
    // local 
        val scopedObj = object{
            val x = 3
        }
        
        println(privateFunction().x )      // Works fine
        println(publicFunction().x ) // ERROR : unresolved reference: x
        println(publicObj.x)    // ERROR : unresolved reference: x
        println(privateObj.x)    // Works fine
        println(scopedObj.x)     // Works fine
    }
}


上面 2 和 4 並不在一個 function 內 , 又是 public , 型別會是Any ,並無法調用

Companion Objects

1. Companion Objects Init

Companion Objects 和 Object declarations 都是屬於 Singleton ,也就是說這個 class 只會有唯一的實例,也不能被其他人建立(new), 他是 Object declarations 的特例, 跟 Object declarations 不同的地方在於 ,Company Objects伴隨一個 class 並會在這個 class 被初始化時跟著被實例,限制只能 一個 class 只能有一個 Companion Objects , 而 Object declarations 並無此限制


class A {

 companion object  {}
  
 object A{}

 object B{}
 
}


2. Class Name

Companion Objects 的名稱是可選的,可以省略 如果沒有給他類名 , 預設類名會是 Companion, 但不管有沒有
給他類名 , 如果外部如果要指向(reference) 一個 class 中的 companion object , 會是透過這個 Class 做指向(reference)


class MyClass1 {
    companion object Named { }
}

val x = MyClass1 // reference to the companion object of MyClass1  

class MyClass2 {
    companion object { }
}

val y = MyClass2 // reference to the companion object of MyClass2

3. 如何調用 Company Object 的 property 或 function

Company Object 和 Object declarations 相同 ,可以包含property ,function 等。如果我們要調用這個 companion object 內 成員函數 , 會像下面這樣


class MyClass1 {

    companion object Named { 
    
    fun getName () {}
    
    }
}

  val name= MyClass1.getName()  

4. Company Object 可以跟一般 的 class 一樣 ,extend class 或 implete interface


interface Factory  {
    fun create()
}

class MyClass {

    companion object : Factory  {
        override fun create()   
    }
}

val f: Factory = MyClass


5. Memory leak

像這種 companion object 或object 聲明這種靜態的寫法,不宜太常使用,因為只要一被實例會跟整個應用程序的生命期一樣長,如果一個其他的物件 (Object)已經沒有用處了,但是單例這種靜態的寫法還持有它的引用(reference),那麼在整個應用程序的生命週期它都不能正常被回收,從而導致內存洩露。


上一篇
[Day 12] Kotlin 條件控制
下一篇
[Day 14 ] Kotlin 空(null)安全檢查
系列文
Android 菜鳥村-開發基礎 30篇32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言