天天看点

Kotlin 开发Android笔记之Kotlin学习篇

Kotlin 开发Android笔记之Kotlin学习篇

var与val 变量标志

val :声明一个不可变的变量,在初始化赋值后不可再改变值,相当于Java中的final类型变量数据

var:声明一个可变的变量,在初始化赋值后还可修改值,相当于Java中的非final类型变量数据

初始化:

//隐式声明变量类型 kotlin可自动推导判断变量类型
var value = 10
//显示声明变量类型
var valueStr:String = "Kotlin"
           

PS:在Android中对一个变量延迟赋值时,Kotlin就无法自动推导变量类型了,此时我们就需要显示的去声明变量类型才行

在Android开发中什么时候用val,什么时候用var呢?

PS:永远优先使用val来声明一个变量,而当val没有办法满足需求时在使用var。这样设计出来的程序会更加的健壮,也更加符合高质量的编码规范。

方法简写
/**
 * 判断两个值大小
 */
fun largerNumber(num1:Int,num2:Int):Int{
    return max(num1,num2)
}
           

简写后:

/**
 * 判断两个值大小
 */
fun largerNumber(num1:Int,num2:Int)= max(num1,num2)
           

由于max会返回Int,所以我们可以省略return和大括号已经返回值类型,kotlin会自动推导返回值类型是Int

if条件控制
/**
 * 自己编写的判断大小方法
 */
fun largerNumber1(num1: Int,num2: Int):Int{
    var value=0;
    if (num1>num2){
        value = num1
    }else{
        value = num2
    }
    return value
}
           

上面是自己利用if条件判断编写的判断两个数大小方法,由于value先赋初值为0,后面再赋大值,所以用var定义。此时方法和Java实现一样。

由于kotlin中的if是可以有返回值的,所以上面代码可以修改为:

/**
 * 自己编写的判断大小方法
 */
fun largerNumber1(num1: Int,num2: Int):Int{
    val value= if (num1>num2){ 
        num1
    }else{
       num2
    }
    return value
}
           

由于kotlin中的if有返回值,所以此处value值赋值了一次,用val声明。看到这里我们发现还可以简写,不要value。

/**
 * 自己编写的判断大小方法
 */
fun largerNumber1(num1: Int,num2: Int):Int{
    return if (num1>num2){
        num1
    }else{
        num2
    }
}
           

上面的代码我们发现既然返回的值是Int,Kotlin完全可以自己推导值类型,可以去掉return了。最后就变为了:

/**
 * 自己编写的判断大小方法
 */
fun largerNumber1(num1: Int,num2: Int)=if (num1>num2){
    num1
}else{
    num2
}
           
when条件控制

Kotlin中的when条件控制语句类似于Java中的switch。

格式:

/**
 * when条件语句
 */
fun getScore(name:String) = when(name){
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 98
    "Lily" -> 87
    else -> 0
}
           

不带参数的when条件控制:

/**
 * when条件语句,不带参数
 * 本方法可以用来查询所有以Tom开头的人的分数
 */
fun getScore(name:String) = when{
    name.startsWith("Tom") -> 86
    name == "Jim" -> 77
    name == "Jack" -> 98
    name == "Lily" -> 87
    else -> 0
}
           
循环语句

Kotlin中的while语句和 Java中完全一致。

for循环,kotlin完全舍弃了Java中的for-i用法,而对Java的for-each进行了加强变成了for-in。

Kotlin中的闭区间表示…:

for (i in 0..10){
        print(i)
    }
           

Kotlin的左闭右开区间until:

Kotlin的步长step:

val value = 0 until 10 //值为:0,1,2,3,4,5,6,7,8,9
    for (i in value step 2){ //步长为2,输出0,2,4,6,8
        println(i)
    }
           

Kotlin的降序downTo:

for (i in 10 downTo 1){ //downTo降序[10,1],每次减1,输出10,9,8,7,6,5,4,3,2,1
        println(i)
    }
           
for (i in 10 downTo 1 step 2){ //downTo降序[10,1],指定步长为2,输出10,8,6,4,2
        println(i)
    }
           
Kotlin中类的继承规则

kotlin中默认类是不可以被继承的,类似于类默认是final的。

