《第一行代码》Kotlin讲堂知识整理——第2章 探索新语言,快速入门Kotlin编程

本文介绍了Kotlin语言的基础知识,包括变量、函数、控制结构、面向对象编程等内容,并详细讲解了Kotlin特有的Lambda编程和空指针处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第二章 探究新语言,快速入门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对象数据类型数据类型说明
intInt整型
longLong长整型
shortShort整型
floatFloat单精度浮点型
doubleDouble双精度浮点型
booleanBoolean布尔型
charChar字符型
byteByte字节型

valvar

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进行实例化的时候必须传入snograde

构造函数中的参数都是创建实例的时候创建的,因此全部声明成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.

函数的可见修饰符

修饰符JavaKotlin
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 小结与点评

略略略~


虽然觉得没必要,但还是附上文中代码地址,免得有些人连复制的懒得复制。

《第一行代码》Kotlin讲堂知识整理

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT小瓯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值