《Android》Chap.2 入门Kotlin

Posted 我还能码嘛。

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Android》Chap.2 入门Kotlin相关的知识,希望对你有一定的参考价值。

Hello Kotlin


kotlin每一行代码的结尾不需要加分号。

编程之本

变量

关键词

语法规则

代码原词用法
valvalue声明一个不可变的变量,在初始赋值后不能在被重新赋值
varvariable声明一个可变的变量,在初始赋值后仍能在被重新赋值

代码实践

package com.example.hellokt

fun main()
    val a = 10
    println("a = " + a)

数据类型

语法规则

Kotlin中使用了对象数据类型,是一个拥有自己的方法和继承结构的类。

数据类型说明
Int整型
Long长整型
Short短整型
Float单精度浮点型
Double双精度浮点型
Boolean布尔型
Char字符型
Byte字节型

代码实践

声明不可变整型变量


报错原因:声明a的关键字为val变量,后续就不能再赋值。

声明可变整型变量
package com.example.hellokt

fun main()
    var a: Int = 10
    a = a * 10
    println("a = " + a)

函数

语法规则

//fun 函数名(参数1名: 参数类型, 参数2名: 参数类型): 函数返回值类型
fun methodName(param1: Int, param2: Int): Int
    return 0

  • 参数的数量可以是任意多个
  • 如果函数不需要返回任何数据,可以不写返回值类型

代码实践

返回较大数

package com.example.hellokt

import kotlin.math.max


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)

代码简化

如果函数中只有一行代码时,可以不必编写函数体,中间用等号链接就好

fun largerNumber1(num1: Int,num2: Int): Int = max(num1,num2)

Kotlin有出色的推导机制,因为max()返回的是Int值,所以通过等号与它相连的largerNumber2()函数返回值也是Int值,可以省略返回值类型

fun largerNumber2(num1: Int,num2: Int) = max(num1,num2)

程序的逻辑控制

if条件语句

语法规则

Kotlin中的if相较与其他的编程语言有一个额外的功能:它可以有返回值!
返回值就是if语句每一个条件中最后一行代码的返回值。

代码实践

返回较大数(基础版)

fun largerNumber(num1: Int, num2: Int): Int
    var value = 0
    if (num1 > num2)
        value = num1
     else 
        value = num2
    
    return value

返回较大数(简化版)

fun largerNumber1(num1: Int,num2: Int): Int 
    var value = if (num1 > num2)
        num1
     else 
        num2
    
    return value

返回较大数(精简版)

fun largerNumber2(num1: Int,num2: Int): Int 
    return if (num1 > num2)
        num1
     else 
        num2
    

返回较大数(进一步精简版)

fun largerNumber3(num1: Int,num2: Int) = if (num1 > num2)
    num1
 else 
    num2

返回较大数(终极精简版)

fun largerNumber4(num1: Int,num2: Int) = if (num1 > num2) num1 else num2

when条件语句

精确匹配

语法规则

在需要很多判断条件时,可以使用when语句
伪代码:

when (参数)
	匹配值 -> 执行逻辑
	匹配值 -> 执行逻辑
	匹配值 -> 执行逻辑
	匹配值 -> 执行逻辑
	else -> 执行逻辑

执行逻辑只有一行时,花括号可以省略

代码实践

类型匹配

语法规则

通过when语句和is关键词,对变量的类型进行判断
伪代码:

when (参数)
	is 匹配类型 -> 执行逻辑
	is 匹配类型 -> 执行逻辑
	is 匹配类型 -> 执行逻辑
	else -> 执行逻辑

执行逻辑只有一行时,花括号可以省略

代码实践

不带参用法

代码实践

字符串判断(补充)

语法规则

判断字符串和对象是否相等可以直接使用变量 == 关键字
字符串.startsWith(“xxx”),可以匹配所有以xxx开头的字符串

代码实践

循环语句(for循环)

Kotlin中的 while语法与java编程语言基本相同

双闭端区间

语法规则

val range = 0..10创建一个双端闭区间,表示为 [ 0 , 10 ] [0,10] [0,10]
其中..为关键字,在..左右两边指定区间的左右端点

代码实践

左闭右开区间

语法规则

val range = 0 until 10创建一个左闭右开区间,表示为 [ 0 , 10 ) [0,10) [0,10)
其中until为关键字,在until左右两边指定区间的左右端点
for循环中你还可以通过step关键字,实现跳过一些元素的效果

代码实践


相当于for(int i = 0;i < 10;i += 2)

降序区间

语法规则

关键字a downTo b,创建一个范围为 [ a , b ] [a,b] [a,b]的降序区间

代码实践


for-in循环不仅可以用来遍历区间还可以遍历集合

面向对象编程

类和对象

语法规则

