Kotlin简介
如何运行Kotlin代码
? ?
三种运行方法
- 使用IntelliJ IDEA。
这是JetBrains的旗舰IDE开发工具,对Kotlin支持得非常好。在IntelliJ IDEA里直接创建-一个Kotlin项目,就可以独立运行Kotlin 代码了。缺点是还要再下载安装-一个IDE工具,有点麻烦,因此这里我们就不使用这种方法了。 - 第二种方法是在线运行Kotlin代码。
为了方便开发者快速体验Kotlin编程, JetBrains 专门提供了一个可以在线运行Kotlin 代码的网站,地址是: htps://try.kotlinlang.org。缺点国内网路慢,也不使用。 - 第三种方法是使用Android Studio。
遗憾的是, Android Studio作为一个专门用 于开发Android 应用程序的工具,只能创建Android项目,不能创建Kotlin项目。但是没有关系,我们可以随便 打开一个Android项目,在里面编写一个Kotlin 的main()函数,就可以独立运行Kotlin代码了。
在AS中运行kotlin文件
创建kotlin文件
HelloWorld—app—scr—main—java—comexample.helloworld右键New—Kotlin File/Class—输入选择下图
编写main函数
shift+F10
编程之本:变量与函数
变量
用来声明一个不可变的变量,在初始值赋值后就再也不能重新赋值,对应java中的final变量。
用来声明一个可变的变量,在初始赋值之后仍然可以再被重新赋值,对应Java中的非final变量。 Kotlin拥有出色的类型推导机制
但是Kotlin的类型推导机制并不总是可以正常工作的,比如说如果我们对一个变量延迟赋值的话,Kotin就无法自动推导它的类型了。这时候就需要显式地声明变量类型才行,Kotlin 提供了对这一功能的支持,语法如下所示: val a: Int = 10
可以看到,我们显式地声明了变量a为Int类型,此时Kotlin 就不会再尝试进行类型推导了。如果现在你尝试将一个字符串赋值给a,那么编译器就会抛出类型不匹配的异常。 Java中int,Kotlin中Int* Kotlin 完全抛弃了Java中的基本数据类型,全部使用了对象数据类型。 在Java中int是关键字 在Kotlin中Int是一个类,它拥有自己的方法和继承结构
例题,a*10
使用val和var的时机 永远优先使用val来声明一个变量,当val没有办法满足你的需求时再使用var。这样设计出来 的程序会更加健壮,也更加符合高质量的编码规范。
函数
函数是用来运行代码的载体 是程序的入口函数,程序一旦运行,就是从main()函数开始执行的。 函数语法规则:
fun methodName (paraml: Int, pa ram2:Int): Int{
return 0
}
fun(function)定义函数的关键字,无论定义什么函数都要fun来声明。
fun(关键字) 函数名(参数名:参数类型, 参数名:参数类型):函数返回类型{函数体
return 0
}
不想接受任何参数,不需要返回任何数据就写一对空括号就可以。 返回类型如不需要可以不写。
- 定义了一个名叫largerNumber()的函数,作用是接收两个整形参数,总是返回两个参数中最大的那个数。(largerNumber()只是对max()做了一个封装)*
fun largerNumber(numl: Int, num2: Int): Int {
return max (numl, num2)
}
import kotlin.math.max AS拥有非常智能的代码提示和补全功能。
fun main() {
val a = 37
val b = 40
val value = largerNumber(a, b)
println("larger number is " + value)
}
fun largerNumber(num1: Int, num2: Int): Int {
return max(num1, num2)
}
运行过程
- 定义a,b两个变量。a=37,b=40,然后调用largerNumber()函数,并将a、b作为参数传入。
- largerNumber()函数会返回最大的那个数,然后打印出来。
Kotlin语法糖 *
当函数中只有一行代码时Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数定义的尾部,中间等号连接。 例如上题就可以简化成
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
这种语法节省了return关键字,等号足以表达返回值的意思。
由于max()函数返回的是一个Int值,而在largerNumber()函数的尾部又使用等号连接了max()函数,因此Kotlin可以推导出largerNumber()函数返回的必然也是一个Int值,也不用再显式地声明返回值类型了。如下
fun largerNumber(num1: Int, num2: Int) = max(num1, num2)
语法糖可以结合Kotlin的其他语言特性一起使用,对简化代码方面帮助很大。
程序的逻辑控制
程序的执行语句主要分为三种
1.if条件语句
fun largerNumber (numl: Int, num2: Int): Int {
var value = 0
if (numl > num2) {
value = numl
} else {
value = num2
}
return value
}
kotlin中的if有一个额外的功能,它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。
`fun largerNumber (numl: Int, num2: Int): Int {
val value = if (numl > num2) {
numl
} else {
num2
}
return value
}
fun largerNumber (numl: Int, num2: Int): Int {
return if (numl > num2) {
numl
} else {
num2
}
}
fun largerNumber (numl: Int, num2: Int) = if (numl > num2) numl else num2
2.when条件语句
有点类似于java中的switch,但远比switch语句强大的多。
- 编写一个查询考试成绩的功能,输入一个学生的姓名,返回该学生考试的分数。(if)
fun getScore(name:String) = if (name == 'Tom'){
86
}else if(name == 'Jim'){
77
}else if(name == 'Jack'){
95
}else if(name == 'Lily'){
100
}else {
0
}
运行过程
- 定义一个getScore()函数,这个函数接受一个学生姓名参数,然后通过if判断找到该学生的分数并返回。(使用了很常用的kotlin语法糖)
fun getScore(name:String) = when(name){
"Tom" -> 86
"Jim" ->77
"Jack" ->95
"Lily" ->100
else -> 0
}
函数体直接=,用了语法糖
when格式: 匹配值 -> { 执行逻辑 } 当你的执行逻辑只有一行代码时,{}可以省略。 除精确匹配之外,when语句还允许类型匹配。
fun checkNumber(num:Number){
when (num){
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
is——类型匹配的核心
由于checkNumber()函数接受一个Number类型的参数(kotlin内置的一个抽象类),像Int、Long、Float、Double等与数字相关的类都是它的子类。
- 尝试在main()函数中调用checkNumber()函数
fun main(){
val num = 10
checkNumber(num)
}
- 实际运行效果
when还有一种不带参数的用法,虽然这种用法可能不太常用,但有的时候却能发挥很强的扩展性。
when还有一种不带参数的用法,虽然这种用法可能不太常用,但有的时候却能发挥很强的扩展性。
fun getScore(name: String) = when {
name == "Tom" -> 86
name == "Jim"-> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
可以看到,这种用法是将判断的表达式完整地写在when的结构体当中。
注意,Kotlin 中判断字符串或对象是否相等可以直接使用==关键字,而不用像Java那样调用equals()方法
可能你会觉得这种无参数的when语句写起来比较冗余,但有些场景必须使用这种写法才能实现。
- 举个例子,假设所有名字以Tom开头的人,他的分数都是86分,这种场景如果用带参数的when吾句来写就无法实现,而使用不带参数的when语句就可以这样写:
fun getScore(name: String) = when {
name. startsWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
name. startsWith(“Tom”) -> 86 不管你传入的名字是Tom还是Tommy,只要是以Tom开头的名字,他的分数就是86分。
3.循环语句
while循环
同java循环
for循环
Kotin在for循环方面做了很大幅度的修改 Java中最常用的for-i循环在Kotlin 中直接被 舍弃了 Java中另一种for- each循环则被Kotlin 进行了大幅度的加强,变成了for- in循环
for - in循环
区间的概念val range = 0..10 (创建一个0-10的区间,并且两端都是闭区间[0,10])
fun main(){
for (i in 0..10){
println(i)
}
}
左闭右开区间的创立,使用until[0,10)
val range = 0 until 10
for - in 循环每次执行会在区间范围内递增1,如果想跳过一些元素,可以使用step关键字
fun main(){
for (i in 0 until 10 step 2){
println(i)
}
}
上述代码表示在遍历[0, 10)这个区间的时候,每次执行循环都会在区间范围内递增2,相当 于for-i循环中i=i+2的效果。运行结果如下 当前区间跳过了所有奇数。 …和until关键字要求区间的的左端小于等于右端,也就是创建的是升序的区间。 降序为downTo
fun main(){
for (i in 10 downTo 1){
println(i)
}
}
for- in循环除了可以对区间进行遍历之外,还可以用于遍历数组和集合
总结一下的话,我觉得for-in循环并没有传统的for-i循环那样灵活,但是却比for-i循环要简单好用得多,而且足够覆盖大部分的使用场景。如果有一些特殊场景使用for-in循环无法实现的话,我们还可以改用while循环的方式来进行实现。
面向对象编程
??面向对象的语言是可以创建类的。类就是对事物的一种封装,比如人、汽车、房子、书等任何事物。
类(通常是名词) ?字段(所拥有的属性,如姓名,年龄价格…)名词 ?函数(类有哪些行为,如吃饭,睡觉,购买…)动词
通过这种类的封装,我们就可以在适当的时候创建该类的对象,然后调用对象中的字段和函数来满足实际编程的需求,这就是面向对象编程最基本的思想。当然,面向对象编程还有很多其他特性,如继承、多态等,但是这些特性都是建立在基本的思想之上的,理解了基本思想之后…
类与对象
首先创建一个Person类,com.example.helloworld包——New——Kotlin File/Class,
- 加入name和age两个字段并且添加eat函数(任何一个人都有名字和年龄,也都需要吃饭)
package com.example.hellowworld
class Person {
var name = ""
var age = 0
fun eat(){
println(name + " is eating.He is " + age +" years old.")
}
}
将代码实例化val p Person()
实例化后的类赋值到p变量上面,p就可以称为Person的一个实例,也可以称为一个对象
fun main() {
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
运行成功
继承与构造函数
继承——面向对象编程中另-一个极其重要的特性 ?继承也是基于现实场景总结出来的一个概念。 ?其实非常好理解。比如现在我们要定义一个Student类,每个学生都有自己的学号和年级,因此我们可以在Student类中加人sno和grade字段。但同时学生也是人呀,学生也会有姓名和年龄,也需要吃饭,如果我们在Student类中重复定义name、age字段和eat()函数的话就显得太过冗余了。这个时候就可以让Student类去继承Person类 ?这样Student就自动拥有了Person中的字段和函数,另外还可以定义自己独有的字段和函数。
- 创建继承类
现在Student 和Person 这两个类之间是没有任何继承关系的,想要让Student类继承Person类,我们得做两件事才行。
- 第一件事,使Person类可以被继承。
可能很多人会觉得奇怪,尤其是有Java编程经验的人。一个类本身不就是可以被继承的吗?为什么还要使Person类可以被继承呢?这就是Kotlin不同的地方,在Kotin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。之所以这么设计,其实和val关键字的原因是差不多的,因为类和变量一样,最好都是不可变的,而一个类允许被继承的话,它无法预知子类会如何实现,因此可能就会存在一些未知的风险。Effective Java这本书中明确提到,如果一个类不是专门为继承而设计的,那么就应该主动将它加上final声明,禁止它可以被继承。(kotlin遵循了这条编程规范,所有非抽象类都是不可以被继承的)
抽象本身不可以被继承,无法创建实例。一定要由子类去继承它才能创建实例,因此抽象类必须被继承才可以实现意义。
- 让Person可以被继承(在Person类前面加上open关键字)
open class Person{... }
加上open关键字之后,我们就是在主动告诉Kotlin编译器,Person 这个类是专广]为继承 设计的,这样Person类就允许被继承了。
- 第二件事,要让Student类继承Person类。
在Java中继承的关键字是extends , 而在Kotlin中变成了一个冒号
class Student : Person() {
var sno = ""
var grade =0
}
继承的写法如果只是替换一下关键字倒也挺简单的,但是为什么Person类的后面要加上-一 对括号呢? Java中继承的时候好像并不需要括号。对于初学Kotlin的人来讲,这对括号确实挺难理解的,也可能是Kotin在这方面设计得太复杂了,因为它还涉及主构造函数、次构造函数等方面的知识,这里我尽量尝试用最简单易懂的讲述来让你理解这对括号的意义和作用,同时顺便学习一下Kotlin中的主构造函数和次构造函数。
kotlin构造函数
- 主构造函数(最常用的构造函数)
- 次构造函数
每个类默认都会有一个不带参数的主构造函数(也可指明参数)。 主构造函数的特点是没有函数体,直接定义在类名的后面即可。
class Student(val sno: String, val grade: Int)
: Person() {
}
这里我们将学号和年级这两个字段都放到了主构造函数当中,这就表明在对Student类进行实例化的时候,必须传入构造函数中要求的所有参数。比如:
val student = Student("a123", 5)
在主函数中编写一些逻辑(kotlin为我们提供了一个init结构体,所有主构造函数中的逻辑都可以写在里面)
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is" + sno)
println("grade is" + grade)
}
}
这里我只是简单打印了一下学号和年级的值,现在如果你再去创建一个 Student类的实例,一定会将构造函数中传入的值打印出来。
继承后边"()"的含义 ?子类中的构造函数必须调用父类中的构造函数
那么回头看一下Student类,现在我们声明了一个主构造函数,根据继承特性的规定,子类的构造函数必须调用父类的构造函数,可是主构造函数并没有函数体,我们怎样去调用父类的构造函数呢?你可能会说,在init结构体中去调用不就好了。这或许是一种办法, 但绝对不是一种好办法,因为在绝大多数的场景下,我们是不需要编写init结构体的。 Kotlin当然没有采用这种设计,而是用了另外一种简单但是可能不太好理解的设计方式:括号。子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。
class Student(val sno: String,val grade: Int )Person() {
}
在这里,Person类后面的一对空括号表示Student类的主构造函数在初始化的时候会调用Person类的无参数构造函数,即使在无参数的情况下,这对括号也不能省略。
任何一个类只能有一个主构造函数,可以有多个次构造函数 次构造函数可以用于实例化一个类,不过次构造函数是有函数体的 Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。 这里我通过一一个具体的例子就能简单阐明,代码如下:
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name,age) {
constructor(name: String, age: Int) : this("", θ, name, age) {
}
constructor() : this("", 0) {
}
}
三种方式对Student类进行实体化
- 不带参数的结构函数
val student1 = Student() - 通过带2个参数的结构函数
val student2 = Student("Jack", 19) - 通过带4个参数的结构函数
val student3 = Student("a123", 5, "Jack", 19)
只有次构造函数,没有主构造函数。(少见但是允许)当一个类没有显示地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。
class Student : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}
接口
接口是用于实现多态编程的重要组成部分。任何一个类最多只能继承一个父类,但是却可以实现任意多个接口 创建Study接口:右击——New——Kotlin File/Class——类型选择Interface
interface Study {
fun readBooks()
fun doHomework()
}
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.")
}
}
上述代码就 表示Student类继承了Person类,同时还实现了Study接口。另外接口的后面不用加上括号,因为它没有构造函数可以去调用。
Study接口定义了readBooks()和doHomework()这两个待实现函数,因此Student类必须实现这两个函数。
- 使用override关键字来重写父类或者实现接口中的函数。
fun main() {
val student = Student("Jack", 19)
doStudy(student)()
}
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
这种叫做面向接口编程,也可称为多态。
运行结果如下 kotlin中额外的功能:允许对接口中定义的函数进行默认实现。
如果接口中的一个函数拥有了函数体,这个函数体中的内容就是它的默认实现。现在当一个类去实现Study接口时,只会强制要求实现readBooks()函数,而doHomework()函数则可以自由选择实现或者不实现,不实现时就会自动使用默认的实现逻辑。
函数的可见性修饰
四种可见性修饰符
Java中public、private、protected、default
Kotlin中public、private、protected、internal
需要那种直接定义在fun关键字前面即可
修饰符的含义及对照表
在kotlin中,public修饰符是默认项。
数据类与单例类
数据类
??在一个规范的系统架构中,数据类通常占据着非常重要的角色,它们用于将服务器端或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。或许你听说过MVC、MVP、MVVM之类的架构模式,不管是哪一 种架构模式,其中的M指的就是数据类。
创建拥有数据类的功能类 右击——New——Kotlin File/Class——选择Class,名字Cellphone。
data class Cellphone(val brand: String, val price: Double) {
}
当在一个类前面声明了data关键字时,就表明希望这个类是一个数据类。 Kotlin会根据主构造函数中的参数帮你将equals()、hashCode()、toString( )等固定且无实际逻辑意义的方法自动生成,从而大大减少了开发的工作量。
当一个类中没有任何代码时,还可以将尾部的大括号省略。
fun main() {
val cellphone1 = Cellphone("Samsung", 1299.99)
val cellphone2 = Cellphone("Samsung", 1299.99)
println(cellphone1)
println("cellphone1 equals cellphone2 " + (cellphone1 == cellphone2))
}
单例类
单例模式(最常用、最基础的设计模式之一) 用于避免复杂的对象。(比如我们希望某个类在全局最多只能拥有一个实例,这时就可以使用单例模式 )
创建kotlin版Singleton单例类 右键——New——K…——名字Singleton类型Object
object Singleton {
fun singlentonTest(){
println("singletonTest is called.")
}
}
调用单例类Singlenton.singlentonTest() 看上去像是静态调用,但Kotlin帮我们自动创建了一个Singleton类的实例,并且保证全局只会存在一个Singleton实例
Lambda编程
集合的创建与遍历
List
- main()中用for - in 循环水果名称集合
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list){
println(fruit)
}
}
listOf()函数创建的是一个不可变的集合。 (不可变的集合:该集合只能用于读取,无法添加修改或删除)
fun main() {
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list){
println(fruit)
}
}
先使用mutableListOf()函数创建可变集合,后添加新名称,使用循环遍历。
Set
只是将listOf关键字替换成了setOf
fun main(){
val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
for(fruit in set){
println(fruit)
}
}
Set 集合底层是使用hash映射机制来存放数据的,因此集合中的元素无法保证有序,这是和List集合最大的不同之处。
Map
Map是一种键值对形式的数据结构 因此在用法上和List、Set集合有较大的不同。
传统的Map用法是先创建一个HashMap的实例,然后将一个个键值对数据添加到Map中。
val map = HashMap<String, Int>()
map.put("Apple", 1)
map.put("Banana", 2)
map.put("Orange", 3)
map.put("Pear", 4)
map.put("Grape", 5)
这种写法与Java相似,但不建议
val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5
向Map中添加一条数据就可以这么写: map["Apple"] = 1 从Map中读取一条数据就可以这么写: val number = map["Apple"]
fun main(){
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
for((fruit,number) in map){
println("fruit is " + fruit + ", number is " + number)
}
}
集合的函数式API
Lambda表达式的语法结构
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val maxlengthFruit = list.maxBy{it.length}
println("max length fruit is" + maxLengthFruit)
使用集合的函数式API Lambda表达式的语法结构:
{参数名1:参数类型, 参数名2:参数类型 ->函数体}
首先最外层是一对大括号,如有参数传入到Lambda表达式中,还需要声明参数列表,参数列表结尾使用一个->符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码(虽然不建议编写太长的代码)并且最后一行代码会自动作为Lambda表达式的返回值
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val lambda = {fruit:String -> fruit.length}
val maxLengthFruit = list.maxBy(lambda)
maxBy(lambda)的作用是接受一个Lambda类型的参数。
val maxlengthFruit = list.maxBy({fruit:String -> fruit.length})
直接将lambda表达式传入maxBy函数中
val maxlengthFruit = list.maxBy(){fruit:String -> fruit.length}
Kotlin 规定,当Lambda参数是函数的最后一个参 数时,可以将Lambda表达式移到函数括号的外面,如上
val maxlengthFruit = list.maxBy{fruit:String -> fruit.length}
如果Lambda参数是函数的唯一个参数的话,还可以将函数的括号省略
val maxlengthFruit = list.maxBy{fruit -> fruit.length}
*Kotlin出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型, *
val maxlengthFruit = list.maxBy{it.length}
当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替
集合中的map函数
用于将集合中的每一个元素都映射成一个另外的值。映射的规则在Lambda表达式中指定,最终生成一个新的集合。
fun main(){
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map{ it.toUpperCase()}
for (fruit in newList){
println(fruit)
}
}
map函数可以按照需求对集合中的元素进行任意的映射转换: 全部转换成小写、只取单词的首字母、转换成单词长度这样的一个数字集合
filter函数
用来过滤集合中的数据,可以单独,也可以配合map
fun main(){
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.filter { it.length <= 5}
.map { it.toUpperCase()}
for (fruit in newList){
println(fruit)
}
}
上述代码先调用了filter再调用map。颠倒顺序也可以,但效率差很多。(因为要先对所有元素进行映射再过滤要比先过滤再映射要麻烦的多)
any和all函数
any用于判断集合中是否存在一个元素满足指定条件。 all用于判断集合中是否所有元素都满足指定条件
fun main(){
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val anyResult = list.any { it.length <= 5}
val allResult = list.all { it.length <= 5}
println("anyResult is "+ anyResult +", allResult is "+ allResult)
}
Java函数式API的使用
在Kotlin中调用Java方法时也可以使用函数式API(存在限制) kotlin中调用java方法(该方法接收一个java单抽象方法接口参数)可以使用函数式API java单抽象方法接口:接口中只有一个待实现方法。(多个就不可以用)
…
空指针检查
可空类型系统
判空辅助工具
?. 操作符
当对象不为空时正常调用
if (a != null){
a.doSomething()
}
这段代码使用 ?. 操作符可以简化成
a?.doSomething()
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?) = text?.length ?: 0
与众不同的辅助工具——let
let是一个函数,提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中。
obj.let{ obj2 ->
}
这里调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且这个obj对象本身还会作为参数传递到Lambda表达式中。不过,为了防止变量重名,这里我将参数名改成了obj2,但实际上它们是同一个对象,这就是let函数的作用。 let函数和空指针检查的关系 let函数的特性配合?.操作符可以在空指针检查的时候起到很大的作用。
fun doStudy(study: Study?){
study?.readBooks()
study?.doHomework()
}
本来进行一次if判断就能随意调用study对象的任何方法,受限于?.操作符的限制,变成了每次调用study对象的方法时都要进行if判断。
fun doStudy(study: Study?){
study?.let{ stu ->
stu.readBook()
stu.doHomework()
}
}
?.操作符表示对象为空时什么都不做,对象不为空时就调用let函数,而let函数会将study对象本身作为参数传递到Lambda表达式中,此时的study对象肯定不为空了,就可以放心的调用它的任意方法了。
- Lambda表达式的参数列表中只有一个参数时,可以不用声明参数名,直接用it关键字来代替即可。
fun doStudy(study: Study?){
study?.let{
it.readBook()
it.doHomework()
}
}
Tips: let函数是可以处理全局变量的判空为题的,而if判断语句则无法做到这一点。比如将doStudy()函数中的参数变成一个全局变量,使用let函数仍然可以正常工作,但是使用if判断语句会提示错误。 之所以这里会报错,是因为全局变量的值随时都有可能被其他线程所修改,即使做了判空处理,仍然无法保证if语句中的study变量没有空指针风险。从这一点上也能体现出let函数的优势。
Kotlin中的小魔术
字符串内嵌套表达式
Kotlin中字符串内嵌表达式的语法规则:
"hello, ${obj.name}. nice to meet you!"
Kotlin允许我们在字符串里嵌入${} 这种语法结构的表达式,并在运行时使用表达式执行的结果替代这一部分内容 。
- 当表达式中仅有一个变量的时候,还可以将两边的大括号省略。
"hello, $name. nice to meet you!"
写法更新
println("Cellphone(brand=" + brand + ", price=" + price +")")
println("Cellphone(brand=$brand, price=$price)")
函数的参数默认值
可以在定义函数的时候给任意参数设定一个默认值,这样当调用此函数时就不会强制要求调用方为此参数传值,在没有传值的情况下会自动使用参数的默认值。
fun printParams(num: Int, str: String = "hello"){
println("num is $num , str is $str")
}
给printParams()函数的第二个参数设定了一个默认值,当调用printParams()函数时,可以选择给第二个参数传值,也可以选择不传,在不传的情况下就会自动使用默认值。
*kotlin的键值对机制
printlnParams(str = "world", num = 123)
参数顺序无所谓,都可以准确的匹配上参数。
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age){
constructor(name: String, age: Int) : this("", 0, name, age) {
}
constructor() : this("", 0){
}
}
}
- 上述代码有一个主构造函数,二个次构造函数。
- 次构造的作用:提供了使用更少参数来对Student类进行实例化的方式。
- 无参的次构造函数会调用两个参数的次构造函数,并且将这两个参数赋值成初始值。
- 两个参数的次构造函数会调用4个参数的主构造函数,并将缺失的两个参数也赋值成初始值。
在Kotlin中完全可以通过只编写一个主构造函数,然后给参数设定默认值的方式来实现。
class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) : Person(name, age){
}
在给主构造函数的每个参数都设定了默认值之后,我们就可以使用任何传参组合的方式来对Student类进行实例化。当然也包含了刚才两种次构造函数的使用场景。 由此,给函数设定参数默认值这个小技巧的作用还是极大的。
小结与点评
Kotlin编程中
学习了——变量、函数、逻辑控制语句、面向对象编程、Lambda编程、空指针检查机制等 后续进行Android学习外加穿插Kotlin进阶。
|