Kotlin 31 Days

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){
    ...
    }

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

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"
    }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章