创建一个Person类:

class Person 
    var name = ""
    var age = 0

    fun eat() 
        println(name + " is eating. He is " + age + " years old.")
    

main函数中进行实例化

fun main()
    val p =Person()
    p.name = "Jack"
    p.age = 19
    p.eat()

p就是Person类的一个实例,也可以成为一个对象

代码实践

继承

语法规则

父类:在类的前面加上open关键字,则表示该类可以被继承

open class Person...

子类:继承的关键字为:

class Student : Person()...

代码实践

构造函数

主构造函数

语法规则

每一个类默认会有一个不带参数的主构造参数,当然也可以指明参数。
主构造参数的特点是没有函数体,直接定义在类名的后面即可。

class Student(val sno: String, val grade: Int) : Person()
    

主构造函数中的逻辑语句写在init结构体

代码实践

子类中的构造函数必须调用父类中的构造函数

当父类中的构造函数有很多时可以在继承时通过父类后面的括号来指定
错误示范:

想要解决这个错误可以在Student类的主构造函数中加上着它父类需要的参数,在将参数传递给父类

注意:在Student类的主构造函数中增加name和age这两个字段时, 不能再将它们声明成val,因为在主构造函数中声明成val或者var的参数 将⾃动成为该类的字段,这就会导致和⽗类中同名的name和age字段造成 冲突。因此,这⾥的name和age参数前⾯我们不⽤加任何关键字,让它的作⽤域仅限定在主构造函数当中即可。

次构造函数

任何⼀个类只能有⼀个主构造函数,但是可以有多个次构造函数。次构造函数也可以⽤于实例化⼀个类,这⼀点和主构造函数没有什么 不同,只不过它是有函数体的。
Kotlin规定,当⼀个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调⽤主构造函数(包括间接调⽤)。

语法规则

关键字constructor

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)

  • 第⼀个次构造函数接收nameage参数,然后它⼜通过this关键字调用了主构造函数,并将snograde这两个参数赋值成初始值;
  • 第⼆个次构造函数不接收任何参数,它通过this关键字调用了第⼀个次构造函数,并将nameage参数也赋值成初始值。
  • 第⼆个次构造函数属于间接调⽤主构造函数。

经过上面的代码现在有三种方式对Student类进行实例化

val stu1 = Student()
val stu2 = Student("Jack", 19)
val stu3 = Student("a123", 5, "Jack", 19)

类中只有次构造函数时


因为Student类没有主构造函数,继承Person类的时候也就不需要再加上括号了

接口

语法规则

关键字含义
interface定义接口
override实现接口中的函数
:使用接口
,分割继承的父类和使用的接口

代码实现

  • Study接⼝中定义了readBooks()doHomework()这两个待实现函数, 因此Student类必须实现这两个函数。
  • doStudy()函数接收⼀个Study类型的参数, 由于Student类实现了Study接⼝,因此Student类的实例是可以传递给doStudy()函数的,接下来调用Study接⼝readBooks()doHomework()函数,这种就叫作⾯向接⼝编程,也可以称为多态

对接口中的函数进行默认实现

可见性修饰符

修饰符Kotlin中的含义Java中的含义
public所有类可见(默认)所有类可见
private当前类可见当前类可见
protected当前类、子类可见当前类、子类、同一包路径下的类可见
default同一包路径下的类可见(默认)
internal同一模块中的类可见

数据类

在⼀个规范的系统架构中,数据类通常占据着非常重要的角色,用于将服务器端或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。

通常需要重写的方法

方法名用途
equals()判断两个数据类是否相等
hashCode()equals()的配套⽅法,也需要⼀起重写,否则会导致HashMapHashSethash相关的系统类⽆法正常⼯作
toString提供更清晰的输入日志,否则⼀个数据类默认打印出来的就是一行内存地址。

代码对比

构建⼀个手机数据类,只有品牌和价格两个字段。

Java代码

public class CellphoneJ 
    String brand;
    double price;
    public CellphoneJ(String brand, double price) 
        this.brand = brand;
        this.price = price;
    
    @Override
    public boolean equals(Object obj) 
        if (obj instanceof CellphoneJ) 
            CellphoneJ other = (CellphoneJ) 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 "CellphoneJ(brand=" + brand + ", price=" + price + ")";
    

Kotlin代码

data class Cellphone(val brand: String, val price: Double)

java中大段的代码用kotlin实现只需要一行
当在⼀个类前面声明了data关键字时,就表明这个类是⼀个数据类,Kotlin会根据主构造函数中的参数将equals()hashCode()toString()等固定且无实际逻辑意义的方法自动生成,从而大大减少了开发的工作量。

代码实现


补充:尝试了一下书中说的删除data关键字,得到了截然不同的结果

单例类

