Kotlin学习笔记

920 阅读10分钟

学习自码上开学
推荐Kotlin 在Android开发中那些让人舒适的地方

1.添加Kotlin依赖

  • 项目根目录下的 build.gradle
buildscript {
    ext.kotlin_version = '1.3.50'
    ...
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
  • app目录下的 build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
...
dependencies {
    ...
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

2.变量

  • 变量的声明与赋值
var v: View

属性需要在声明的同时初始化,除非你把它声明成抽象的
解决办法:
1.加 lateinit 关键字,这个变量的初始化就全靠你自己了,编译器不帮你检查了(不适用Int,Long原始类型,原始类型用Delegates.notNull)

lateinit var view: View
override fun onCreate(...) {
    ...
    view = findViewById(R.id.tvContent)
}

//或者
class App : Application() {
    companion object {
        var instance: App by Delegates.notNull()
    }

2.在类型右边加一个 ? 号,解除它的非空限制,就像 Java 变量一样没有非空的限制了

var view: View? = null
view?.setBackgroundColor(Color.RED)

确保view不是null的情况下调用,否则它会抛出异常

view!!.setBackgroundColor(Color.RED)

空安全调用 ?.,在对象非空时会执行后面的调用,对象为空时就会返回 null

val str: String? = "Hello"
var length: Int = str?.length//IDE 报错,Type mismatch. Required:Int. Found:Int? (str?.length返回一个null给length)

是null的情况下的替代值

val name = artist?.name ?: "empty"
  • is来判断变量类型,类似Java中instanceOf
fun judgeType(obj: Any) {
    if (obj is String) {
        println("传入的是String类型")
        // 做过类型判断以后,obj会被系统自动转换为String类型
        println("字符串长度为${obj.length}")
    } else if (obj is Int) {
        println("传入的是Int类型")
    } else if (obj is Double) {
        println("传入的是Double类型")
    }
}
  • 两个数值的比较 : 判断两个数值是否相等(==),判断两个数值在内存中的地址是否相等(===)。

3.函数

  • 函数的声明
fun cook(name: String): Food {
    ...
}

Kotlin 里是返回 Unit,并且可以省略:

// Unit 返回类型可以省略
fun main() {
    
}

函数参数也可以有可空的控制,可空变量传给不可空参数,报错

// 可空变量传给不可空参数,报错
var myName : String? = "rengwuxian"
fun cook(name: String) : Food {}
cook(myName)

// 可空变量传给可空参数,正常运行
var myName : String? = "rengwuxian"
fun cook(name: String?) : Food {}
cook(myName)

// 不可空变量传给不可空参数,正常运行
var myName : String = "rengwuxian"
fun cook(name: String) : Food {}
cook(myName)

可以给参数指定一个默认值使得它们变得可选

fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, length).show()
}
//==============================调用=======================================
toast("Hello")
toast("Hello", Toast.LENGTH_LONG)

扩展函数

扩展函数数是指在一个类上增加一种新的行为,甚至我们没有这个类代码的访问权 限

//它可以被任何Context或者它的子类调用,比如Activity或者Service
fun Context.toast(message: CharSequence, duration: Int = Toast.L
ENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}
//这个方法可以在Activity内部直接调用:
toast("Hello world!")
toast("Hello world!", Toast.LENGTH_LONG)

4.类型

  • 数组

    • 数组的创建
      • arrayOf() : 参数是一个可变参数的泛型对象。为元素是基本类型的数组增加了xxArray类(其中xx也就是Byte,Short, Int等基本类型)
        //类似java中:  int[] array = new int[] {1, 2};
        var array: IntArray = intArrayOf(1, 2)
        
        var array2 = arrayOf("0","2","3",'a',32.3f)
        
      • arrayOfNulls():用于创建一个指定数据类型且可以为空元素的给定元素个数的数组
        var arr3 = arrayOfNulls<Int>(3)
        
      • 工厂函数Array():Array() => 第一个参数表示数组元素的个数,第二个参数则为使用其元素下标组成的表达式
        var arr4 = Array(5,{index -> (index * 2).toString() })
        for (v in arr4){
           print(v)
           print("\t")
        }
        //输出结果为:
        //0   2   4   6   8
        
    • 数组的遍历
    val items = arrayOf(1, 2, 3)
    //遍历value
    for (value in items){
        print("${value}  ")
    }
    println()
    //遍历index
    for (index in items.indices){
        print("${index}  ")
    }
    println()
    //遍历index和item
    for ((index,value) in items.withIndex()){
        println("index: ${index}  value: ${value} ")
    }
    
    • 数组的过滤。filter:对每个元素进行过滤操作,如果 lambda 表达式中的条件成立则留下该元素,否则剔除,最终生成新的集合
     val intArray = intArrayOf(1, 2, 3) // [1, 2, 3]
     //注意,这里变成了 List。结果为  {2, 3}
    
     val newList: List = intArray.filter { i ->
        i != 1 // 过滤掉数组中等于 1 的元素
     }
    
    • map:遍历每个元素并执行给定表达式,最终形成新的集合
     val intArray = intArrayOf(1, 2, 3) // [1, 2, 3]
     //最后结果为  {2, 3, 4}
     val newList: List = intArray.map { i ->
        i + 1 //每个元素加 1
     }
    
    • flatMap:遍历每个元素,并为每个元素创建新的集合,最后合并到一个集合中
     val intArray = intArrayOf(1, 2, 3) // [1, 2, 3]
     //最后结果 {"2", "a" , "3", "a", "4", "a"}
     intArray.flatMap { i ->
        listOf("${i + 1}", "a") //生成新集合
     }
    
  • 集合

    • list集合的创建
     listOf(); 返回不可变的集合(内部用ArrayLit实现,返回的list是只读的)
    
     listOfNull(); 返回不可变集合,和前一个函数的唯一的区别是,该函数会自动去掉传入的null,也就是说返回的时候不会返回null,会过滤掉。
    
     mutableListOf(); 该函数返回可变的MutableListOf()集合
     
     arrayListOf();同java的ArrayList
     
     //数组转集合
     val arr = arrayOf("1",2,3,4)
     val mutableList = mutableListOf(arr) 
    
    • set集合的创建
     setOf();该函数返回不可变的Set集合,该集合可以接收0个或过个参数,这些参数将作为集合的元素。
    
     mutableSet0f():该函数返回可变的MutableSet集合,
    
     hashSetOf():该函数返回可变的HashSet集合,
    
     linkedSetOf():该函数返回可变的LinkedHashSet集合。
    
     sortedSetOf():该函数返回可变的TreeSet集合, 用法同下。
    
    • map类型的创建

       mapOf():不可变的Map类型集合的初始化使用
      
       mutableMapOf():可变的Map类型集合的初始化使用
      
       hashMapOf():同Java中的HashMap
      
      • map的遍历
        val map =  mapOf("key1" to 2 , "key1" to 3 , "key1" to "value1" , "key2" to "value2")
        
        map.forEach{
           key,value -> println("$key \t $value")
        }
        
  • 总结

-数组:
      arrayOf(...)

不可变集合:
      listOf(...), 
      mapOf(...), 
      setOf(...) , 
      ...

可变集合:
      mutableListOf(...), 
      mutableMapOf(...), 
      mutableSetOf(...), 
      ...

集合间相互转换:
      toList(), 
      toSet(), 
      toHashSet(), 
      toMutableList(), 
      toSet(), 
      toIntArray(), 
      ...

5.类和对象

  • : 不仅可以表示继承,还可以表示 Java 中的 implement
//继承类AppCompatActivity,同事实现了接口 Impl
class MainActivity : AppCompatActivity(), Impl {}
  • Kotlin 里的类默认是 final 的,使用open可以解除 final 限制
open class MainActivity : AppCompatActivity() {}

//这样一来,我们就可以继承了。
class NewActivity: MainActivity() {}
  • Kotlin也有和 Java 一样的 abstract 关键字,这俩关键字的区别就是 abstract 关键字修饰的类无法直接实例化(除非使用object关键字修饰)
  • 在Kotlin中,允许有一个主构造函数和多个二级构造函数(辅助构造函数)。Kotlin 中使用 constructor 表示构造函数,其中主构造函数是类头的一部分,
    • 主构造函数
    class Test constructor(num : Int){
        ...
    }
    
    • 如果在主构造器的参数声明时加上 var 或者 val,就等于直接声明了属性
    class User(var name: String) {
    }
    
    // 等价于:
    class User(name: String) {
       var name: String = name
    }
    
    • 辅助(二级)构造函数,以constructor关键字作为前缀。使用this关键字对同一类的另一个构造函数进行委派
    class Test constructor(num: Int){
       ...
       constructor(num : Int, num2: Int) : this(num) {
           println(num + num2)
       }
    }
    
    • init代码块中的代码,编译后会按顺序放在构造函数中,并且先于构造函数中原来的代码执行
    class Person(name: String) {
        init {
            println("init---" + name);
        }
    
        constructor(name: String, age: Int) : this(name) {
            println("constructor---" + name);
        }
    }
    //别处调用
    var person = Person("test")
    //输出顺序
    先给name赋值-->
    init---test-->
    constructor---name
    
  • 类的实例化,kotlin没有Java的new关键字
     var test = Test()
     var test1 = Test(1,2)
    
  • 不加可见性修饰符的话, 类,方法默认是public的
  • get和set函数。
class User {
   var name = "Mike"
   fun run() {
       name = "Mary"// 实际上是这么调用的setName("Mary")
       println(name)//法实际上是这么调用的print(getName())
   }
}

所以下面打印出name属性值时,默认调用了他的get()方法,输出了testhaha

class Person() {

   var name = "test"
       get() {
           return field + "haha"
       }
}

fun main(args: Array<String>) {
   print(Person().name);
}
  • object(单例类型)修饰类时,这个类的所有所有变量相当于静态变量,所有方法相当于静态方法。相当于创建了一个单例类
    object Sample {
      val name = "A name"
      fun printName()=println(name)
    }
    //调用时,直接相当于java中static修饰后调用
    Sample.name
    Sample.printName()
    
    • 在一个普通类里,其内部类用object修饰后,相当于创建了他的静态内部类,让类中的一部分函数和变量是静态的
       class A {
          object B {
             var c: Int = 0
         }
       }
       //调用
       A.B.c
      
    • 类中嵌套的对象可以用 companion 修饰,此时对象的名字也可以省略掉。此时就可以像 Java 一样通过类直接引用
      class A {
         companion object {
             var c: Int = 0
         }
      }
      
  • 顶层声明:就是把属性和函数的声明不写在 class 里面。这样写的属性和函数,不属于任何 class,而是直接属于 package,它和静态变量、静态函数一样是全局的
package com.hencoder.plus
// 属于 package,不在 class/object 内
fun topLevelFuncion() {

}

//使用
import com.hencoder.plus.topLevelFunction //直接 import 函数
topLevelFunction()

命名相同的顶级函数 : 当出现两个同名顶级函数时,IDE 会自动加上包前缀来区分,这也印证了「顶级函数属于包」的特性。println()就是kotlin添加的一个顶级函数

  • const val:相当于Java中的 static final。不过 声明在对象(包括伴生对象)或者「top-level 顶层」中,只有基本类型和 String 类型可以声明成常量

6.范型

编码的时候用符号来指代类型,在使用的时候,再确定它的类型

6.1 Java中范型

TextView textView = new Button(context);//这是多态,编译器不会报错

List<Button> buttons = new ArrayList<Button>();
//由于类型擦除,多态用在这里会报错 incompatible types: List<Button> cannot be converted to List<TextView>
List<TextView> textViews = buttons;

...

//在 Java 里用数组做类似的事情,是不会报错的,这是因为数组并没有在编译时擦除类型
TextView[] textViews = new TextView[10];
Button[] buttons = new Button[10];
textViews = buttons;

不过Java 提供了「泛型通配符」 ? extends? super 来解决这个问题

  • ? extends
List<? extends TextView> textViews = new ArrayList<Button>();
  • ? super
List<? super Button> buttons = new ArrayList<TextView>();
Java中?相当于? extends Object

6.2 Kotlin中范型

  • Kotlin中out相当于Java中? extends
  • Kotlin中in相当于Java中? super
  • Kotlin中?相当于Java中?。即Kotlin中Class<*>相当于Java中Class<?>

7.高级语法特性

  • lambda表达式 : 接口只有一个回调的方法适合使用
    • lambda 表达式总是被大括号括着;
    • 其参数在 -> 之前声明(参数类型可以省略);
    • 函数体在 -> 后面
    • 唯一一个参数,可以省略大括号
    • 最后一个参数得时候,可以写到括号外
  fun positiveButton(
    @StringRes res: Int? = null,
    text: CharSequence? = null,
    click: DialogCallback? = null
  ): MaterialDialog {
      ...
  }
  //调用positiveButton()方法
  positiveButton(text = "确认") {
        //最后一个参数得时候,可以写到括号外
        //点击事件
   }
  
  
mView.setEventListener({
   data: Data ->
   //todo
})

//或者可以直接省略Data,借助kotlin的智能类型推导
mView.setEventListener({
   data ->
   //todo
})

//如果以上代码中的data参数没有使用到的话,可以直接把data去掉
mView.setEventListener({
  //todo
})

由于setEventListener函数最后一个参数是一个函数,可以直接把括号的实现提到圆括号外面
mView.setEventListener(){
   //todo
}

由于setEventListener这个函数只有一个参数,可以直接省略圆括号
mView.setEventListener{
  //todo
}

图片来自Kotlin:巧用内置函数let、also、with、run、apply大大提高开发效率!

  • let函数
    • 定义一个变量在一个特定的作用域范围内
     object.let{
        it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法
     ...
     }
    
    • 避免写一些判断null的操作
    //没有使用let函数的代码是这样的,看起来不够优雅
    mVideoPlayer?.setVideoView(activity.course_video_view)
    mVideoPlayer?.setControllerView(activity.course_video_controller_view)
    mVideoPlayer?.setCurtainView(activity.course_video_curtain_view)
    
    
    //使用let函数后的代码是这样的
    //表示mVideoPlayer不为null的条件下,才会去执行let函数体
    mVideoPlayer?.let {
       it.setVideoView(activity.course_video_view)
       it.setControllerView(activity.course_video_controller_view)
       it.setCurtainView(activity.course_video_curtain_view)
    }
    
  • inline关键字:作用是提升性能,适用于代码量不多但是需要频繁调用的方法。Kotlin的inline内联函数
//创建代码块只提供 Lollipop 或者更高版本来执行:
inline fun supportsLollipop(code: () -> Unit) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        code()
    }
}
//它只是检查版本,然后如果满足条件则去执行。现在我们可以这么做:
supportsLollipop {
    window.setStatusBarColor(Color.BLACK)
}
  • by lazy:懒加载,只有用到时才会对初始化,适用于只获取,不赋值,并且多次使用的对象。lateinit 只用于变量 var,而 lazy 只用于常量 val

    val lazyValue: String by lazy {
       println("computed!")
       "Hello"
    }
    
    fun main(args: Array<String>) {
       println(lazyValue)
       println(lazyValue)
    }
    
    打印结果
    computed!
    Hello
    
    Hello
    ``
    
  • run:作用域函数

    • 单独的作用域,run{ }代码块是独立的。即我可以在run()函数中写一些和项目无关的代码,因为它不会影响项目的正常运行
     fun test(){ 
         var animal = "cat" 
         run { 
             //在run函数中能够重新定义一个animal变量,和上面的变量不会冲突
             val animal = "dog" println(animal) // dog 
         } 
         println(animal) //cat 
     }
    
    • run函数当中它不仅仅只是一个作用域,他还有一个返回值。他会返回在这个作用域当中的最后一个对象
     //根据`islogin`的值返回不同的dialog并显示
     run {
         if (islogin) loginDialog else getAwardDialog
     }.show()
    
     val index = 3
     val num = run {
         //when关键字是一个有返回值的表达式
         when(index){
             0 -> "kotlin"
             1 -> "java"
             2 -> "php"
             3 -> "javaScript"
             else -> "none"
         }
     }.length
     println("num = $num")//3
    
    • 使用当前对象的上下文
     val str = "kotlin"
     str.run {
         println( "length = ${this.length}" )//length = 6
         println( "first = ${first()}")//first = k
         println( "last = ${last()}" )//last = n
     }
    
  • with函数: with函数接收一个 T 类型的对象和一个被作为扩展函数的函数。这个方法主要是让这个t对象去执行body函数

     inline fun <T> with(t: T, body: T.() -> Unit) { t.body() }
    
  • with函数中的参数是一个对象,我们可以带方法中直接引用对象的公有属性或者公有方法,而不用使用方法名
    和Java代码做对比

     TextView text=(TextView)findViewById(R.id.tv_text)
     text.setText("hahaha")
     text.setTextSize(23)
    
     with(tv_text){
        text="hahaha"
        textSize=23
     }
    
  • 返回值为函数块的最后一行或指定return表达式

    val result = with(user, {
         println("my name is $name, I am $age years old, my phone number is $phoneNum")
         1000
     })
     //但是由于with函数最后一个参数是一个函数,可以把函数提到圆括号的外部,所以最终with函数的调用形式如下:
    
     val result = with(user) {
         println("my name is $name, I am $age years old, my phone number is $phoneNum")
         1000
     }
    
  • apply函数 : 整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值

  • as操作符

      val y = 66
      //表示允许 String 可空,这样当 y = null 时,不会抛异常;但是,当类型转换失败时,还是会崩溃。直接用as不安全
      val x: String? = y as String?
    
      val y = 66
      //安全的 类型转换操作符 as?,当类型转换失败时,它会返回 null,但不会抛出异常崩溃
      val x: String? = y as? String
      println("x = $x")   // x = null
    

8.其他

  • 函数作为参数,T.()->Unit 和 ()->Unit 的区别
    在代码块里面写this的时候,T.()->Unit里的this代表的是自身实例,而()->Unit里,this代表的是外部类的实例。
    fun <T : View> T.afterMersure(f: T.() -> Unit){
    }
    
    fun <T : View> T.afterMersure2(f: () -> Unit){
    }
    

  • kotlin委托属性+SharedPreference实例

  • return@label

    return语句默认为跳出最近的函数

    //直接跳出foo函数
    fun foo(ints: List<Int>) {
        ints.forEach {
            if (it == 0) return
            println(it)
        }
    }
    

    如果需要在lambda表达式返回有两种方法

    • 把lambda表达式改为匿名函数
       fun foo() {
         ints.forEach(fun(value: Int) {
             if (value == 0) return 
             print(value)
         })
       }
      
    • 结合label使用
       fun foo() {
         ints.forEach lit@ {
             if (it == 0) return@lit
             print(it)
         }
       }