Kotlin 入门笔记01 | 青训营笔记

312 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的第2天

Kotlin 入门笔记01

Kotlin 为 JVM 语言,.kt 代码会被编译成 .class 的 字节码,由 JVM 执行

语言特性

真·万物皆对象

Kotlin 没有基本数据类型,全部都是对象

静态强类型

静态是指 Kotlin 的类型检查发生在编译期,即一个变量是什么类型是在编译期确定的。静态语言一般要在声明变量时指定类型,而 Kotlin 提供了类型推断的功能。

强类型是指 Kotlin 的变量类型一旦确定,除非显式强转,不然其数据类型不会改变,也不能赋其他数据类型的值。

默认常量

kotlin 认为如果一个变量不需要被修改,就应该被赋值为常量,一个类不需要被继承,就应该默认被 final 修饰。

尽可能避免空指针异常

kotlin 提供了良好的机制和代码检查来避免空指针异常

工作原理

编译型语言:将所有代码进行一次编译,得到可执行文件,然后执行该文件即可。

解释型语言:代码需经过虚拟机一行行解释成机器指令运行。

在执行 Java 代码时,会先 Java 的代码编译成 class 文件,然后交给 JVM 解释执行。

而如果有一门语言也能将自己的代码编译成 class 文件,就可以交给 JVM 解释执行,这类语言就叫 JVM 语言。

Kotlin 就是一门 JVM 语言。

变量

var 变量 variable

val 常量 value

推荐优先声明常量,需要修改时再变为变量

和 Go 语言一样,语句最后不用加分号,加了也行,不会报错。

 fun main(args: Array<String>) {
     println("Hello World!")
 ​
     var a = 1
     val b = 2
     println(add(a,b))
 }
 ​
 fun add(n:Int, m:Int):Int {
     return n + m
 }

类型推导

 fun main() {
     var a = 10
     println(a)
     //a = "duide" // Type mismatch. Required: Int Found: String
 }

显示声明

变量名: [数据类型]

 val a: Int = 10

对象数据类型

kotlin 与 Java 中的基本数据对应的封装类不同的仅有两个:Int 和 Char (Java 中分别为 Integer 和 Character)

其余的均为 首字母变成大写。比如 long 对应 Long

Java中的基本数据类型对应的Kotlin 数据类型说明
shortShort16位整型
intInt32位整型
longLong64位整型
byteByte8位整型(字节型)
doubleDouble64位浮点数
floatFloat32位浮点数
charChar字符型
booleanBoolean布尔型

函数

fun 关键字标识

 fun largerNumber(num1: Int, num2: Int): Int {
  return max(num1, num2)
 }

当一个函数中只有一行代码时,Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可。

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

类型推导返回值

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

逻辑控制

分支结构

Kotlin 中的条件语句主要有两种实现方式:if 和 when。

Kotlin 中的if语句相比于 Java 有一个额外的功能,它是可以有返回值的,返回值就是if语句每 一个条件中最后一行代码的返回值。

 fun largerNumber(num1: Int, num2: Int): Int {
     val value = if (num1 > num2) {
         num1
     } else {
         num2
     }
     return value
 }

语句压缩

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

when 语句和 if 语句一样, 也是可以有返回值的

when 语句允许传入一个任意类型的参数,然后可以在 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 相当于 Java 中的 instanceof

通过 IDEA 可以将 kotlin 生成的字节码反编译成 Java

 public static final void checkNumber(@NotNull Number num) {
     Intrinsics.checkNotNullParameter(num, "num");
     String var2;
     if (num instanceof Integer) {
         var2 = "number is Int";
         System.out.println(var2);
     } else if (num instanceof Double) {
         var2 = "number is Double";
         System.out.println(var2);
     } else {
         var2 = "number not support";
         System.out.println(var2);
     }
 }

和 Go 语言的 switch 一样,when 可以在 匹配结果处写条件判断语句

 fun getScore(name: String) = when {
     name.startsWith("Tom") -> 86
     name == "Jim" -> 77
     name == "Jack" -> 95
     name == "Lily" -> 100
     else -> 0
 }

循环结构

kotlin 和 Java 一样有两种循环 while 和 for

kotlin 的 while 和 Java 的 while 没有区别。

