Kotlin31Days

newtrek發表於2018-07-01

Day1 可見性

參考

31 天,從淺到深輕鬆學習 Kotlin
Kotlin實戰

在 Kotlin 中一切都是預設 public 的。在Kotlin中,存在private、protected、internal和 public四種修飾符,它們可用於修飾類、物件、介面、構造器、函式、屬性、以及屬性的設值方法等。

// 預設public
val isVisible = true
// 只有在相同原始檔內可見
private val isHidden = true
// 同一模組內可見
internal val almostVisible = true

class Foo{
    // 預設public
    val isVisible = true
    // 只能被本類或其子類訪問
    protected val isInheritable = true
    // 只能被本類訪問
    private val isHidden = true
    // 同一模組可見
    internal val isMan = true
}
修飾符 類成員 頂層宣告
public(預設) 所有地方可見 所有地方可見
internal 模組中可見 模組中可見
protected 子類中可見
private 類中可見 檔案中可見

內部類和巢狀類:預設是巢狀類

類A在另一個類B中宣告 在Java中 在Kotlin中
巢狀類(不儲存外部類的引用) static class A class A
內部類(存貯外部類的引用) class A inner Class A
/**
 * 內部類
 */
class Outer{
    inner class Inner{
        fun getOuterReference(): Outer = this@Outer //獲取外部類的引用
    }
}

Day2 可空性

參考

Kotlin Reference: Null Safety
Kotlin教程(四)可空性

可空型別與非空型別

如果一個變數可能為空,然後對這個變數直接進行呼叫是不安全的,很容易造成NullPointerException,通常我們的辦法是加個if判斷,但是用java寫個if在Kotliln看來語句也是聽多的。所以Kotlin給變數增加了可空型別和非空型別特性。

通常宣告一個變數,如果變數型別後面沒有加?,那麼它是非空型別,也就是它不能為null

例子:

沒有加?是非空型別

非空型別變數a賦值為null,直接爆紅

但是我加了?就正常了,證明加了?表示這個變數為可空型別,可以賦值為null

加?是可空型別

非空判斷與安全呼叫

加? 和不加可以看做是兩種型別,不能直接呼叫可空型別的方法,只有與null進行比較後,編譯器才會智慧轉換這個型別。
例如:

fun strLen(s: String?) = if(s != null) s.length else 0  

這樣s才能轉為非空型別,才能呼叫其length屬性

為了簡化是否為null的if語句,Kotlin提供了?.這個安全呼叫運算子,它把一次null檢查和一次方法呼叫合併成一個操作,例如例如表示式s?.toUpperCase() 等同於if (s != null) s.toUpperCase() else null,換句話說,如果你試圖呼叫一個非空值的方法,這次方法呼叫會被正常地執行。但如果值是null,這次呼叫不會發生,而整個表示式的值為null。因此表示式s?.toUpperCase() 的返回型別是String?

安全呼叫支援鏈式呼叫,很實用,例:

bob?.department?.head?.name

Elvis 操作符

對於一個可為空的引用 r,我們可以說“如果 r 不為空,則使用它,否則使用另外的非空值 x”
原來寫法:

val l: Int = if (b != null) b.length else -1

使用Elvis寫法:

val l: Int = b?.length ?: -1

如果 ?: 左邊的表示式不為空,則 Elvis 操作符返回該表示式;否則返回右邊的表示式。注意只有在左邊的表示式不為空時,才會計算右邊的表示式。

因為在 Kotlin 中 throw 和 return 也屬於表示式,所以它們也可以放在 Elvis 操作符的右邊。這一點非常方便,如在檢查函式引數時:

fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ...
}

!!操作符

如果你想要看到NPE,就是讓他像用Java一樣可能拋NPE,那麼你要顯示的在變數後面加!!,不過這樣的情況一般是在你100%確定該變數不會為空。
例如:

var a: String? = "abc" 
 var b: Int = a!!.length

安全轉換

常規的轉換會在物件不屬於目標型別時導致 ClassCastException。此外還可以使用安全轉換,安全轉換會在轉換失敗時返回 null:

val aInt: Int? = a as? Int

Day3 String模板

格式化字串?將$放在變數名的前面去表達字串中的變數和表示式。使用 ${expression} 求表示式的值。
例子:

    val i = 21
    val s = "I`m $i years old! "
    println(s)
    val b = 2
    val s2 = "after $b years,I`m ${i+b} years old!"
    println(s2)

Day4 when表示式

參考文章

31 天,從淺到深輕鬆學習 Kotlin
kotlin中的when:強大的switch

