Kotlin入门学习

Kotlin学习

变量

val (value)的缩写,用它来申明一个不可变的变量 在初始化赋值后不可以再变

var (variable)的缩写, 用来申明一个可变的变量

package com.ybr.helloworld

fun main() {
    val a = 10 //会自动推导 为 整形变量 若 把字符串赋值给a 则a被推导为字符串
    println("a = " + a)
}

但是自动推导不是一直都可以 若延迟赋值的话,需要显示地声明变量类型

val a: Int = 10

类型关键字都为首字母大写 摒弃了java所有的数据类型

fun main() {
    val a: Int = 10
    a = a * 10
    println("a = " + a)
}//Error a为val 需把val 改为 var

val var 关键字是为了java中final关键字没有被合理使用的问题,编码建议:优先使用val申明一个变量,当val没有办法满足需求时候再使用var,这样的程序健壮性更高。

函数

与其他语言一样,kotlin也允许自用定义函数 语法规则如下

fun methodName(param1: Int, param2: Int) : Int {
    return 0
}

fun 是function的简写,用于自定义函数

methodName函数名字

参数格式 “参数名:参数类型 ” paeam1 : Int

函数之后是返回值 若不许要返回值 则可以不写

package com.ybr.helloworld

import android.os.Build
import androidx.annotation.RequiresApi
import java.lang.Integer.max


@RequiresApi(Build.VERSION_CODES.N)
fun largerNumber(num1: Int, num2: Int) : Int {
    return max(num1, num2)//max是自带的函数 区别大小 需要导入包
}

@RequiresApi(Build.VERSION_CODES.N)
fun main() {
    val a = 10
    val b = 34
    val value = largerNumber(a, b)
    println("larger Number is " + value)
}

语法糖用法:如largerNumber只有一句话 可以简化为

fun largerNumber(num1: Int, num2: Int) : Int = max(num1, num2)
//因为推导性 所以可以直接推导为Int形
fun largerNumber(num1: Int, num2: Int) = max(num1, num2)

逻辑控制

kotlin分为顺序语句 条件语句 和 循环语句

1. 条件语句 if

if与绝大多是语言的if没有任何区别

fun largerNumber(num1: Int, num2: Int) : Int {
    var value = 0//value需要重赋值 所以用var类型
    if (num1 > num2) {
        value = num1
    }else {
        value = num2
    }
    return value
}
//kotlin里的if可以有返回值 
//可以简化如下 因为value没有重写赋值的机会
//此时value就是一个不可变的变量
fun largerNumber(num1: Int, num2: Int) : Int {
    val value = if (num1 > num2) {
        num1
    } else {
        num2
    }
    return value
}

此时的value也是一个多余的变量 那么 可以直接将if语句返回

fun largerNumber(num1: Int, num2: Int) : Int {
    return if (num1 > num2) {
        num1
    } else {
        num2
    }
}

根据语法糖 return可以代替为= 所以可以简化为

fun largerNumber(num1: Int, num2: Int) : Int = if (num1 > num2) {
    num1
} else {
    num2
}

fun largerNumber(num1: Int, num2: Int) : Int = if (num1 > num2) num1 else num2//🐂哇🐂哇

2.条件语句 when

Kotlin中的when像是java中的switch 但是比switch强大的多

举个例子,此时定义一个getSorce函数用来查询成绩 if这样写

fun getScore(name: String) = if (name == "Tom") {
    86
} else if (name == "Jack") {
    77
}else if (name == "Jim") {
    66
}else if (name == "Lily") {
    100
}else {
    0
}

这种方法是最常用的方法,但是太过冗余,此时考虑when语句

fun getScore(name: String) = when (name) {
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 93
    "Lily" -> 100
    else -> 0
}

when 和 if 一样有返回值,所以可以使用语法糖

when可以传入任意类型的参数,然后再when中定义一系列的条件 格式是 匹配值 -> {执行逻辑} 当逻辑只有一行时候,可以省去{}

除此之外,when语句支持类型匹配 例如定义一个checkNumber函数

fun checkNumber(num: Number) {
    when (num) {
        is Int -> println("It's Int")
        is Float -> println("It's Float")
        else -> println("I don't know!")
    }
}//is相当于java中的instanceof关键字 接收一个Number类型的参数 
//这是kotlin中的一个抽象类 Int Float Double都是它的抽象类

when还有一种用法就是 when无参 这种用法是判断字符串或者对象是否相等可以直接使用==关键字 不用调用equals()方法,这样显得冗余 但是 有些情况必须用这种形式 如 所有Tom开头的人都是86分 用带参数的就没法写