for-i 这样的索引循环被弃用,for-each 这样的迭代器循环更改成了 for-in 循环

两端闭区间

..是创建两端闭区间的关键字

 val range = 0..10

使用循环遍历区间

 fun main() {
     for (i in 0..1000000) {
         println(i)
     }
 }

用 IDEA 进行反编译

 public static final void main() {
     int i = 0;
 ​
     for(int var1 = 1000000; i <= var1; ++i) {
         System.out.println(i);
     }
 ​
 }

这里 kotlin 将 for-in 循环翻译成了 for-i 的形式

并且根据需求不同,用于迭代的 i 的数据类型也不同

i 从 0 到 100

 fun main() {
     for (i in 0..100) {
         println(i)
     }
 }

反编译

 public static final void main() {
     int i = 0;
 ​
     for(byte var1 = 100; i <= var1; ++i) {
         System.out.println(i);
     }
 ​
 }

左闭右开区间

 val range = 0 until 10

0 util 10 即 [0,10)

用 step 关键字指明每次迭代的步长

 fun main() {
     for (i in 0 until 10 step 2) {
         println(i)
     }
 }

创建一个降序的区间,可以使用 downTo 关键字

 fun main() {
     for (i in 10 downTo 1) {
         println(i)
     }
 }

面向对象

(虽然字符串也支持用 + 号拼接,但是 kotlin 推荐使用 字符串内嵌表达式)

 class Person {
     var name = "mie"
     var age = 3
     fun play() {
         println("$name is playing. He is $age years old.")
     }
 }

调用

 val p = Person()
 p.play()

kotlin 的对象默认是 public final

class默认修饰.png

继承

kotlin 中类默认不可继承,如果要使一个类可以继承,需要在父类上加上 open 关键字。

 open class Person(name: String, age: Int) {
     var name = "mie"
     var age = 3
     fun play() {
         println(name + " is playing. He is " + age + " years old.")
     }
 }

另外如果一个类不是为了被继承,在 Java 中也推荐使用 final 修饰。

Student 继承 Person

 class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
     init {
         println("sno is $sno")
         println("grade is $grade")
     }
     // 重写 play() 方法,需要在方法前加 open 关键字
     override fun play() {
         println("$name is playing. He is $age years old.")
     }
 }

Person(name, age) 代表调用了 Person 类的构造函数

init 代码块用于初始化

创建子类对象并调用

 val student = Student("2000", 2, "Mie", 17)
 student.play()

主要构造函数和次要构造函数

 // 主构造函数 在继承时即调用了父类的构造函数
 class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
 ​
     // 次要构造函数, 是 Student类的构造函数
     constructor(name: String, age: Int) : this("", 0, name, age) { // 次要构造函数①
     }
 ​
     constructor() : this("", 0) { // 次要构造函数② 通过 this 调用 次要构造函数①
     }
 }

Kotlin 允许只存在次要构造函数,但是要通过 super() 来调用父类的构造函数

 class Student : Person {
     constructor(name: String, age: Int) : super(name, age) {
     }
 }

is-a 与 has-a

虽然 Java 中继承的编程思想是 子类是一个父类(即is-a)

比如 猫是一个动物

但是就目前继承的实现机制而言,实际上做的是 子类有一个父类 (即has-a)

(具体可以在 Java 中写一个父类和子类,让父类和子类的一个成员变量同名但不同值,然后通过父类的引用和子类的引用分别调用并观察值,这时会发现两个同名变量实际上是分别属于父类和子类的两个变量)

故创建子类对象时,一定要调用父类的构造函数来创建一个父类

接口

kotlin 中的接口关键字也是 interface ,接口中默认定义的没有函数体的抽象函数,也可以有默认函数(JDK 8 里也出现了同样的特性)

 interface Write {
     fun write() // 抽象函数,实现类必须实现
     fun draw() { // 默认函数
         println("Write interface draw!")
     }
 }

kotlin 中的继承和实现都使用冒号 : ,中间用逗号 , 分隔

override 表明子类重写的函数

 class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age), Write {
 ​
     override fun play() {
         println("$name is playing. He is $age years old.")
     }
 ​
     override fun write() {
         println("write!")
     }
 ​
 }

