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 Aclass A
内部类(存贮外部类的引用)class Ainner 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

例子:
Kotlin 31 Days

非空类型变量a赋值为null,直接爆红

但是我加了?就正常了,证明加了?表示这个变量为可空类型,可以赋值为null

Kotlin 31 Days

非空判断与安全调用

加? 和不加可以看做是两种类型,不能直接调用可空类型的方法,只有与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<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"
    }
    }

相关推荐