单例模式是最常用、最基础的设计模式之⼀,它可以用于避免创建重复的对象。比如我们希望某个类在全局最多只能拥有⼀个实例,此时就可以使用单例模式。

代码对比

Java代码

public class SingletonJ 
    private static SingletonJ instance;
    private SingletonJ() 
    public synchronized static SingletonJ getInstance() 
        if (instance == null) 
            instance = new SingletonJ();
        
        return instance;
    
    public void singletonJTest() 
        System.out.println("singletonJTest is called.");
    

在调用上述代码时:

SingletonJ singletonJ = SingletonJ.getInstance();
singletonJ.singletonTest();

Kotlin代码

object Singleton 

此时Singleton就已经是⼀个单例类了
可以在其中直接编写需要的函数:

object Singleton 
    fun singletonTest() 
        println("singletonTest is called.")
    

调用方法:

Singleton.singletonTest()

Lambda编程

集合的创建与遍历

List

初始化方法1(基础)

val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")

初始化方法2(不可变)

val list = listOf("Apple","Banana","Orange","Pear","Grape")

初始化方法3(可变)

val list = mutableListOf("Apple","Banana","Orange","Pear","Grape")
list.add("Watermelon")

遍历集合

Set

Set中不可以存放重复元素,如果存放了多个相同的元素,只会保留其中的一份。
用法与List基本相同,只是创建集合的方式变成了setOf()mutableSetOf()

Map

Map是⼀种键值对形式的数据结构

初始化方法1(传统)

传统的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)

初始化方法2(“数组下标”)

val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5

在读取时:

val num = map["Apple"]

初始化方法3(不可变)

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)

初始化方法4(可变)

val map = mutableMapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4)

遍历集合

集合的函数式API

不知是因为版本问题还是其他什么原因,这一小节的代码我在尝试的时候会报错,且暂时没有找到解决的方法,所以决定暂时跳过,预计于2021.11.21前解决问题并将这一段补上。不好意思

暂时在网上搜寻无果,如果有大佬看到这篇文章,且能根据报错信息提出修改建议,本人感激不尽

Java函数式API的使用

API的英文全程为:Application Programming Interface,中文译为应用程序编程接口。所以,API就是指接口。
所谓函数式接口,就是指那些只定义了一个待实现的抽象函数的接口。

Kotlin中调⽤Java方法时也可以使⽤函数式API,只不过这是有⼀定条件限制的。具体来讲,如果我们在Kotlin代码中调用了⼀个Java方法,并且该方法接收⼀个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有⼀个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。

示例1:Runnable接口

使用匿名类的写法,创建⼀个Runnable接口的匿名类实例,并将它传给了Thread类的构造方法,最后调用Thread类start()方法执行这个线程。

Thread(object : Runnable
    override fun run() 
        println("Thread is running")
    
).start()

因为Runnable类中只有⼀个待实现方法,即使这里没有显式地重写run()⽅法,Kotlin也能自动明白Runnable后⾯的Lambda表达式就是要在run()方法中实现的内容。所以就可以精简代码:

Thread(Runnable 
    println("Thread is running")
).start()

如果⼀个Java⽅法的参数列表中有且仅有⼀个Java单抽象方法接口参数,我们还可以将接口名进行省略。当Lambda表达式是方法的最后⼀个参数时,可以将Lambda表达式移到方法括号的外面。同时,如果Lambda表达式还是方法的唯⼀⼀个参数,还可以将方法的括号省略,最终简化结果如下:

Thread  println("Thread is running") .start()

三段代码最终可以得到同样的运行结果:

示例1:OnClickListener接口

Kotlin代码

button.setOnClickListener

空指针检查

空指针是⼀种不受编程语⾔检查的运行时异常,只能由程序员主动通过逻辑判断来避免,但即使是最出色的程序员,也不可能将所有潜在的空指针异常全部考虑到。

可空类型系统

Kotlin利用编译时判空检查的机制几乎杜绝了空指针异常,将空指针异常的检查提前到了编译时期。

语法规则

可为空的类型系统就是在类名的后面加上⼀个问号?
Int表示不可为空的整型,⽽Int?就表示可为空的整型;
String表示不可为空

以上是关于《Android》Chap.2 入门Kotlin的主要内容,如果未能解决你的问题,请参考以下文章

最全Android Kotlin入门教程(Kotlin 入门指南高级Kotlin强化实战Kotlin协程进阶实战)

Android:Kotlin详细入门学习指南-基础语法

Android:Kotlin详细入门学习指南-高阶函数-基础语法

Android:Kotlin详细入门学习指南-高阶函数-基础语法

Android:Kotlin详细入门学习指南-类和对象(上)-基础语法

Android:Kotlin详细入门学习指南-类和对象(下)-基础语法