fun getScore(name: String) = when {
    name.startsWith("Tom") -> 86
}

此时不管是传入tom 还是tommy都是86

3.循环语句

循环语句主要两种 for 和 while ,kotlin中的while和其他语言几乎一样,主要是for,在kotlin中舍弃了for-i的选项,选择了for-in的机制。

在kotlin中加入了区间的概念 如:val range = 0…10 就是[0,10]闭区间

fun main() {
    for (i in 0..10) {
        println(i)
    }
}//得到的是0-10 11个数 for in 会默认为自加1 如i++一样

如果需要一个左闭右开的去接 可以使用关键字until

fun main() {
    for (i in 0 until 10) {
        println(i)
    }
}//得到的是0-9 10个数

//若想跳过一些元素
fun main() {
    for (i in 0 until 10 step 2) {
        println(i)
    }
}//得到的是 0 2 4 6 8 五个数 像是i = i + 2

…和until都要求左边必须小于等于右边 这俩语法创建的都是升序 如果想要降序需要使用downTo关键字

fun main() {
    for (i in 10 downTo 1) {
        println(i)
    }
}//得到的是10 - 1 10个数

面向对象编程

1.类和对象

与其他面向对象语言一样, 首先创建类

class Person {
    var name = ""
    var age = 0

    fun eat() {
        println(name + " is eating. He is" + age + " years old.")
    }
}

实例化类

fun main() {
    val p = Person()
    p.name = "梁广汽"
    p.age = 22
    p.eat()
}//将new 关键字去除 直接实例化

2.继承与构造函数

面向对象的一个特性就是继承,因为kotlin有着C++的思想(我猜的),使得父类可以被继承,为什么是可以被继承???我认为这个好像C++的纯虚函数,子类如果继承且要实现子类的函数(重写),那么父类就不能够实例化对象,实例化了子类就完全成了父类的样子,就没意义了。

在kotlin中,任何一个非抽象的类都是不可以被继承的,其原理和val关键字差不多,类和变量一样最好都是可变的,一个类允许被继承,它无法预知子类如何实现,可能就会存在一些未知的风险。(一个类不是为继承而设计的,则就应该加上final,禁止被继承 --Java思想)默认非抽象类是无法被继承的,而抽象类无法创建实例,所以使其变为抽象类后再让子类继承,然后创建实例。

此时创建一个Student类 继承 Person类

open class Person {
    var name = ""
    var age = 0

    fun eat() {
        println(name + " is eating. He is" + age + " years old.")
    }
}//加open关键字 使其变为抽象类

class Student : Person (){
    var sno = ""
    var grade = 0
}//继承的person类需要加(),因为它还涉及主构造函数和次构造函数等方面

任何一个面向对象的语言都会有构造函数的概念,在kotlin中分为主构造函数和次构造函数

主构造函数没有函数体,直接定义在类名的后面

class Student(val sno : String, val grade = Int) : Person() {
}
val student = Student("a123", 5)//不需要重写赋值,直接实例化赋初始值,所以将参数全设为val型

如果要编写一些逻辑在主构造函数里 kotlin提供了init结构体

package com.ybr.helloworld

class Student(val sno : String, val grade : Int) : Person (){
    init {
        println("sno = " + sno)
        println("grade = " + grade)
    }
}
fun main() {
    val student = Student("qqq", 5)
}

子类的构造函数必须调用父类的构造函数,但是父类的构造函数也没有函数体,kotlin没有选择愚蠢的调用父类的init结构体,而是通过*****括号*****来指定父类无参的构造函数,而Person后面的括号是Student的主构造函数在初始化时候调用Person类的无参数构造函数,即使是无参数时候,这对括号也不能改变。

此时将父类构造函数修改

open class Person (val name : String, val age : Int) {
    init {
        println("name is " + name)
        println("age is " + age)
    }
}
//此时 Student类调用了Person的构造函数,但是优先调用的是无参构造函数,此时已经修改为有参数的构造函数 ,所以需要在Student类构造函数传入name 和 age
class Student(val sno : String, val grade : Int, name : String, age : Int) : Person (name , age) {
    init {
        println("sno = " + sno)
        println("grade = " + grade)
    }
}

fun main() {
    val student = Student("qqq", 5, "Jack", 22)
    student.eat()
}

