Kotlin快速入门
本文内容为阅读《第一行代码 Android 第3版-郭霖-人邮-2020》的笔记
内容从md文件导入到CSDN,布局可能会出现异常。
1. 介绍
- Kotlin是由JetBrains公司开发与设计的。
- 2011年,JetBrains公布了Kotlin的第一个版本。并在2012年将其开源。
- 2016年,Kotlin发布了1.0正式版。
- 2017年Google宣布Kotlin正式成为Android一级开发语言。
Kotlin的优势
- 语法更加简洁,对于同样的功能,使用Kotlin开发的代码量可能会比使用Java开发的减少50% 甚至更多。
- Kotlin的语法更加高级,相比于Java比较老旧的语法,Kotlin增加了很多现代高级语言的语法特性,使得开发效率大大提升。
- Kotlin在语言安全性方面下了很多工夫,几乎杜绝了空指针这个全球崩溃率最高的异常。
- 它和Java是100%兼容的。Kotlin可以直接调用使用Java编写的代码,也可以无缝使用Java第三方的开源库。
2. 如何运行Kotlin代码?
-
IntelliJ IDEA
-
在线运行Kotlin代码:https://try.kotlinlang.org
-
Android Studio
-
第一行Kotlin代码:
package com.example.helloworld fun main() { println("Hello Kotlin!"); }
3. 变量和函数
变量
声明变量:
-
val(value的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值,对应Java中的final变量。
val a = 0
-
var(variable的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新赋值,对应Java中的非final变量。
var a = 0
-
Kotlin不需要在每行代码结尾写分号;
显式声明变量类型:
val a: Int = 10
Kotlin中没有基本数据类型,全部使用了对象数据类型。
Java和Kotlin数据类型对照表:
Java基本数据类型 | Kotlin对象数据类型 | 数据类型说明 |
---|---|---|
int | Int | 整型 |
long | Long | 长整型 |
short | Short | 短整型 |
float | Float | 单精度浮点型 |
double | Double | 双精度浮点型 |
boolean | Boolean | 布尔型 |
char | Char | 字符型 |
byte | Byte | 字节型 |
函数
-
函数和方法就是同一个概念,这两种叫法都是从英文翻译过来的,函数翻译自function,方法翻译自method,它们并没有什么区别,只是不同语言的叫法习惯不一样而已。
-
Kotlin自定义函数规范:
fun methodName(param1: Int, param2: Int): Int { return 0 }
4. 逻辑控制
if条件语句
-
Kotlin中的条件语句主要有两种实现方式:
if
和when
fun largerNumber(num1: Int, num2: Int): Int { var value = 0 if (num1 > num2) { value = num1 } else { value = num2 } return value }
Kotlin中的if语句相比于Java有一个额外的功能,它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。
fun largerNumber(num1: Int, num2: Int): Int { var value = if (num1 > num2) { num1 } else { num2 } return value }
简化后:
fun largerNumber(num1: Int, num2: Int): Int { return if (num1 > num2) { num1 } else { num2 } }
当一个函数只有一行代码时,可以省略函数体部分,直接将这一行代码使用等号串连在函数定义的尾部。
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) { num1 } else { num2 }
再精简一下
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
-
when条件语句
Kotlin中的when语句有点类似于Java中的switch语句,但它又远比switch语句强大得多。
示例代码:
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 }
使用when
fun getScore(name: String) = when (name) { "Tom" -> 86 "Jim" -> 77 "Jack" -> 95 "Lily" -> 100 else -> 0 }
-
when语句允许传入一个任意类型的参数,然后可以在when的结构体中定义一系列的条件,格式是:
匹配值 -> { 执行逻辑 }
当你的执行逻辑只有一行代码时,{ }可以省略。
when语句还有一种不带参数的用法:
fun getScore(name: String) = when { name == "Tom" -> 86 name == "Jim" -> 77 name == "Jack" -> 95 name == "Lily" -> 100 else -> 0 }
这种用法是将判断的表达式完整地写在when的结构体当中。
fun getScore(name: String) = when { name.startsWith("Tom") -> 86 name == "Jim" -> 77 name == "Jack" -> 95 name == "Lily" -> 100 else -> 0 }
-
循环语句
Java中最常用的for-i循环在Kotlin中直接被舍弃了。
而Java中另一种for-each循环则被Kotlin进行了大幅度的加强,变成了for-in循环。
区间:
val range = 0..10
表示创建了一个0到10的区间,并且两端都是闭区间,这意味着0到10这两个端点都是包含在区间中的。
用数学的方式表达出来就是[0, 10]
使用for in 遍历区间
fun main() {
for (i in 0..10) {
println(i)
}
}
左闭右开的区间在程序设计当中更加常用
Kotlin中可以使用until关键字来创建一个左闭右开的区间,如下所示:
val range = 0 until 10
它的数学表达方式是[0, 10)
默认情况下,for-in循环每次执行循环时会在区间范围内递增1, 如果你想跳过其中的一些元素,可以使用step关键字:
fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}
创建一个降序的区间,可以使用downTo关键字
fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
5. 面向对象
基本概念
类:对事物的一种封装。比如说人、汽车、房屋、书等任何事物,我们都可以将它封装一个类,类名通常是名词。
对象:对象是类的具体表现。
属性:类拥有属性。
函数(方法):表示类的行为。
类是对象的一种抽象,对象是类的具体表现。
类与对象
Kotlin中也是使用class关键字来声明一个类
class Person {
}
加入字段和函数
class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
创建对象
val p = Person()
与java不同的是,在kotlin中创建对象不需要new关键字
操作对象
fun main() {
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
继承与构造函数
创建一个Student类
class Student {
var sno = ""
var grade = 0
}
在Kotlin中任何一个非抽象类默认都是不可以被继承的。
之所以这里一直在说非抽象类,是因为抽象类本身是无法创建实例的,一定要由子类去继承它才能创建实例,因此抽象类必须可以被继承才行,要不然就没有意义了。
让Person类可以被继承:在class前加上open关键字
open class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
要让Student类继承Person类。在Java中继承的关键字是extends,而在Kotlin中变成了一个冒号
class Student : Person() {
var sno = ""
var grade = 0
}
为什么Person类的后面要加上一对括号?
Kotlin将构造函数分成了两种:主构造函数和次构造函数。
每个类默认都会有一个不带参数的主构造函数,当然你也可以显式地给它指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即
可。例如:
class Student(val sno: String, val grade: Int) : Person() {
}
主构造函数没有函数体,如果我想在主构造函数中编写一些逻辑,该怎么办呢?
Kotlin给我们提供了一个init结构体,所有主构造函数中的逻辑都可以写在里面:
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is " + sno)
println("grade is " + grade)
}
}
回到:为什么Person类的后面要加上一对括号?
继承特性:子类中的构造函数必须调用父类中的构造函数
在这里,Person类后面的一对空括号表示Student类的主构造函数在初始化的时候会调用Person类的无参数构造函数,即使在无参数的情况下,这对括号也不能省略。
将Person改造一下,将姓名和年龄都放到主构造函数当中,如下所示:
open class Person(val name: String, val age: Int) {
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
此时继承Person类就需要调用新的主构造函数,父类构造函数中所需的参数name,age可以通过子类的构造函数传递
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
}
注意,我们在Student类的主构造函数中增加name和age这两个字段时,不能再将它们声明成val,因为在主构造函数中声明成val或者var的参数将自动成为该类的字段,这就会导致和父类中同名的name和age字段造成冲突。因此,这里的name和age参数前面我们不用加任何关键字,让它的作用域仅限定在主构造函数当中即可。
次构造函数
任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可以用于实例化一个类。
Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)
示例:
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) {
}
}
次构造函数是通过constructor关键字来定义的
第一个次构造函数通过this关键字调用了主构造函数。
第二个此构造函数通过this关键字调用了第一个次构造函数,间接调用了主构造函数。
**特殊情况:**类中只有次构造函数,没有主构造函数。
class Student : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}
既然没有主构造函数,继承Person类的时候也就不需要再加上括号了
由于没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将this关键字换成了super关键字
接口
Java是单继承结构的语言,任何一个类最多只能继承一个父类,但是却可以实现任意多个接口,Kotlin也是如此。
定义接口:
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.")
}
}
Java中继承使用的关键字是extends,实现接口使用的关键字是implements,而Kotlin中统一使用冒号,中间用逗号进行分隔。
Kotlin中使用override关键字来重写父类或者实现接口中的函数。
Kotlin还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。
interface Study {
fun readBooks()
fun doHomework() {
println("do homework default implementation.")
}
}
函数的可见性修饰符
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
internal | 无 | 同一模块中的类可见 |
数据类与单例类
数据类
如果使用Java来实现这样一个数据类,代码就需要这样写:
public class Cellphone {
String brand;
double price;
public Cellphone(String brand, double price) {
this.brand = brand;
this.price = price;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Cellphone) {
Cellphone other = (Cellphone) obj;
return other.brand.equals(brand) && other.price == price;
}
return false;
}
@Override
public int hashCode() {
return brand.hashCode() + (int) price;
}
@Override
public String toString() {
return "Cellphone(brand=" + brand + ", price=" + price + ")";
}
}
在Kotlin中:
data class Cellphone(val brand: String, val price: Double)
声明data关键字的类被标记为数据类,Kotlin会根据主构造函数中的参数自动生成equals()、hashCode()、toString()等
另外,当一个类中没有任何代码时,还可以将尾部的大括号省略。
单例类
在Java中最常见的写法:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void singletonTest() {
System.out.println("singletonTest is called.");
}
}
在Kotlin中创建一个单例类的方式极其简单,只需要将class关键字改成object关键字即可。
object Singleton {
}
在单例类中新增一个函数
object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}
调用单例类中的函数,比较类似于Java中静态方法的调用方式
Singleton.singletonTest()
6. Lambda编程
集合的创建与遍历
现在需要创建一个集合
在Java中的方式:
val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")
Kotlin专门提供了一个内置的listOf()函数来简化初始化集合的写法
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
使用for in 循环来遍历这个集合
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list) {
println(fruit)
}
}
注意:listOf()函数创建的是一个不可变的集合。
不可变的集合指的就是该集合只能用于读取,我们无法对集合进行添加、修改或删除操作。
如何创建一个可变的集合?
使用mutableListOf()函数
fun main() {
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list) {
println(fruit)
}
}
Set集合的用法几乎与此一模一样,只是将创建集合的方式换成了setOf()和mutableSetOf()函数而已
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)
在Kotlin中并不建议使用put()和get()方法来对Map进行添加和读取数据操作,而是更加推荐使用一种类似于数组下标的语法结构
比如向Map中添加一条数据就可以这么写:
map["Apple"] = 1
而从Map中读取一条数据就可以这么写:
val number = map["Apple"]
因此,上述代码经过优化过后就可以变成如下形式:
val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5
Kotlin也地提供了一对mapOf()和mutableMapOf()函数来继续简化Map的用法。
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
这里的to
并不是关键字,而是一个infix函数
遍历Map
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
如何在一个水果集合里面找到单词最长的那个水果?
普通写法:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
var maxLengthFruit = ""
for (fruit in list) {
if (fruit.length > maxLengthFruit.length) {
maxLengthFruit = fruit
}
}
println("max length fruit is " + maxLengthFruit)
使用集合的函数式API
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val maxLengthFruit = list.maxBy { it.length }
println("max length fruit is " + maxLengthFruit)
定义:Lambda就是一小段可以作为参数传递的代码。
Lambda表达式的语法结构:
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
maxBy函数:
根据我们传入的条件来遍历集合,从而找到该条件下的最大值
标准结构
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda)
简化1:直接将lambda表达式传入maxBy函数当中
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
简化2:当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
简化3:如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
简化4:由于Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
简化5:当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替
val maxLengthFruit = list.maxBy { it.length }
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)
}
}
filter函数:
filter函数是用来过滤集合中的数据的,它可以单独使用,也可以配合刚才的map函数一起使用。
比如我们只想保留5个字母以内的水果,就可以借助filter函数来实现,代码如下所示:
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函数。如果你改成先调用map函数再调用filter函数,也能实现同样的效果,但是效率就会差很多,因为这样相当于要对集合中所有的元素都进行一次映射转换后再进行过滤,这是完全不必要的。而先进行过滤操作,再对过滤后的元素进行映射转换,就会明显高效得多。
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单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。
Java原生API中有一个最为常见的单抽象方法接口——Runnable接口。
public interface Runnable {
void run();
}
Thread类的构造方法中接收了一个Runnable参数,我们可以使用如下Java代码创建并执行一个子线程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running");
}
}).start();
注意,这里使用了匿名类的写法。翻译成kotlin如下:
Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()
注意:Kotlin中匿名类的写法和Java有一点区别,由于Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候就不能再使用new了,而是改用了object关键字。
当前Thread类的构造方法是符合Java函数式API的使用条件:
Thread(Runnable {
println("Thread is running")
}).start()
如果一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数,我们还可以将接口名进行省略
Thread({
println("Thread is running")
}).start()
当Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面。
同时,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略。
Thread {
println("Thread is running")
}.start()
后面要经常打交道的Android SDK还是使用Java语言编写的,当我们在Kotlin中调用这些SDK接口时,就很可能会用到这种Java函数式API的写法。
举个例子,Android中有一个极为常用的点击事件接口OnClickListener,其定义如下:
public interface OnClickListener {
void onClick(View v);
}
使用Java代码去注册这个按钮的点击事件,需要这么写:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
而用Kotlin就可以使用函数式API的写法来对代码进行简化
button.setOnClickListener {
}
注意:本小节中学习的Java函数式API的使用都限定于从Kotlin中调用Java方法,并且单抽象方法接口也必须是用Java语言定义的。
Kotlin中有专门的高阶函数来实现更加强大的自定义函数式API功能,从而不需要像Java这样借助单抽象方法接口来实现。
7. 空指针检查
Android系统上崩溃率最高的异常类型就是空指针异常(NullPointerException)。
示例代码
public void doStudy(Study study) {
study.readBooks();
study.doHomework();
}
在改方法中,如果向doStudy方法传入一个null参数,这里就会发生空指针异常。
如何避免?
public void doStudy(Study study) {
if(study != null){
study.readBooks();
study.doHomework();
}
}
可空类型系统
Kotlin利用编译时判空检查的机制几乎杜绝了空指针异常。
示例代码:
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
Kotlin默认所有的参数和变量都不可为空,所以这里传入的Study参数也一定不会为空。
如果你尝试向doStudy()函数传入一个null参数,kotlin的编译将会失败。
Kotlin将空指针异常的检查提前到了编译时期,如果我们的程序存在空指针异常的风险,那么在编译的时候会直接报错。
如何让一个类型可以为空?
在类名的后面加上一个问号:
Int表示不可为空的整型,而Int?就表示可为空的整型
String表示不可为空的字符串,而String?就表示可为空的字符串
fun doStudy(study: Study?) {
study.readBooks()
study.doHomework()
}
此时可以传入一个null,但是在doStudy()函数中调用参数的readBooks()和doHomework()方法时,却出现了一个红色下滑线的错误提示
由于我们将参数改成了可为空的Study?类型,此时调用参数的readBooks()和doHomework()方法都可能造成空指针异常,因此Kotlin在这种情况下不允许编译通过。
fun doStudy(study: Study?) {
if (study != null) {
study.readBooks()
study.doHomework()
}
}
判空辅助工具
?.操作符
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
有的时候我们可能从逻辑上已经将空指针异常处理了,但是Kotlin的编译器并不知道,这个时候它还是会编译失败。
例如:
var content: String? = "hello"
fun main() {
if (content != null) {
printUpperCase()
}
}
fun printUpperCase() {
val upperCase = content.toUpperCase()
println(upperCase)
}
printUpperCase()函数并不知道外部已经对content变量进行了非空检查,在调用toUpperCase()方法时,还认为这里存在空指针风险,从而无法编译通过。
在这种情况下,如果我们想要强行通过编译,可以使用非空断言工具,写法是在对象的后面加上!!,如下所示:
fun printUpperCase() {
val upperCase = content!!.toUpperCase()
println(upperCase)
}
这是一种有风险的写法,意在告诉Kotlin,我非常确信这里的对象不会为空。
let函数
这个函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中。
obj.let { obj2 ->
// 编写具体的业务逻辑
}
这里调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且这个obj对象本身还会作为参数传递到Lambda表达式中。
let函数属于Kotlin中的标准函数
使用let函数优化之前的示例如下:
fun doStudy(study: Study?) {
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
}
简化:当Lambda表达式的参数列表中只有一个参数时,可以不用声明参数名,直接使用it关键字来代替即可
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
let函数是可以处理全局变量的判空问题的,而if判断语句则无法做到这一点。比如我们将doStudy()函数中的参数变成一个全局变量,使用let函数仍然可以正常工作,但使用if判断语句则会提示错误。
之所以这里会报错,是因为全局变量的值随时都有可能被其他线程所修改,即使做了判空处理,仍然无法保证if语句中的study变量没有空指针风险。从这一点上也能体现出let函数的优势。
8. Kotlin中的技巧
字符串内嵌表达式${}
示例:
"hello, ${obj.name}. nice to meet you!"
当表达式中仅有一个变量的时候,还可以将两边的大括号省略
"hello, $name. nice to meet you!"
函数的参数默认值
前面提到次构造函数在Kotlin中很少用,因为Kotlin提供了给函数设定参数默认值的功能,它在很大程度上能够替代次构造函数的作用。
例如:
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
如果第一个参数设定默认值,第二个参数没有设定默认值,需要通过键值对的方式来传参
fun printParams(num: Int = 100, str: String) {
println("num is $num , str is $str")
}
调用方法:
printParams(str = "world")
回顾前面次构造函数示例:
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) {
}
}
使用设定默认值的方式:
class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) : Person(name, age) {
}
在给主构造函数的每个参数都设定了默认值之后,我们就可以使用任何传参组合的方式来对Student类进行实例化,当然也包含了刚才两种次构造函数的使用场景。