调用

 val student = Student("2000", 2, "Mie", 17)
 student.play()
 student.write()
 student.write()

可见性修饰符

Java 和 kotlin 都有四种权限修饰符, Java 默认缺省,kotlin 默认 public

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

可以看出 Java 与 kotlin 主要差别在包和模块下的可见性

IDEA 下

模块与包IDEA.png

Android Studio 下

模块与包Android.png

数据类

class 前使用 data 关键字声明数据类

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

数据类会根据字段自动生成相应的 get() 函数(如getBrand())、构造函数、component()函数(如 component1()),深拷贝函数、synthetic method、toString()、hashCode()、equals() 等等。

并且如果类中没有其他内容可以不用写花括号。

单例类

单例类是指在全局最多只有一个实例的类

使用 object 关键字定义单例类

 object Singleton {
     fun singletonPlay() {
         println("singleton!")
     }
 }

调用

 Singleton.singletonPlay()

调用时 kotlin 会自动创建唯一的 实例对象

集合

创建与遍历

kotlin 和 Java 同样有三种主要类型的集合:List、Set、Map

List

与 Java 类似的初始化

 val list1 = ArrayList<String>()
 list1.add("Mie")
 list1.add("HaoYe")
 list1.add("DuiDe")

使用内置函数 listOf()

 val list2 = listOf("Mie", "HaoYe", "DuiDe")

使用 listOf() 创建的集合是不可变的集合,只能读取,无法添加、修改和删除

使用 mutableListOf() 创建可变数组

 val list3 = mutableListOf("Mie", "HaoYe")
 list3.add("DuiDe")

需要注意的是,list3 本身是一个引用类型,存储的是 List 数组的地址。list3 声明为 val 仅代表 list3 无法重新赋值为另一个 List 数组的地址。

使用 for-in 对 集合进行遍历

 for (l in list3) {
     println(l)
 }

使用 .indices 获取遍历用的索引

 for (index in res.indices step 2){
     println(res[i])
 }
Set

与 List 类似,只是Set 中不能存放相同的元素,会自动去重。

使用 setOf() 和 utableSetOf() 创建 Set 集合并使用 for-in 遍历

 val set1 = setOf("Mie", "HaoYe", "DuiDe")
 val set2 = mutableSetOf("Mie", "HaoYe")
 set2.add("DuiDe")
 ​
 for (s in set2) {
     println(s)
 }
Map

Map 集合中存放 键值对 (Key-Value)

与 Java 类似的初始化

 val map1 = HashMap<String, Int>()
 map1.put("Mie", 100)
 map1.put("HaoYe", 200)
 map1.put("DuiDe", 300)

与 Go 类似的初始化

 val map2 = HashMap<String, Int>()
 map2["Mie"] = 100 // kotlin 推荐
 map2["HaoYe"] = 200
 map2["DuiDe"] = 300

使用内置函数 mapOf() 和 mutableMapOf()

 val map3 = mapOf("Mie" to 100, "HaoYe" to 200, "DuiDe" to 300)
 val map = mutableMapOf("Mie" to 100, "HaoYe" to 200, "DuiDe" to 300)

这里的 to 不是关键字,是 infix 函数

for-in 遍历 map

 for (entry in map3) { // 获取键值对
     println("entry: $entry")
 }
 ​
 for ((k, v) in map) { // 分别获取键和值
     println("key: $k value: $v")
 }

Lambda 表达式

与 Java 中的 Lambda 表达式类似

 {参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}

函数体中可以编写任意行代码,并且最后一行代码会自动作为Lambda表达式的返回值。

 val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
 val lambda:(String) -> Int = { fruit: String -> fruit.length }
 val maxLengthFruit1 = list.maxByOrNull(lambda)
 println(maxLengthFruit)

由上面代码的类型指明可以得出,Lambda 也是一种数据类型

简化写法

当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面

 val maxLengthFruit2 = list.maxByOrNull() { fruit: String -> fruit.length }

Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略

 val maxLengthFruit3 = list.maxByOrNull { fruit: String -> fruit.length }

Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型

 val maxLengthFruit4 = list.maxByOrNull { fruit -> fruit.length }

当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用 it 关键字来代替

 val maxLengthFruit5 = list.maxByOrNull { it.length }