class Person {
    var name = ""
    var age = 0
    fun eat(){
        println("$name is eatting. He is $age year old .")
    }
}
           

要想上面的Person类可以被继承,我们需要在class的前面加上open关键字

open class Person {
    var name = ""
    var age = 0
    fun eat(){
        println("$name is eatting. He is $age year old .")
    }
}
           

继承实现:

class Student :Person(){
    var sno = ""
    var grade = 0

}
           
Kotlin 的主构造函数,次构造函数

主构造函数: 每个类都会默认有一个无参数的主构造函数,当然我们也可以直接显式的指明参数。主构造函数的特点是没有函数体,直接定义到类后面即可。

class Student(val sno:String,val grade:Int) :Person(){
}
           

上面的Student类定义了一个带参数的主构造函数,继承于Person类。在Student中,我们把类原有的类成员变量直接放到了主构造函数里。调用时必须如此:

由于主构造函数没有函数体,我们需要在主构造函数里面编写逻辑的时候可以用init结构体,所有主构造函数的逻辑都可以写到这个结构体里面

class Student(val sno:String,val grade:Int) :Person(){
    //init结构体,所有主构造函数的逻辑都写在这里
    init {
        println("sno is $sno")
        println("grade is $grade")
    }
}
           

次级构造函数: Kotlin类中只能有一个主构造函数,但可以有多个次级构造函数,当一个类中既有主构造函数又有次级构造函数时,所有的次级构造函数都必须调用主构造函数。

class Student(val sno:String,val grade:Int,name: String,age:Int) :Person(name,age){
    //init结构体,所有主构造函数的逻辑都写在这里
    init {
        println("sno is $sno")
        println("grade is $grade")
    }
    //次级构造函数1,接收name与age两个参数,并通过this关键字调用了主构造函数,给sno与grade赋了初值
    constructor(name: String,age: Int) : this("",0,name,age){
        
    }
    //次级构造函数2,不接收任何参数,但调用了主构造函数
    constructor() : this("",0){
        
    }
}
           

上面的Student类有一个主构造函数,两个次级构造函数。这样我们就有3种方式去对类实例化。

var student1 = Student()  //不带参数的实例化,利用的次级构造函数2
    var student2 = Student("Tom",18) //带name和age两个参数,利用的是次级构造函数1
    var student3 = Student("a10001",98,"Tom",19)
           

当类中只有次级构造函数,没有主构造函数时:

class Student:Person{
    constructor(name: String,age: Int) : super(name,age){
    }
}
           

上面的Student类的后面没有显示的定义主构造函数,在继承Person类时,就不需要Person类后的()了。由于没有主构造函数,次级构造函数只能直接调用父类的构造函数,所以把this关键字换成了super。这里和Java类似。

Kotlin的接口

Kotlin只能继承一个类,可以实现多个接口。Kotlin接口中不需要函数体

interface Study {
    fun readBooks()
    fun doHomework()
}
           

Kotlin中实现接口用逗号隔开即可

class Student(val sno:String,val grade:Int,name: String,age:Int) :Person(name,age),Study{
    //init结构体,所有主构造函数的逻辑都写在这里
    init {
        println("sno is $sno")
        println("grade is $grade")
    }
    //次级构造函数1,接收name与age两个参数,并通过this关键字调用了主构造函数,给sno与grade赋了初值
    constructor(name: String,age: Int) : this("",0,name,age){

    }
    //次级构造函数2,不接收任何参数,但调用了主构造函数
    constructor() : this("",0){

    }

    override fun readBooks() {
        println("$name is reading .")
    }

    override fun doHomework() {
        println("$name is doing homework .")
    }
}
           

调用:

var student3 = Student("a10001",98,"Tom",19)
    doStudy(student3)
/**
 * 接口调用测试
 */
fun doStudy(study: Study){
    study.readBooks()
    study.doHomework()
}
           

输出为:

Tom is reading .
Tom is doing homework .
           

接口的默认实现。就是在定义接口时候就给接口里面的方法添加默认的实现,这样在类实现该接口的时候,接口已经实现默认的函数就可实现可不实现。

interface Study {
    fun readBooks()
    /**
     * 接口 方法doHomework添加了默认实现,类在实现本接口的时候doHomework不是必须的
     */
    fun doHomework(){
        println("do homework default implementation .")
    }
}
           