以上就是主构构造函数,而一个类只能有一个构造函数,但是可以有若干个次构造函数,次构造函数也可以用来实例化一个类,次构造函数有函数体。当一个类有主构造函数和次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)

    constructor(name: String, age: Int) : this("", 0, name, age) {
        //这个次构造函数接收 name 和 age 通过this调用主构造函数将sno和 grade赋值为初始值 “” 和 0
    }
    
    constructor() : this("", 0) {
        //这个次构造函数不接收任何值,this调用了第一个次构造函数,此时将name 和 age 赋值为 “” 和 0 这个次构造函数间接调用了主构造函数 这也是合法的
    }

fun main() {
    val student = Student("qqq", 5, "Jack", 22)
    val student1 = Student("Tom", 33)
    val student2 = Student()
    student.eat()
    student1.eat()
    student2.eat()
}

特殊情况:类中只用次构造函数没有主构造函数,只有次构造函数

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

因为子类没有主构造函数,同时又定义了次构造函数(constructor),所以现在Student类没有主构造函数,所以继承Person就不需要加括号,只能调用父类的构造函数 此外调用父类主构造函数的this变成了super。

3.接口

接口时多态实现的重要组成部分,而java和kotlin一样时单继承模式,但是可以实现若干接口,所以先定义一个Study的接口

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

让student类继承person类并调用study接口

class Student(name : String, age : Int) : person(name, age), Study{
    override fun readBooks() {
        println(name + " is reading")
    }
    
        override fun doHomework() {
        println(name + " is doing homework ")
    }

    override fun eatFood() {
        println(name + " is eating")
    }
}

fun doStudy(study: Study) {
    study.readBook()
    study.doHomework()
    study.eatFood()
}

fun main() {
    val student = Student("lgq", 22)
    doStudy(student)
}

Kotlin中允许对接口定义的函数进行默认实现

interface Study {
    fun readBook()
    fun doHomework() {
        println("do homework dufault implementation")
    }
}

此时当调用一个接口的时候,需要强制使用radbook函数,而dohomework函数用不用都行,不用就会默认使用在定义时候的逻辑功能

4.数据类和单例

在一个规范的系统架构中,数据类通常占着非常重要的角色,他们用于服务器端或者数据库中的数据映射到内存中。一般的 MVC MVP MVM之类的构架模式 M就是数据类。数据类需要重写equals(), hashCode(), toString()方法,第一个时判断两个数据是否相等,第二个是配套第一个的 也需要重写,否则会导致HashMap HashSet等hash相关的系统类无法工作。最后一个用于提供更清晰的输入日志。创建一个手机类

data class Mobbile(val brand: String, val prance: Double)
//data关键字就是数据类 此时kotlin会将狗赞函数的参数将equals(), hashCode(), toString()自动生成,当类中没有任何代码 可以将{}去掉
fun main() {
    val mobbile1 = Mobbile("xiaomi", 4399.99)
    val mobbile2 = Mobbile("Apple", 9999.99)
    println(mobbile1)
    println(" mobbile1 equals mobbile2 " + (mobbile1 == mobbile2))
}

单例模式是最常用的设计模式之一,它可以用于避免创建重复的对象。比如某个类在全局只能有一个实例,这时就要用单例模式。

package com.ybr.helloworld

object Singleton {
    fun singletonTest() {
        println("singletonTest is called")
    }
}

fun main() {
    Singleton.singletonTest()
}

只需把class换成object即可创建一个单例类,调用像是静态调用,但是kotlin在背后自动创建了一个Singleton的实例,并且保证全局只存在一个Singleton实例。

Lambda编程

1.集合的创建和遍历

传统意义上的集合就是List和Set包括Map这样的键值对数据结构也可以包括进来这些接口都有对应

list实现类是ArrayList和LinkedList

Set实现类是HashSet

Map实现类是HanshMap

创建一个水果的集合

fun main() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Peach")
    for (fruit in list) {
        println(fruit)
    }
}

listof()是一个初始化集合的函数他是一个不可变的集合,也就是说我们在调用时无法进行增、删、查 如果想要有增删查的功能需要用 mutableListof()

fun main() {
    val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Peach")//初始化集合
    list.add("Watermelon")//增加元素
    for (fruit in list) {
        println(fruit)//for in 遍历
    }
}

set集合和list集合用法一样,但是set集合底层是使用hash映射来存放数据的,因此集合中元素不保证有序 setOf() 和 mutableSetOf()

而Map集合则是一种键值对的数据结构。传统的Map用法是先创建一个HashMap的实例,然后一个一个将键值对添加到Map中

    val list = hashMapOf<String, Int>()
    list.put("Apple", 1)
    list.put("Banana", 2)
    list.put("Orange", 3)
    list.put("Pear", 4)
    list.put("Peach", 5)//这样容易理解

