我的kotlin學習筆記(一)——對象

正兒八經上班第一天,剛剛想寫的什麼的時候,發現同事在項目裏面使用了kotlin。真不知道是該高興奈還是該高興奈,總有人推着你前進。那我也開始吧。半年前看過一點,現在忘得差不多了,寫的比較亂,都是邊開發邊學習。
————————-jjj的kotlin學習筆記————————-
1、對象

1.1、對象表達式

先回顧下java的匿名內部類:匿名內部類就是沒有名字內部類,因爲沒有名字只能使用 一次。

// 匿名內部類:
new 類名或接口名(){
    重寫方法;
}; 

// 常見實例
textView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        ...
    }
});

那kotlin中如何表達匿名內部類?
這時候對象表達式就登場了:

// 對象表達式:
object: 類名或接口名(){
    重寫方法
};
// 實例:
textView.setOnClickListener(object : View.OnClickListener{
    override fun onClick(v: View?) {
        ...
    }
})

如果父類有構造函數,則必須傳遞相應的構造函數;
如果有多個父類,則使用逗號分隔,寫在冒號後面。

var a = object : A(this), B {
    override fun testA() {
        ...
    }

    override fun testB() {
        ...
    }
}

abstract class A(ctx: Context) {
    abstract fun testA()
}
interface B {
    fun testB()
}

如果只需要一個對象,沒有父類的情況。

val o = object {
    var x = 1
    var y = 2
}
var result = o.x + o.y

值得注意的是,匿名對象可以用作本地(同一方法裏)和私有(private)作用域中。具體原因看鏈接文檔

// 私有函數,其返回類型是匿名對象類型
private fun foo1() = object {
     val x: String = "x"
     val y: String = "y"
}

fun bar(){
    // 本地屬性
    val foo2 = object {
        val x: String = "x"
        val y: String = "y"
    }

    val x1 = foo1().x
    val x2 = foo2.y
}
1.2、對象聲明和伴生對象

由於kotlin移除了static的概念,便引入對象聲明。
對象聲明由object關鍵字+名稱表示,對象聲明不能用在賦值語句的右邊,使用場景:

// 1.靜態變量
object Constant {
    /**
     * baseUrl
     */
    val REQUEST_BASE_URL = "http://gank.io/api/"
    val ALL = "all"
    val ANDROID = "Android"
    ...
}

// 2、單例模式 
object SimpleSington {
    fun test() {}
}

到這裏,先回顧下什麼是單例

單例是種設計模式;使用時,單例的對象必須保證只有一個實例存在,不允許自由構造對象。因此單例的構造函數不對外開放,通常爲private。

單例模式的分類:

// 1、餓漢式:
public class Singleton{  
    private static Singleton instance = new Singleton();  
    private Singleton(){}  
    public static Singleton newInstance(){  
        return instance;  
    }  
}

// 2、懶漢式:
public class Singleton{  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static Singleton newInstance(){  
        if(null == instance){  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}  
// 懶漢式:加同步鎖解決線程同步問題
public class Singleton{  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static synchronized Singleton newInstance(){  
        if(null == instance){  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}  

// 3、雙重校驗鎖:
public class Singleton {  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {//2  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}  

// 雙重校驗鎖:防止指令重排優化
public class Singleton {  
    private static volatile Singleton instance = null;  
    private Singleton(){}  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}  

// 4、靜態內部類
public class Singleton{  
    private static class SingletonHolder{  
        public static Singleton instance = new Singleton();  
    }  
    private Singleton(){}  
    public static Singleton newInstance(){  
        return SingletonHolder.instance;  
    }  
}  

// 5、枚舉
public enum Singleton{  
    instance;  
    public void whateverMethod(){}      
}  

下面依次分析下各模式:
餓漢式:最簡單的實現方式,在類加載的時候就會對實例進行創建,實例在整個程序週期都存在,這也是它的缺點:該類只要加載單例就會被創建,即使沒用到,所以容易形成了內存浪費。優點是:只在類加載的時候創建一次實例,不會存在多個線程創建多個實例的情況,避免多線程同步的問題。

懶漢式:解決了餓漢式的類加載後單例創建的問題,懶漢式的單例只在需要的時候才創建,如果單例已經創建,再次調用接口不會重新創建新的對象,而是返回之前的創建對象。但是懶漢模式沒有考慮線程安全問題,多個線程可能併發調用它的getInstance()方法,導致創建多個實例,這時候可以加上同步鎖。

雙重校驗鎖:懶漢式中使用synchronized修飾的同步方法比一般方法要慢很多,如果多次調用單例方法將存在性能問題。雙重校驗鎖調整了synchronized修飾的位置,並在同步代碼塊外多了一層instance爲空的判斷。因此,大部分情況下,調用getInstance()都不會執行到同步代碼塊,從而提高了程序性能。但也不是萬無一失的,設立涉及到java中的指令重排優化,最終結果到時候雙重校驗鎖失效,不過jdk1.5版本後新增的volatile關鍵字可以解決這個尷尬問題。

總之雙重校驗鎖既實現了延遲加載,又解決了線程併發問題,同時還解決了執行效率的問題。

靜態內部類:這種加載方式利用了類加載機智保證只創建一個instanc實例。與餓漢模式一樣,不存在多線程併發的問題。不一樣的是,靜態內部類是在內部類裏面創建對象實例的,只要應用中不使用內部類,JVM就不會去加載這個單例類,也不會去創建單例對象,從而實現了懶漢式的延遲加載。

枚舉:上面的四種單例模式都有兩個共同的缺點:
(1)需要額外的工作來實現序列化
(2)可以使用反射強行調用私有構造函數
枚舉很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器之外,還提供了自動序列化機制,防止序列化的時候創建新的對象。奇怪的是在實際工作中,很少有人用枚舉。

————————————分割線———————————
kotlin聲明單例模式,請參考原文

// 常見寫法
object SimpleSington {
    fun test() {}
}

// 在Kotlin裏調用
SimpleSington.test()

// 在java裏調用
SimpleSington.INSTANCE.test();

// 上面的寫法相類似於java中的:
public final class SimpleSington {
   public static final SimpleSington INSTANCE;

   private SimpleSington() {
      INSTANCE = (SimpleSington)this;
   }

   static {
      new SimpleSington();
   }
}

你可能會發現,原來object類型的單例模式,本質上就是餓漢式加載,即在類加載的時候創建單例。

kotlin的懶漢式加載
先了解下kotlin中伴生對象:類內部的對象聲明可以用companion關鍵字標記,

// 1、伴生對象實例
class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}
// 調用方式
val instance = MyClass.create()

// 2、可省略伴生對象的名稱
class MyClass {
    companion object {
    }
}
// 調用方式
val x = MyClass.Companion

// 懶漢式加載方式
class LazySingleton private constructor(){
    companion object {
        val instance: LazySingleton by lazy { LazySingleton() }
    }
}

懶漢式加載特徵:
1. 顯示聲明構造方法爲private;
2. companion object用來在class內部聲明一個對象;
3. LazySingleton 的實例instance通過lazy來實現懶漢式加載
4. lazy默認情況下線程是安全的,可以避免多個線程同事訪問生成多個實例的問題

對於實例初始化花費時間較少,並且內存佔用較低的話,應該使用object形式的餓漢式加載。否則使用懶漢式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章