这是我参与「第四届青训营 」笔记创作活动的第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 数据类型 | 说明 |
|---|---|---|
| short | Short | 16位整型 |
| int | Int | 32位整型 |
| long | Long | 64位整型 |
| byte | Byte | 8位整型(字节型) |
| double | Double | 64位浮点数 |
| float | Float | 32位浮点数 |
| char | Char | 字符型 |
| boolean | Boolean | 布尔型 |
函数
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
继承
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
| 权限修饰符 | Java | kotlin |
|---|---|---|
| public | 所有类可见 | 所有类可见(默认) |
| private | 当前类可见 | 当前类见 |
| protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
| default | 同一包路径下的类可见(默认) | 无 |
| internal | 无 | 同一模块中的类可见 |
可以看出 Java 与 kotlin 主要差别在包和模块下的可见性
IDEA 下
Android Studio 下
数据类
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 }