事实上不建议这么写,不建议用get put方法来对list进行添加和读取,而是使用类似于数组下标的语法结构

    val list = hashMapOf<String, Int>()
    list["Apple"] = 1
    list["Banana"] = 2
    list["Orange"] = 3
    list["Pear"] = 4
    list["Peach"] = 5
//读取
	val number = list["Apple"]

Map保留了mapOf()和mutableMapOf()函数简化Map

    val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4)
    for ((fruit, number) in map) {
        println("fruit is " + fruit + " it's number "+ number)
    }

    val map = mutableMapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4)
    map.put("Watermelon", 4)
    for ((fruit, number) in map) {
        println("fruit is " + fruit + " it's number "+ number)
    }

2.集合的函数式API

需求:找到水果集合中最长的单词

val list = mutableSetOf("Apple", "Banana", "Orange", "Pear", "Peach")
val maxLengthFruit = ""
for(fruit in list) {
    if (fruit.length > maxLengthFruit) {
        maxLengthFruit = fruit
    }
}
println("maxLengthFruit is " + maxLengthFruit)

这么写逻辑清晰 但是可以使用API更加容易,此时引入Lambda表达式,它可以允许传入一小段代码

    val list = listOf("Apple", "Banana", "Orange", "Pear", "Peach", "Watermelon")
    val maxLengthFruit = list.maxBy { it.length }
    println("max length is " + maxLengthFruit)

Lambda表达式格式:{参数名1: 参数类型, 参数名2:参数类型 -> 函数体}

接下来将解析此api表达式 首先用lambda表达式的结构改写上述代码

    val list = listOf("Apple", "Banana", "Orange", "Pear", "Peach", "Watermelon")
	val lambda = {fruit : String -> fruit.length}
	val maxLengthFruit = list.maxBy(lambda)

简化策略:1.不需要专门定义lambda

val maxLengthFruit =list.maxBy({fruit : String -> fruit.length})

2.当lambda参数是函数最后一个参数时,可以将表达式移到括号外

val maxLengthFruit = list.maxBy(){fruit:String -> fruit.length}

3.当lambda参数是函数的唯一参数时候可以将括号省去,因为有推导所以不需要参数类型

val maxLengthFruit = list.maxBy{fruit -> fruit.length}

4.当lambda的参数列表只有一个参数,不需要申明参数名 可以用it代替

val maxLengthFruit = list.maxBy { it.length }

补:toUpperCase() 大写 toLowerCase() 小写

常用函数API -filter函数 : 过滤集合中数据

fun main() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Peach", "Watermelon")
    val newList = list.filter { it.length <= 5 }
                      .map {it.toUpperCase()}
//    println("max length is " + maxLengthFruit)
    for (fruit in newList) {
        println(fruit)
    }//过滤小于等于5个字母的单词 且 变为大写 先调用filter再调用map效率高,因为map存在映射关系 先过滤再映射快捷

常用函数API-----any() all()

any() : 用来判断集合中是否在在一个元素满足指定条件

all() : 用来判断集合是否所有元素都满足指定条件

    val list = listOf("Apple", "Banana", "Orange", "Pear", "Peach", "Watermelon")
    val anyResult = list.any{ it.length <= 5}
    val allResult = list.all { it.length <= 5 }
    println("anyResult is " + anyResult + " allResult is " + allResult)

3.java函数式API的使用

再kotlin中调用java方法也可以使用函数API,但是有限制,如果在kotlin中调用了一个java方法,且该方法接收一个Java单抽像方法的接口参数(接口中只有一个待实现方法),就可以使用函数式API,否则不行。

举例 java原生API中的 Runnable接口 这个接口中只有一个run()方法

package com.ybr.helloworld;

public interface Runnable {
    void run();
}

这个方法主要是要结合线程一起用的因此需要继承Thread类 而子线程方法实现

new Thread (new Runnable() {
    @Override
    public void run() {
        System,out.println("Thread is running");
    }
}).start()

如果将上述java代码直接翻译为kotlin

Thread(object : Runnable {
    override fun run() {
        println("Thread is running")
    }
}).start()

这个时候有个注意的事情就是 Kotlin中匿名类的写法,实例化不用new而用object 目前Thread类的构造方法是符合Java函数式的API使用条件的

//简化1
Thread(Runnable {
    println("Thread is running")
}).start
//因为runnable只有一个未实现的方法 而且也没有显示的重写runnable方法,根据推断性 kotlin自动推断出后面的Lambda表达式要实现run的功能

