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版)》