文章目录
第二章 探究新语言,快速入门Kotlin编程
前言大概意思是:势如破竹,赶紧学。
2.1 Kotlin语言简介
编译型语言:编译器直接会将我们编写的源代码编译成计算机可识别的二进制。(C、C++)
解释型语言:我们编写的代码需要经过解释器解释一遍,才能被编译成计算机可识别的二进制。(Java、kotlin)
所以知道为什么Java、kotlin两种不同语言却可以无缝连接了吧。
2.2 如何运行kotlin
JetBrains提供的在线网站:https://try.kotlinlang.org/
右上角点击运行按钮,在下方查看控制台输出。
在as中运行:
在android项目中新建一个C02Test
类,然后输入代码
点击main
函数左边的绿色运行按钮,运行结果如下
2.3 编程之本:变量和函数
2.3.1 变量
一般格式:
val a: Int = 10
Int
中的I是大写的,因为kotlin完全中完全抛弃了基本数据类型,对应的关系为:
Java基本数据类型 | Kotlin对象数据类型 | 数据类型说明 |
---|---|---|
int | Int | 整型 |
long | Long | 长整型 |
short | Short | 整型 |
float | Float | 单精度浮点型 |
double | Double | 双精度浮点型 |
boolean | Boolean | 布尔型 |
char | Char | 字符型 |
byte | Byte | 字节型 |
val
和 var
val(value的简写)用来声明一个不可变的变量。
var(variable的简写)用来声明一个可变的变量。
不改变a的值
运行正常,尝试改变a的值,报错。
一个好的编程习惯是:
在java中,除非一个变量明确允许被修改,否则都应该给它加上final关键字。
在kotlin中,永远优先使用val来声明,当val没有办法满足你的需求时再使用var。
2.3.2 函数
语法规则
fun methodName(param1: Int, param2: Int): Int{
return 0
}
善用代码补全功能
语法糖:当一个函数中只有一行代码时,Kotlin允许我们不必写函数体。
因此可以改为:
fun largeNum(num1: Int, num2: Int): Int = max(num1, num2)
再根据类型推导机制,简化为:
fun largeNum(num1: Int, num2: Int) = max(num1, num2)
2.4 程序的逻辑控制
2.4.1 if条件语句
与Java中的不同是Kotlin中的if
是可以有返回值的
fun largeNum(num1: Int, num2: Int) = if (num1 > num2)num1 else num2
2.4.2 when条件语句
写一个查询考试成绩的功能
用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
}
换成when
的写法:
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
最有特点的是:when
语句允许传入一个任意类型的参数。
比如,假设所有名字以Tom开头的人,他的分数都是86分。
fun getScore(name: String) = when {
name.startsWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
2.4.3 循环语句
while
循环和java中没有任何区别。
for in
循环
@Test
fun testFor() {
for (i in 1..10) {
print(i)
}
println()
for (i in 0 until 10 step 2) {
print(i)
}
println()
for (i in 10 downTo 1) {
print(i)
}
}
输出
12345678910
02468
10987654321
1…10表示[1,10]双闭区间。
until
关键字表示左闭右开区间。
step 2
这里表示每次递增2,downTo
降序。
2.5 面向对象编程
2.5.1 类与对象
定义一个Person
类
class Person {
var name = ""
var age = 0
fun eat() {
println("$name is eating. He is $age year old.")
}
}
试验一下
@Test
fun test3() {
val p = Person()
p.name = "Owen"
p.age = 18
p.eat()
}
输出
Owen is eating. He is 18 year old.
kotlin中去掉了new关键字,因为当你调用了某个类的构造函数时,你的意图只可能是对这个类进行实例化。
2.5.2 继承与构造函数
Kolin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final
关键字。
如需定义成可被继承,加上open
关键字。
open class Person {
...
}
定一个一个Student
类继承Person
类:
class Student: Person() {
var sno = ""
var grade = 0
}
Java中继承的关键字是extends,在Kotlin中改成了冒号。
Person()
后面的括号是主构造函数,主构造函数的特点是没有函数体,直接定义在类名的后面。
我们为Student
类定义一个构造函数:
class Student(val sno: String, val grade: Int): Person() {
init {
println("sno is $sno")
println("gradle is $grade")
}
}
(val sno: String, val grade: Int)
就是Student的主构造函数,表明要对Student
进行实例化的时候必须传入sno
和grade
。
构造函数中的参数都是创建实例的时候创建的,因此全部声明成val
。
我们知道,存在继承关系时,子类构造函数会调用父类的构造函数,子类调用父类的那个构造函数,由继承时的括号来决定。
比如说这里指定的是Person()
,表示初始化的时候调用的是Person
类的无参构造函数。
次构造函数
次构造函数用constructor
关键字定义。
修改Person
类的构造函数如下:
open class Person(val name: String, val age: Int) {
...
}
在Student
中加入次构造函数
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)
}
注:所有的次构造函数都必须调用主构造函数(包括间接调用)。
这样我们就有了3种对Student
类实例化的方式:
val s1 = Student()
val s2 = Student("Jack", 19)
val s3 = Student("a123", 5, "Jack", 19)
这里没有细讲次构造函数作用,因为这完全可以通过默认参数来实现,相信后面的内容会对次构造函数的内容有补充。
特殊情况:类中只有次构造函数,没有主构造函数。
class Student: Person {
constructor(name: String, age: Int) : super(name, age)
}
因为Student
没有主构造函数,次构造函数只能直接调用父类的构造函数,这里的this
关键字也就变成了super
关键字。
另一点就是,既然没有主构造函数,继承Person
类的时候也就不需要加上括号了。
2.5.3 接口
定义接口
interface Study {
fun readBooks()
fun doHomework()
}
让Student
类去实现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.")
}
}
Kotlin中继承和实现统一使用冒号分隔,另外接口的后面不需要加上括号,因为它没有构造函数去调用。
在测试函数中调用
@Test
fun test4() {
val student = Student("Jack", 19)
doStudy(student)
}
private fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
查看输出日志:
Jack is reading.
Jack is doing homework.
以上就是接口的最基础用法,是不是很简单?
额外功能:允许对接口中定义的函数进行默认实现。(Java在JDK1.8之后也支持这个功能)
修改Study
接口如下
interface Study {
fun readBooks()
fun doHomework() {
println("do homework default implementation.")
}
}
给doHomework
方法加了默认实现,去掉Student
类中对该方法的重写,重新运行程序,得到结果:
Jack is reading.
do homework default implementation.
函数的可见修饰符
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
internal | 无 | 同一模块中的类可见 |
2.5.4 数据类和单例类
定义一个数据类
data class Cellphone (val brand: String, val price: Double)
用data
关键字声明,就表明这个类是数据类,Kotlin会根据主构造函数的参数,默认实现equals()
、hashCode()
、toString()
等方法。
在Test函数中编写代码如下
@Test
fun test5() {
val cellphone1 = Cellphone("Sansung", 1299.99)
val cellphone2 = Cellphone("Sansung", 1299.99)
println(cellphone1)
println("cellphone1 equals cellphone2 " + (cellphone1 == cellphone2))
}
运行
Cellphone(brand=Sansung, price=1299.99)
cellphone1 equals cellphone2 true
tip:在
data class
中Kotlin自动为其生成了基于内容的equals()
方法,从而方便地进行基于内容的相等性比较。换句话说,如果你希望自定义对象能够按照内容(而非引用)进行比较,除了覆盖equals()
方法和hashCode()
方法(通常一起覆盖以保持一致性)来实现,更推荐使用data class
来自动生成这些方法的实现。
定义一个单例类
object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}
只需要把class
关键字改成object
关键字即可,在测试类中调用
@Test
fun test6() {
Singleton.singletonTest()
}
输出
singletonTest is called.
2.6 Lambda编程
JDK1.8之后加入,Kotlin从第一个版本就加入了。
2.6.1 集合的创建与遍历
List集合
传统写法
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")
在测试函数中遍历这个集合并打印
@Test
fun test7() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list) {
println(fruit)
}
}
查看输出
Apple
Banana
Orange
Pear
Grape
注意:用listOf()
函数初始化的集合是不可变的集合。只能读取,无法编辑。
用mutableListOf()
创建可变的集合,代码如下:
@Test
fun test7() {
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
println(fruit)
}
}
查看输出
Apple
Banana
Orange
Pear
Grape
Watermelon
Set集合
和List集合类似,有setOf()
和mutableSetOf()
方法。查看示例:
@Test
fun test8() {
val list = mutableSetOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
println(fruit)
}
}
输出和上面完全一致。
Set集合的底层是使用hash映射机制类存放数据的,因此集合元素中无法保证有序。
Map集合
Map是一种键值对形式的数据结构,因此在用法上和List、Set集合有所不同。
传统写法:
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)
在添加和读取上,Kolin推荐使用类似数组小标的语法结构:
//添加或编辑
map["Apple"] = 10
// 读取
val num = map["Apple"]
用mapOf()
或mutableMapOf()
的定义形式:
val map = mutableMapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
这里的
to
其实并不是关键字,而是一个infix函数。会在第九章中讲到。
编写测试函数遍历这个Map:
@Test
fun test9() {
val map = mutableMapOf("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")
}
}
点击方法左侧的运行按钮,就能在控制台就能查看输出了
fruit is Apple, number is 1
fruit is Banana, number is 2
fruit is Orange, number is 3
fruit is Pear, number is 4
fruit is Grape, number is 5
2.6.2 集合的函数式API
本节重点学习函数式API的语法结构,也就是Lambda表达式的语法结构。
需求:找到水果集合里单词最长的那个水果?
传统写法
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")
函数式写法
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
var maxLengthFruit = list.maxBy { it.length }
println("max length fruit is $maxLengthFruit")
来看一下Lambda表达式的语法结构:
{参数1: 参数类型, 参数2: 参数类型 -> 函数体}
理解一下推导过程
Lambda表达式写法
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
// list.maxBy()相当于遍历这个集合,传入的lambda便是过滤的条件
val lambda = {fruit: String -> fruit.length}
var maxLengthFruit = list.maxBy(lambda)
1、将表达式传入maxBy函数当中
var maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
2、Kotlin规定,当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面
var maxLengthFruit = list.maxBy(){ fruit: String -> fruit.length }
3、如果Lambda参数是函数的唯一一个参数的话,可以将函数的括号省略
var maxLengthFruit = list.maxBy{ fruit: String -> fruit.length }
4、Kotlin优秀的类型推导
var maxLengthFruit = list.maxBy{ fruit -> fruit.length }
5、Lambda表达式的参数列表中只有一个参数时,不必声明参数名,而是使用it关键字代替
var maxLengthFruit = list.maxBy { it.length }
这样就得到了和一开始一模一样的写法。
map函数、filter函数
map
转换,filter
过滤
需求:保留5个字母以内的水果,并转大写
@Test
fun test11() {
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)
}
}
输出
APPLE
PEAR
GRAPE
any和all函数
any
为存在一个元素满足即为true,all
为所有元素满足才为true。
@Test
fun test12() {
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")
}
输出
anyResult is true, allResult is false
2.6.3 Java函数式API的使用
Java函数式API的使用限定于从Kotlin中调用Java方法,并且单抽象方法接口也必须是使用Java语言定义(Kotlin中有高阶函数)。
以单抽象方法接口Runnable
为例
public interface Runnable {
public abstract void run();
}
在Thread
类的构造方法中接收一个Runnable
参数,并执行
@Test
fun test13() {
Thread(object : Runnable {
override fun run() {
println("Thread is running")
}
}).start()
}
Kotlin完全舍弃了
new
关键字,改用了object
关键字,注意object
后面有个冒号。
目前Thread
类的构造方法是符合Java函数式API的使用条件的,因此代码可以简化为:
Thread(Runnable {
println("Thread is running")
}).start()
这样既简化了代码,也不会造成任何误解,因为run
本身就是接口中唯一的方法。
当只存在一个Java单抽象接口的参数时,省略接口名
Thread({
println("Thread is running")
}).start()
Lambda表达式是最后一个参数,简化,是唯一一个参数时,再简化,最后为
@Test
fun test13() {
Thread {
println("Thread is running")
}.start()
}
查看输出
Thread is running
在比如点击事件接口OnClickListener
,最后可以简化为
button.setOnClickListener{
}
2.7 空指针检查
2.7.1 可空类型系统
Kotlin将空指针异常的检查提前到了编译时期。
如何声明可空类型:在类型后面加一个?
,如图
这时候就需要加上判断处理
private fun doStudy(study: Study?) {
if (study != null) {
study.readBooks()
study.doHomework()
}
}
2.7.2 判空辅助工具
?.
操作符
private fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
当对象为空时什么都不做。
?:
操作符
以下功能
val c = if (a != null) {
a
} else {
b
}
可简化成
val c = a ?: b
!!
非空断言
val upperCase = content!!.toUpperCase()
println(upperCase)
加了非空断言,若为空则抛出异常。
let
函数
上面的例子中
private fun doStudy(study: Study?) {
study?.readBooks()
study?.doHomework()
}
其实相当于
if (study != null) {
study.readBooks()
}
if (study != null) {
study.doHomework()
}
这时候其实就可以借助?.
操作符和let
函数来进行代码优化
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
简化后
study?.let {
it.readBooks()
it.doHomework()
}
值得一提的是:let
函数是可以处理全局变量的判空问题的。我们来看
这里之所以会报错, 是因为全局变量的值随意可能被其他线程所修改。
这里也能体现出let
函数的优势。
2.8 Kotlin中的小魔术
2.8.1 字符串内嵌表达式
@Test
fun test16() {
val age = 18
val stu = Student("Jack", age)
println("The boy is named ${stu.name}, he is $age year old")
}
2.8.2 函数的参数默认值
private fun printParams(num: Int, str: String = "hello", string: String = "csdn") {
println("num is $num, str is $str, string is $string")
}
@Test
fun test17() {
printParams(string = "world", str = "lively", num = 3)
}
通过键值对传参就能无视顺序。
2.9 小结与点评
略略略~
虽然觉得没必要,但还是附上文中代码地址,免得有些人连复制的懒得复制。