此时Student类可以选择不去重新doHomework()方法。

class Student(val sno:String,val grade:Int,name: String,age:Int) :Person(name,age),Study{
    //init结构体,所有主构造函数的逻辑都写在这里
    init {
        println("sno is $sno")
        println("grade is $grade")
    }
    //次级构造函数1,接收name与age两个参数,并通过this关键字调用了主构造函数,给sno与grade赋了初值
    constructor(name: String,age: Int) : this("",0,name,age){

    }
    //次级构造函数2,不接收任何参数,但调用了主构造函数
    constructor() : this("",0){

    }

    override fun readBooks() {
        println("$name is reading .")
    }

}
           

输出为:

Tom is reading .
do homework default implementation .
           
Ktolin可见性修饰符

private: 和Java一样,只对当前类内部可见

public: 默认值,默认是public的,所有类都可见

protected: 和Java不一样,只表示对当前类和子类可见

internal: 只对同一模块下的类可见

Kotlin数据类

kotlin中标明数据类的关键字是data,当一个类利用关键字data修饰时就代表这是数据类,kotlin会自动把equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成。

/**
 * Cellphone 数据类
 * 包含品牌和价格两个字段
 */
data class Cellphone(val brand:String,val price:Double)
           

调用:

val cellphone1 = Cellphone("Samsung",1223.54)
    val cellphone2 = Cellphone("Oppo",5999.54)
    println(cellphone1)
    println("cellphone1 equals cellphone2:"+(cellphone1 == cellphone2))
           

输出:

Cellphone(brand=Samsung, price=1223.54)
cellphone1 equals cellphone2:false
           
Kotlin单例模式

单例模式,可以避免创建重复的对象。Kotlin中用object来标明该类是单例类

/**
 * object关键字用于类的单例模式实现,声明该类为单例类
 */
object Singleton {
    /**
     * 在单例类中自定义的函数,用于编写自己的实现
     */
    fun singletonTest(){
        println("singletonTest is called.")
    }
}
           

调用:

输出:

Kotlin List集合

listOf,创建的为不可变的List集合,就是我们没法对该集合进行增删改查

val list  = listOf("Apple","Banana","Orange","Pear","Grape") //定义集合
    for(fruit in list){ //遍历集合
        println(fruit)
    }
           

mutableListOf,定义可变List集合,能对集合进行增删改查

var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape") //可变集合
    list1.add("Watermelon")
    for(fruit in list1){ //遍历集合
        println(fruit)
    }
           

同理创建Set集合也是如此:setof、mutableSetOf

Kotlin Map集合

var map = mutableMapOf("Apple" to 1,"Banana" to 2,"Orange" to 3,"Pear" to 4,"Grape" to 5) //定义可变集合
    map["Watermelon"] = 6 //添加数据
    for ((fruit,number) in map){ //遍历集合
        println("fruit is $fruit ,number is $number")
    }
           

集合的函数式API

Lambda: 是一小段可以作为参数的代码。

Lambda表达式的语法结构:

首先最外层是一对大括号,如果有参数传入我们需要声明参数列表。参数列表的结尾使用一个->符号表示参数列表的结束以及函数体的开始,函数体中可以编写任意代码,并且最后一行代码会自动作为Lambda表达式的返回值。

查找列表中水果名最长的水果:

var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape") //可变集合
    list1.add("Watermelon")
    val lambda = {fruit:String -> fruit.length} //根据Lambda表达式数据结构定义Lambda表达式
    val maxLength = list1.maxBy(lambda)
           

输出:

Watermelon
           

继续简化:

var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape") //可变集合
    list1.add("Watermelon")
    val maxLength = list1.maxBy({fruit:String -> fruit.length})
           

如果Lambda参数是函数的最后一个参数的话,可以把Lambda表达式移到函数括号外面:

如果Lambda参数是函数的唯一一个参数的话,可以省略函数的括号:

由于Kotlin有自己的类型推导机制,所以可以省略参数类型声明:

最后当Lambda表达式的参数列表中只有一个参数时,不必声明参数名,可以用it关键字来代替

