Kotlin基礎 | 2 = 12 ?泛型、類委託、重載運算符綜合應用

項目應用中一般會對 SharedPreference 再封裝一層,使用 Kotlin 語法糖能夠極大簡化這層封裝。本文將使用委託、泛型、重載運算符來達到化簡的目的。java

這是 Kotlin 系列的第八篇,系列文章目錄以下:bash

  1. Kotlin基礎:白話文轉文言文般的Kotlin常識app

  2. Kotlin基礎:望文生義的Kotlin集合操做ide

  3. Kotlin實戰:用實戰代碼更深刻地理解預約義擴展函數函數

  4. Kotlin實戰:使用DSL構建結構化API去掉冗餘的接口方法工具

  5. Kotlin基礎:屬性也能夠是抽象的post

  6. Kotlin進階:動畫代碼太醜,用DSL動畫庫拯救,像說話同樣寫代碼喲!動畫

  7. Kotlin基礎:用約定簡化相親ui

  8. Kotlin基礎 | 2 = 12 ?泛型、類委託、重載運算符綜合應用this

SharedPreference 在項目中的封裝類一般以下所示:

public final class SPUtils {
    //'持有SharedPreferences實例'
    private SharedPreferences sp;
    
    //'在構造函數中構建SharedPreferences實例'
    private SPUtils(final String spName) {
        sp = Utils.getApp().getSharedPreferences(spName, Context.MODE_PRIVATE);
    }
    
    //'寫函數'
    public void put(@NonNull final String key, final int value, final boolean isCommit) {
        if (isCommit) {
            sp.edit().putInt(key, value).commit();
        } else {
            sp.edit().putInt(key, value).apply();
        }
    }
    
    //'讀函數'
    public int getInt(@NonNull final String key, final int defaultValue) {
        return sp.getInt(key, defaultValue);
    }
    ...
}
複製代碼

SharedPreferences 共支持 6 種數據類型的讀寫,加起來一共 12 個讀寫函數。

類委託

用 Kotlin類委託語法,能夠將 SharedPreferences 工具類聲明以下:

//'將類委託給 SharedPreferences 實例'
class Preference(private val sp: SharedPreferences) : SharedPreferences by sp {
    ...
}
複製代碼

Preference繼承接口SharedPreferences,這樣作的目的是對業務層保留SharedPreferences原有接口。並把構建SharedPreferences實例移出工具類,交給業務層處理。

class Class2(private val c1: Class1) : Class1 by c1這個語法叫類委託,它的意思是:Class2Class1的子類型而且Class2將全部接口的實現委託爲c1實例,這樣 Kotlin 會自動生成全部接口並將其實現委託給超類型的實例。這就是爲啥Preference並未實現接口中的任何一個函數,編譯器卻不報錯,打開 Kotlin 字節碼驗證一下:

public final class Preference implements SharedPreferences {
   private final SharedPreferences sp;
   
   public Preference(@NotNull SharedPreferences sp) {
      Intrinsics.checkParameterIsNotNull(sp, ‘sp’);
      super();
      //'依賴注入'
      this.sp = sp;
   }

   public boolean getBoolean(String key, boolean defValue) {
      //'將實現委託爲sp實例'
      return this.sp.getBoolean(key, defValue);
   }

   public float getFloat(String key, float defValue) {
      //'將實現委託爲sp實例'
      return this.sp.getFloat(key, defValue);
   }
   ...
 }
複製代碼

Kotlin 自定將全部接口的實現委託給了 SharedPreferences 實例。by關鍵詞使咱們能夠省去這些模版代碼。

重載運算符

爲了簡化讀寫函數的調用,從新定義了兩個函數:

class Preference(private val sp: SharedPreferences) : SharedPreferences by sp {
    //'寫函數'
    operator fun set(key: String, isCommit: Boolean = false , value: Int) {
        val edit = sp.edit()
        edit.putInt(key,value)
        if (isCommit) {
            edit.commit()
        } else {
            edit.apply()
        }
    }

    //'讀函數'
    operator fun get(key: String, default: Int): Int = sp.getInt(key,default)
}
複製代碼

這兩個函數都以保留詞operator開頭,它表示重載運算符,即從新定義運算符的語義。Kotlin 中預約義了一些函數名和運算符的對應關係,稱爲約定

函數名 運算符
plus a + b
times a * b
div a / b
mod a % b
minus a - b
unaryPlus + a
unaryMinus - a
not ! a
inc ++a , a++
dec --a, a--
get a[b]
set a = b
rangeTo ..

此次用到的是約定是getset,聲明他們時能夠定義任意長度的參數,分別以 2 參數和 3 參數爲例:

java kotlin
p.get( key, default ) p[ key, default ]
p.set( key, true, value ) p[ key, true ] = value

其中set函數中最後一個參數是=右邊的值,其他參數是=左邊的值。

而後就能夠像這樣簡潔地讀寫了:

class Activity1 : AppCompatActivity() {

    private lateinit var pre: Preference

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //'構建Preferences實例'
        pre = Preference(getSharedPreferences(」test「, Context.MODE_PRIVATE))
        //'寫'
        pre[」a「] = 1
        //'讀'
        pre[」a「,0]
    }
}
複製代碼

但只定義了 Int 值的讀寫,難道其他的類型都要新起一對讀寫函數?

泛型

泛型是類型的類型,使用泛型就能夠讓讀寫函數與具體類型解耦:

class Preference(private val sp: SharedPreferences) : SharedPreferences by sp {
    //'將寫函數的數據類型抽象爲T'
    operator fun <T> set(key: String, isCommit: Boolean = false , value: T) {
        with(sp.edit()) {
            //'將泛型具體化,並委託爲sp實例'
            when (value) {
                is Long -> putLong(key, value)
                is Int -> putInt(key, value)
                is Boolean -> putBoolean(key, value)
                is Float -> putFloat(key, value)
                is String -> putString(key, value)
                is Set<*> -> (value as? Set<String>)?.let { putStringSet(key, it) }
                else -> throw IllegalArgumentException(」unsupported type of value「)
            }
            if (isCommit) {
                commit()
            } else {
                apply()
            }
        }
    }
    
    //'將讀函數的數據類型抽象爲T'
    operator fun <T> get(key: String, default: T): T = with(sp) {
        //'將泛型具體化,並委託爲sp實例'
        when (default) {
            is Long -> getLong(key, default)
            is Int -> getInt(key, default)
            is Boolean -> getBoolean(key, default)
            is Float -> getFloat(key, default)
            is String -> getString(key, default)
            is Set<*> -> getStringSet(key, mutableSetOf())
            else -> throw IllegalArgumentException(」unsupported type of value「)
        } as T
    }
}
複製代碼

其中的withaswhenis的詳細解釋能夠翻閱該系列前面的文章。

而後就能夠像這樣無視類型地使用讀寫函數了:

class DelegateActivity : AppCompatActivity() {

    private lateinit var pre: Preference

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        pre = Preference(getSharedPreferences(」test「, Context.MODE_PRIVATE))
        //'寫int'
        pre[」a「] = 1
        //'使用commit方式寫String'
        pre[」b「,true] = 」2「
        //'寫Set<String>'
        pre[」c「] = mutableSetOf(」cc「,「dd」)
        //'讀String'
        pre[」b「]
    }

}
複製代碼
相關文章
相關標籤/搜索