when相當於switch,但是Kotlin的when是個強大的switch,幾乎可以匹配任何東西,字面值,列舉,陣列範圍

像用Java的switch一樣使用

when(條件){
  條件值1 -> 執行語句1
  條件值2 -> 執行語句2
  條件值3 -> 執行語句3
  else -> 執行語句4
}

自動轉換型別

 when (view) {
     is TextView -> toast(view.text)
     is RecyclerView -> toast("Item count = ${view.adapter.itemCount}")
     is SearchView -> toast("Current query: ${view.query}")
     else -> toast("View type not supported")
 }

無自變數when,類似if else的含義,可做表示式

 val res = when {
     x in 1..10 -> "cheap"
     s.contains("hello") -> "it`s a welcome!"
     v is ViewGroup -> "child count: ${v.getChildCount()}"
     else -> ""
 }

Day5 迴圈,範圍表示式與解構

    for(i in 1..100){
    ...
    }
    for(i in 100 downTo 1){
    ...
    }
    val array = arrayOf("a","b","c")
    for (i in 1 until array.size step 2){
        println(i)
    }
    for ((index,element) in array.withIndex()){
    ...
    }

    val map = mapOf(1 to "one", 2 to "two")
    for ((key,value) in map){
    ...
    }

Day6 屬性

再Kotlin中,類可以具有可變和只讀屬性,預設情況下生成getter和setter,也可以自定義。

 class User{
     // 只讀屬性
     val id: String = ""
     // 可變屬性,預設有getter和setter
     var name: String = ""

     var surname: String = ""
     get() = surname.toUpperCase()//自定義getter

     var email: String = ""
     set(value){// 自定義setter
         if (isEmailValid(value)) field = value
     }
 }

Day7 解構

Android KTX 使用解構來分配顏色的元件值。您可以在您的類中使用解構,或者擴充套件現有的類來新增解構。

    val (red,green,blue) = color
    val (left,top,right,bottom) = react
    val (x,y) = point

解構宣告可以一次宣告多個變數,任何表示式都可以出現在解構宣告的右側,只要我們可以對它呼叫所需數量的component函式(注意:componentN()函式需要用operator關鍵字修飾,以允許其在解構宣告宣告中使用它們)。資料類自動宣告component函式。

data class Person(val name: String = "Kotlin",val sex: String = "男",val age: Int = 20)
fun foo(){
    var (name,sex,age) = Person()
    var (_,sex2,age2) = Person()
    println("name:$name,sex:$sex,age:$age")
}

也可以在for迴圈中使用解構宣告,只要集合中的每個元素提供有componentN()方法

var collections= arrayListOf<Person>()//list集合中元素要為其每個成員屬性提供有componentN方法
    for((name,sex,age) in collections){
        println("name=$name&sex=$sex&age=$age")
    }

也可以對對映使用解構賦值。因為map結構提供函式 component1() 和 component2() 來將每個元素呈現為一對。

    var map= mutableMapOf<String,String>()
    map.put("name","Kotlin")
    map.put("sex","男")
    map.put("age","13")
    for ((key,value) in map){
        println("$key=$value")
    }

Day 8 簡單的bundle

通過簡潔的方式建立bundle,不呼叫putString,putInt,或它們的20個方法中的任何一個。一個呼叫生成一個新的bundle,甚至可以處理Arrays,bundle很像map

val bundle = bundleOf(
      "KEY_INT" to 1,
      "KEY_LONG" to 2L,
      "KEY_BOOLEAN" to true,
      "KEY_NULL" to null,
      "KEY_ARRAY" to arrayOf(1,2)
)

val testMap = mapOf(
            "int" to 1,
            "long" to 2L,
            "boolean" to true,
            "null" to null,
            "array" to arrayOf(1,2)
    )

Day 9 Parcelize

習慣Parcelable的速度,但不喜歡寫所有的程式碼?和@Parcelize打個招呼.

@Parcelize
data class User(val name: String, val occupation: Work): Parcelable

// build.gradle
androidExtensions {
  // enale experimental kotlin features in gradle to enable Parcelize
    experimental = true
}

Day10 Data類和equality

可以建立一資料類,它可以預設實現生成equals()方法相當於hashCode(),toString()和copy(),並檢查結構是否相等。

fun main(args: Array<String>){
     val user1 = User("wang","123456","12345")
    val user2 = User("wang","123456","12345")
    println("user1 equals user2 :${user1 == user2}")
}


data class User(val name: String,
                val email: String,
                val address: String)

Day11 簡化postDelay

Android KTX已經為Handler做了個小包裝,類似於傳閉包的形式傳入引數。