//简化2
Thread({
    println("Thread is running")
}).start()
//如果一个Java方法的参数列表中不存在一个以上java单抽像的方法接口参数,可以将接口名省略

//简化3
Thread {
    println("Thread is running")
}.start()
//当lambda表达式是方法唯一一个参数 可以将方法的括号省略

API使用限定都是从kotlin调用Java方法,并且单抽像方法接口也必须是Java语言定义的。

空指针检查

空指针是一种不受编程语言检查的允许时异常,只能通过人主动通过逻辑判断来避免 如果以下的例子

public void doStudy(Study study) {
    study.readBooks();
    study.doHomework();
}

这段代码不一定安全,万一传入doStudy方法一个null参数,这里就会出现空指针异常 因此在传参之前需要做一个判断空处理

public void doStudy(Study study) {
    if(study != null){
    	study.readBooks();
    	study.doHomework();
    }
}

这样就万无一失了

1.可空类型系统

Kotlin利用编译时的判空检查杜绝了空指针异常,还是上述的代码

fun doStudy(study: Study) {
    study.doHomework()
    study.readBooks()
    study.eatFood()
}

实际上这段代码时没有任何空指针风险的 如果强传一个null

Kotlin利用编译时的判空检查提前检查到了空指针,只有修复才能成功运行

如果需要的业务逻辑中需要传一个空,Kotlin也有一套可空的类型系统,就是在类名后面加?如Int? 就是可控的整形 String?就是可空的字符串

此时需要调用参数的 readBooks() doHomework() eatFood() 都空指针异常了 此时需要做判断处理

fun doStudy(study: Study?) {
    if (study != null) {
        study.doHomework()
        study.readBooks()
        study.eatFood()
    }
}

2.判空辅助工具

因为这种可空类型系统需要很多if判断是否为空 所以Toklin提供了一系列的赋值工具

(1)?. 操作符 对象不为空正常调用方法,为空啥也不干

if (a != null) {
    a.doSomething()
}

//等同于

a?.doSomething()

所以上面的代码可以修改为

fun doStudy(study: Study?) {
    study?.doHomework()
    study?.readBooks()
    study?.eatFood()
}

(2) ?: 操作符 有点像三目运算符 左右两边都接受一个表达式 左边表达式不为空就返回左边 为空就返回右边

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

//等同于
val c a :? b

例子:编写一个函数用来获得一段文字的长度

fun getTextLegth (text : String) {
    if (text != null) {
        return text.length
    }
    return 0 
}
//等同于
fun getTextLegth(text : String?) = text ?. length ?: 0

(3)let函数

let函数提供了函数式的API接口,并将调用的对象作为参数传递到lambda表达式中

obj.let { obj2 -> 
        //函数具体逻辑
}//obj是对象  所以上述代码可以改为

fun doStudy(study : Study?) {
    study?.let{
        it.readBook()
        it.doHomework()
    }
}

let是一个可以判断全局变量的判空问题的,if无法做到因为如果是全局,该处值随时可以被其他线程所修改,即使做了判空处理,也没法保证if中的不是空指针

var study : Study? = null

fun doStudy(study: Study?) {
    study?.let {
        it.eatFood()
        it.readBooks()
        it.doHomework()
    }
}

fun main() {
    val student = Student("qqq", 5, "Jack", 22)
    student.eat()
    doStudy(student)
}

生活小妙招

1.字符串内嵌表达式

语法规则 :“Hello, ${obj.name}. nice to meet you!”

当表达式中只有一个变量的时候,可以省略大括号 : “Hello, $name. nice to meet you!”

    val brand = "xiaomi"
    val prance = 4399
    println("Mobbile(brand ="+ brand +", price = " + prance + ")")
//使用内嵌表达式
	println("Mobbile( brand=$brand, prance=$prance)")

2.函数参数的默认值

函数参数默认值,在定义函数的时候给任意参数设定一个默认值,这样当调用函数时不会强制要求调用方为此参数传值,在没有传值的情况下会自动使用参数的默认值。

fun printParams(num: Int, str: String = "Hello") {
    println("num is $num,str is $str")
}
fun main() {
    printParams(111)
}

当第一个参数也有默认值时候,就是俩都有默认值,此时只想使用第一个的默认值直接使用该函数,而这个可以通过键值对的方式来传参

printParams(str = "sss", num = 100)

fun printParams(num: Int = 100, str: String = "Hello") {
    println("num is $num,str is $str")
}
fun main() {
    printParams(str = "World")
}

感谢《第一行代码Android(第3版)》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值