map函数: 集合中的map函数时最常用的一种函数式API,它用于将集合中的每一个元素都映射成一个另外的值,映射的规则就是Lambda表达式中指定,最终生成一个新的集合。

实例:我们把list中的水果名都变成大写:

var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape","Watermelon") //可变集合
    val newList = list1.map{it.toUpperCase()} //将每个值都变为大写
    for(fruit in newList){ //遍历新集合
        println(fruit)
    }
           

输出:

APPLE
BANANA
ORANGE
PEAR
GRAPE
WATERMELON
           

这里只是通过map转换成大写而已,还可以小写,取首字母等。只要是映射成新的集合,都可以,而转换规则自己通过Lambda来写

filter函数: 是用来过滤集合中的数据的,可以单独使用,也可以配合map函数一起使用。

实例:保留5个字母以内的水果:

var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape","Watermelon") //可变集合
    val newList = list1.filter { it.length<=5 }.map{it.toUpperCase()} //只保留五个字母以内的水果,并将每个值都变为大写
    for(fruit in newList){ //遍历新集合
        println(fruit)
    }
           

输出:

APPLE
PEAR
GRAPE
           

any和all函数: any函数用于判断集合中是否至少存在一个元素满足指定条件。all函数用于判断集合中是否所有元素都满足指定条件。

var list2 = mutableListOf("Apple","Banana","Orange","Pear","Grape","Watermelon") //可变集合
    val anyResult = list2.any{it.length<=5} //列表中是否所有元素都存在5个以内的单词
    val allResult = list2.all { it.length<=5 } //列表中是否所有所有单词都在5个字母以内
    println ("anyResult is $anyResult , allResult is $allResult")
           

输出:

JAVA函数式API的使用

Kotlin调用Java函数式API的限制:接口中只有一个待实现的方法,如果接口中有多个待实现的方法,就无法使用函数式API。

如接口Runnable:

.public interface Runnable{
	void run();
}
           

java 的调用:

new Thread(new Runnable(){
@Override
public void run(){
//................
}
}),start();
           

上述代码翻译成Kotlin代码实现:

Thread(object :Runnable { 
      override fun run(){
          //,,,,  
      }
    }).start()
           

上面的Runnable接口满足JAVA函数式API,下面就来简写:

因为只有一个待实现方法,即使不重写run()方法一样的。

Thread(Runnable {
      //接口方法执行逻辑
    }).start()
           

如果一个Java方法的参数列表中不存在一个以上Java单抽象方法接口参数,可以将接口名省略。

Thread({
      //接口方法执行逻辑
    }).start()
           

当Lambda表达式是方法的最后一个参数时,可以将表达式移到方法括号后面:

Thread(){
        //接口方法执行逻辑
    }.start()
           

同时,如果Lambda表达式是方法唯一的一个参数,可以省略方法括号:

Thread{
        //接口方法执行逻辑
    }.start()
           

如OnClickListener点击事件接口.。

我们就可以写成:

textView.setOnClickListener { 
            
        }
           

Kotlin空指针检查

判空辅助性操作符:?.

一般的判空操作:

/**
 * 接口调用测试
 */
fun doStudy(study : Study?){
    if (study!=null) {
        study.readBooks()
        study.doHomework()
    }
}
           

使用?.操作符后,就可以省略if条件判断了:

/**
 * 接口调用测试
 */
fun doStudy(study : Study?){
        study?.readBooks()
        study?.doHomework()
}
           

判空辅助性操作符:?:

这个操作符的左右两边都接收一个表达式,如果左边表达式的结果不为空,就返回左边表达式的结果,否则就返回右边表达式的结果。

val c =if (a!=null){
       a
   }else{
       b
   }
           

上面代码用?:操作符就是:

val c =a ?: b
           

实例:获取文本的长度

/**
 * 计算文本长度
 */
fun getTextLength(text: String?): Int{
    if (text!=null){
        return text.length
    }
    return 0
}
           

使用?,和?:操作符后:

/**
 * 计算文本长度
 */
fun getTextLength(text: String?): Int{
    return text?.length ?: 0
}
           

非空断言操作符 !! 。需要慎用

判空函数let:

表达式:

obj.let{
	obj2 -> 
	//编写业务逻辑
}
           