// android KTX api
fun Handler.postDelayed(
  delay:Int,
  token: Any? = null,
  action: () -> Unit
)

handle.postDelayed(50){
  // pass a lambda to postDelayed
}

Day12 預設引數

如果方法中的引數太多,Kotlin可以在函式中指定預設引數值,使用命名引數使程式碼更具有可讀性。

class BulletPointSpan(
  private val bulletRadius: Float = DEFAULT_RADIUS,
  private val gapWidth: Int = DEFAULT_GAP_WIDTH,
  private val color: Int = Color.BLACK
){...}
// 只用預設值
val bulletPointSpan = BulletPointSpan()

// 傳第一個引數值
val bulletPointSpan2 = BulletPointSpan(resources.getDimension(R.dimen.radius))
// 傳最後一個引數的值
val bulletPointSpan3 = BulletPointSpan(color = Color.RED)

Day13 java呼叫Kotlin

java和,Kotlin混用,預設情況下,編譯器可以生成頂級類,名稱為YourFileKt,可以通過使用@file:JvmName註釋檔案來更改它。

ShapesGenerator.kt

package com.newtrekwang.kotlinprac
fun generateCicir():String{
    return "circle"
}

fun generateRect():String{
    return "rect"
}

val WIDTH : Int = 100
var height: Int = 300

java呼叫

 public static void main(String argv[]){
          String circle =   ShapesGeneratorKt.generateCicir();
          int width = ShapesGeneratorKt.getWIDTH();
          int height = ShapesGeneratorKt.getHeight();
          ShapesGeneratorKt.setHeight(200);
        }

使用file註解

@file:JvmName("Test")
package com.newtrekwang.kotlinprac

fun generateCicir():String{
    return "circle"
}

fun generateRect():String{
    return "rect"
}

val WIDTH : Int = 100
var height: Int = 300

java呼叫

  public static void main(String argv[]){
          String circle =   Test.generateCicir();
          int width = Test.getWIDTH();
          int height = Test.getHeight();
         Test.setHeight(200);
        }

Day14 在沒有迭代器的情況下迭代型別

迭代器用在了有趣的地方!Android KTX 將迭代器新增到 viewGroup 和 sparseArray。要定義迭代器擴充套件請使用 operator 關鍵字。 Foreach 迴圈將使用副檔名!
可惜androidKTX要最新版開發版AS才能用。

// Andrfoid KTX
for(view in viewGroup){}
for(key in sparseArray){}

// 你的project
operator Waterfall.iterator(){
  // 新增一個迭代器給waterFall類
}
for(item in myClass){}

第二週小結:
這周我們更深入的學習了 Kotlin 的特性:簡潔 bundle,迭代,Data,postDelay,預設引數,序列化。

Day 15 Sealed Class

Kotlin的sealed類可以讓你輕鬆的處理錯誤資料

密封類的好處在於:使用when表示式,如果能覆蓋所有情況,就無需再新增else子句

sealed class NetworkResult
data class Success(val result: String):NetworkResult()
data class Failure(val error: Error):NetworkResult()

fun useSealedClass(networkResult: NetworkResult){
    when(networkResult){
        is Success -> showResult(networkResult.result)
        is Failure -> showError(networkResult.error)
    }
}

如果將sealed類用在RecyclerView的Adapter中,非常適合於ViewHolders,用一組乾淨的型別明確地分派給每個持有者。用作表示式時,如果有型別不匹配,編譯器將會出錯。

override fun onBindViewHolder(holder: SealedAdapterViewHolder?,position: Int){
  when(holder){
      is HeaderHolder -> {...}
      is DetailHolder -> {....}
  }
}

使用RecyclerView,如果我們有很多來自RecyclerView中的item的回撥,比如一個點選,分享和刪除item的專案,我們可以用sealed類,一個回撥處理所有的事情!

sealed class DetailItemClickEvent
data class DetailBodyClick(val section: Int): DetailItemClickEvent()
data class ShareClick(val platform: String): DetailItemClickEvent()
data class DeleteClick(val confirmed: Boolean): DetailItemClickEvent()

interface ItemOnClickListener{
    fun onClick(detailItemClickEvent: DetailItemClickEvent)
}

class MyHander: ItemOnClickListener{
    override fun onClick(detailItemClickEvent: DetailItemClickEvent) {
        when(detailItemClickEvent){
            is DetailBodyClick -> {...}
            is ShareClick -> {...}
            is DeleteClick -> {...}
        }
    }
}

Day 16 懶載入

通過使用懶載入,可以省去昂貴的屬性初始化的成本直到它們真正需要時。計算值然後儲存併為了未來的任何時候的呼叫。

lazy 應用於單例模式(if-null-then-init-else-return),而且當且僅當變數被第一次呼叫的時候,委託方法才會執行。

val preference : String by lazy {
    sharedPreferences.getString(PREFERENCE_KEY)
}

Day 17 lateinit

在kotlin中不為空的物件必須初始化,var 變數加這個關鍵字表示這個變數遲早會初始化,不過lateinit只能用於var

例如,Activity裡的成員初始化

class MyActivity: AppCompatActivity(){
    lateinit var recyclerView: RecyclerView
 override fun onCreate(savedInstanceState: Bundler?){
     recyclerView = findViewById(R.id.recycler_view)
   }
}

Day18 要求(require)和檢查(check)

判斷方法引數的有效,可以用require在使用前檢查它們,如果它們是無效的,將會丟擲IllegalArgumentException

fun setName(name: String){
    require(name.isNotEmpty()){ "Invalid name" }
}

封閉類的狀態是否正確,可以使用check來驗證,如果檢查的值為false,它將丟擲IllegalStateException

fun logout(){
    check(isLogin()){"沒有登入!"}
    //todo
}

本質就是Kotlin標準庫定義了個require和check含閉包的方法

Day19 內聯 InLine

參考:Kotlin語法(十九)-行內函數(Inline Functions)

呼叫一個方法是一個壓棧和出棧的過程,呼叫方法時將棧針壓入方法棧,然後執行方法體,方法結束時將棧針出棧,這個壓棧和出棧的過程會耗費資源,這個過程中傳遞形參也會耗費資源。
例如:

fun <T> check(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        } finally {
            lock.unlock()
        }
    }

通過內聯Lambda表示式方式,可以減少這種開銷。等同於:

 l.lock()
        try {
            return "我是lambda方法體"
        } finally {
            l.unlock()
        }

定義方法:

inline fun <T> check(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        } finally {
            lock.unlock()
        }
    }

inline關鍵字實際上增加了程式碼量,但是提升了效能,而且增加的程式碼量是在編譯期執行的,對程式可讀性不會造成影響。

Day20 運算子過載

參考:Kotlin – 運算子過載

用操作符過載快更快速寫 Kotlin。像 Path,Range或 SpannableStrings 這樣的物件允許像加法或減法這樣的操作。通過 Kotlin,您可以實現自己的操作符。

Day21 頂級方法和引數

頂級方法,將它們新增到原始檔的頂層,在Java中,它們被編譯為該類的靜態方法

// 定義一個頂級方法,為RecyclerView建立databinding adapter
@BindingAdapter("userItems")
fun userItems(recyclerView: RecyclerView,list: List<User>?){
  // todo
}
class UsersFragment: Fragment{...}

也可以定義頂級屬性,它們將編譯為欄位和靜態訪問器

// 為Room database 定義一個頂級屬性
private const val DATABASE_NAME = "MyDatabase.db"

private fun makeDatabase(context: Context): MyDatabase{
  return Room.databaseBuilder(
  context,
  MyDatabase::class.java,
  DATABASE_NAME
).build()
}

Day22 簡單的內容值

將ContentValues的強大功能與kotlin的簡潔性相結合,使得Android KTX只傳遞一個Pair<StringKey, Value>建立ContentValues.

val contentValues = contentValuesOf(
  "KEY_INT" to 1,
  "KEY_LONG" to 2L,
  "KEY_BOOLEAN" to true,
  "KEY_NULL" to null
)

Day23 DSLs

特定於域的語言可以通過使用型別安全的構建器來完成。它們為簡化API做出貢獻。你可以藉助擴充套件lambdas和型別安全構建器等功能構建它們.

html {
    head {
             title {+"This is Kotlin!"}
         }
    body{
          h1 {+"A DSL in Kotlin!"}
          p {+"It`s rather"
            b {+"bold."}
            +"don`t you think?"
            }
        }
}

Anko例子:

frameLayout {
    button("Light a fire"){
      onClick {
        ligntAFire()
      }
    }

}

Day 24 具體化

Android KTX中的Context.systemService()使用泛化來通過泛型傳遞“真實”型別,沒有通過getSystemService.

// the old way
val alarmManager = context.getSystemService(AlarmManager::class.java)

// the reified way
val alarmManager : AlarmManager  = context.systemService()

// KTX定義的行內函數+泛型
inline fun <reified T> Context.systemService() = getSystemService(T::class.java)

Day25 代理

類委託

fun main(args: Array<String>){
    val 代理 = 代理律師("財產")
    原告(代理).打官司()
}