上面的obj和obj2 是同一个对象,这里只是作为区分而已。obj调用了let函数,然后Lambda表达式中的代码就是立即执行,并且obj对象还会作为参数传递到Lambda表达式中。如:

/**
 * 接口调用测试
 */
fun doStudy(study : Study?){
        study?.readBooks()
        study?.doHomework()
}
           

换成let函数就是:

/**
 * 接口调用测试
 */
fun doStudy(study : Study?){
        study?.let { stu ->
            stu.readBooks()
            stu.doHomework()
        }
}
           

当study通过?.操作符判断不为空时,就会立即执行Lambda表达式里面的逻辑,这样就不用每次都去判断当前对象是否为空,并且对全局对象也适用。由于Lambda表达式只有一个参数,可以不用声明参数名,并用it代替:

/**
 * 接口调用测试
 */
fun doStudy(study : Study?){
    study?.let { 
        it.readBooks()
        it.doHomework()
    }
}
           
函数的参数默认值
           

在定义函数时,给函数的参数设定一个默认值,这样在调用的时候对已经设定默认值的参数选择可传值,也可不传值:

/**
 * 定义函数时,给函数的参数设定一个默认值
 */
fun printText(num:Int,str:String="Hello"){
    println("num is $num , str is $str")
}
           

定义printText函数,给第二个参数设定了一个默认值,调用:

输出:

num is 123 , str is Hello
           

键值对传值

/**
 * 定义函数时,给函数的第一个参数设定一个默认值,第二个参数不设默认值,
 * 此时就要在调用时通过键值对的形式传值,这样数据就不会错乱
 */
fun printText(num:Int=100,str:String){
    println("num is $num , str is $str")
}
           

正确的调用

错误的调用:

//会默认把值赋给第一个参数,这样就是导致问题
printText("Hello")
           
标准函数with、run和apply

with函数: 接收两个参数,第一个参数是任意类型的对象,第二个参数是Lambda表达式。Lambda表达式的最后一行作为返回值

val result = with(obj){
        //这里是obj的上下文
        "value" //这里是返回值
    }
           

作用: 连续调用同一个对象的多个方法,精简代码

例如:遍历拼接水果列表

val list  = listOf("Apple","Banana","Orange","Pear","Grape") //定义集合
val builder = StringBuilder()
    builder.append("Start eating fruits. \n")
    for (fruit in list){
        builder.append("$fruit \n")
    }
    builder.append("Ate all fruits.")
    val result = builder.toString()
    println(result)
           

输出:

Start eating fruits. 
Apple 
Banana 
Orange 
Pear 
Grape 
Ate all fruits.
           

利用with函数简化代码:

val list  = listOf("Apple","Banana","Orange","Pear","Grape") //定义集合
val result = with(StringBuilder()){
        append("Start eating fruits. \n")
        for (fruit in list){
            append("$fruit \n")
        }
        append("Ate all fruits.")
        toString()
    }
    println(result)
           

上面的代码减少了对builder对象的多次调用,输出:

Start eating fruits. 
Apple 
Banana 
Orange 
Pear 
Grape 
Ate all fruits.
           

run函数:作用和with函数类似,不过语法改变了:

val result = obj.run{
        //这里是obj的上下文
        "value" //这里是返回值
    }
           

上面的实例用run函数写就是这样

val result = StringBuilder().run{
        append("Start eating fruits. \n")
        for (fruit in list){
            append("$fruit \n")
        }
        append("Ate all fruits.")
        toString()
    }
    println(result)
           

apply函数: 和run函数类似,只不过无法指定返回值

val result = obj.apply{
        //这里是obj的上下文
     
    }
  
           

上面的实例修改为:

val result = StringBuilder().apply{
        append("Start eating fruits. \n")
        for (fruit in list){
            append("$fruit \n")
        }
        append("Ate all fruits.")
    }
    println(result.toString())
           

上面的三个函数可以用于页面跳转时的数据传递:

val intent = Intent(this,FirstActivity::class.java)
            intent.putExtra("name","Tom")
            intent.putExtra("age",18)
            startActivity(intent)
           

上面的代码可以修改为:

val intent = Intent(this,FirstActivity::class.java).apply {
                putExtra("name","Tom")
                putExtra("age",18)
            }
            startActivity(intent)