interface Base{
    fun 打官司()
}

class 原告(律師 : 代理律師): Base by 律師

class 代理律師 (str : String):Base{
    override fun 打官司() {
        println("打官司 $str")
    }
}

屬性委託
語法是: val/var <屬性名>: <型別> by <表示式>。在 by 後面的表示式是該 委託, 因為屬性對應的 get()(和 set())會被委託給它的 getValue() 和 setValue() 方法。 屬性的委託不必實現任何的介面,但是需要提供一個 getValue() 函式(和 setValue()——對於 var 屬性)。

fun main(args: Array<String>){
    val e = Example()
    println(e.property)
    e.property = "new"
    println(e.property)
}
class Example{
    var property: String by DelegateProperty()
}
class DelegateProperty{
    var temp = "old"
    operator fun getValue(ref: Any?,p: KProperty<*>):String {
        return "DelegateProperty --> ${p.name} --> $temp"
    }
    operator fun setValue(ref: Any?,p: KProperty<*>,value: String){
        temp = value
    }
}

Day26 擴充套件方法

沒有更多的Util類,可以通過使用擴充套件方法擴充套件類的功能。只要把你要擴充套件的類的名字放在你新增的方法的名字後面。

擴充套件功能的一些特性:

  • 不是成員函式
  • 不要以任何方式修改原始類
  • 通過靜態型別資訊解決編譯時間
  • 會被編譯為靜態函式
  • 不要多型性

例如String的擴充套件:

// 定義
inline fun String.toUri(): Uri = Uri.parse(this)
// 使用
val myUri = "www.developer.android.com".toUri()

Day27 Drawable.toBitmap()輕鬆轉換

AndroidKTX對Drawable進行了擴充套件,能將Drawable輕鬆轉換為bitmap

val myDrawable = ContextCompat.getDrawable(context,R.drawable.icon)

// convert the drawable to a bitmap
val bitmap = myDrawable.toBitmap()

Day28 Sequences,lazy和generators

參考

Kotlin系列之序列(Sequences)原始碼完全解析

序列操作又被稱之為惰性集合操作,Sequences序列介面強大在於其操作的實現方式。序列中的元素求值都是惰性的,所以可以更加高效使用序列來對資料集中的元素進行鏈式操作(對映、過濾、變換等),而不需要像普通集合那樣,每進行一次資料操作,都必須要開闢新的記憶體來儲存中間結果,而實際上絕大多數的資料集合操作的需求關注點在於最後的結果而不是中間的過程,

序列是在Kotlin中運算元據集的另一種選擇,它和Java8中新增的Stream很像,在Java8中我們可以把一個資料集合轉換成Stream,然後再對Stream進行資料操作(對映、過濾、變換等),序列(Sequences)可以說是用於優化集合在一些特殊場景下的工具。但是它不是用來替代集合,準確來說它起到是一個互補的作用。

val sequence = List(50){it * 5}.asSequence()

sequence.map { it * 2}
        .filter {it % 3 == 0 }
        .map { it + 1 }
        .toList()

Day 29 更簡單的Sqans

Spans 功能強大,但是API很難用,Android KTX為常見的Span新增了擴充套件功能。

val string = buildSpannedString {append("no styling text")}
bold {
  append("bold")
  italic { append("bold and italic")}
}
inSpans(RelativeSizeSpan(2f),QuoteSpan()){ append("double sized quote text")}

Day30 updatePadding 擴充套件

Android KTX的檢視擴充套件

view.updatePadding(left = newPadding)
view.updatePadding(right = newPadding)
view.updatePadding(top = newPadding)
view.updatePadding(bottom = newPadding)
view.updatePadding(right = newPadding,left = newPadding)

Day 31 範圍外run,let,with,apply

kotlin的標準函式,簡短強大,run,let,with和apply都有一個接收器(this),可能由一個引數(it)並可能有一個返回值,差異如下:

run

val string = "a"
val result =  string.run {
    // this = "a"
    // it 不可訪問
        print("test run")
        1
    }

執行後,result為1,run就是一個傳閉包的函式,閉包裡可以呼叫物件的方法和this

let

val string = "a"
    val result =  string.let {
// this = this@MyClass
// it = "a"
       print(it)
        2
    }

with

val string = "a"
    val result = with(string) {
        // this = "a"
        // it 不可訪問
        3 // 程式碼塊返回結果
    }

apply

val string = "a"
    val result = string.apply { 
        // this = "a"
        // it 不可訪問
        5 // 這個值不是程式碼塊返回結果,而是"a",所以result="a"
    }
    }