Kotlin语言学习
背景
从2017年开始,Google在2017年的I/O大会上宣布,Kotlin正式成为Android的一级开发语言。
Google在2019年的I/O大会上宣布Kotlin成为了Android第一开发语言,后续也将优先提供Kotlin版本的API使用说明。
Kotlin是由JetBrains公司开发与设计的,2016年发布了1.0正式版本。
特性
Kotlin和Java一样,都是在编译之后变成字节码文件,然后由虚拟机解释执行。其中字节码相当于某种中间语言,由更高级的抽象语言解析编译得来。
Kotlin可以直接调用Java编写的代码,也可以无缝使用Java的第三方开源库。
语法
基础入口函数
fun main(args: Array<String>) {
println("Hello World!")
println("Program arguments: ${args.joinToString()}")
}
Java函数入口点也必须在一个类中,Kotlin相比之下则不太相同,只需要定义main方法即可。
变量
- val:指的是value,用于声明一个不可变的变量,对应Java的final。一般优先使用val是个好习惯。
- var:指的是variable,声明一个可变的变量,对应Java中非final变量
Kotlin默认使用类型推导机制 val a = 9 但是也可以显式赋值,类似下面这样 val a: Int = 9 Kotlin已经完全不使用基本数据类型了,全部使用对象数据类型。 这里的8大数据类型,都是原来Java的基本数据类型的关键词,并且第一个字母大写。
函数
函数的定义方法
fun methodName(param1: Int, param2: Int): Int {
return 0
}
程序逻辑控制
- if相比Java唯一的变化就是,它可以有返回值了。
可以赋值
fun largerNumber(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
也可以直接通过return返回
fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
甚至可以通过下面的语法糖使用
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}
以及压缩成一行 fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
- when的逻辑控制 原始例子
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的完整格式是下面这样子 匹配值 -> { 执行逻辑 } 其中花括号可以省略
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
when也可以不传入参数使用
fun getScore(name: String) = when {
name.startsWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
其中字符串或对象是否相等直接使用==判断即可,不需要Java那样使用equals方法
- while循环的写法和Java完全一致
- for-i循环被移除,但是for-each循环被大幅加强,变成了for-in循环。
关于区间的概念 val range = 0..10 上述代码表示创建了一个0到10的区间,并且两端都是闭区间,这意味着0到10这两个端点都是包含在区间中的,用数学的方式表达出来就是[0, 10]
val range = 0 until 10 使用until关键字来创建一个左闭右开的区间[0, 10)
有了区间之后,可以通过for-in循环来遍历这个区间
fun main() {
for (i in 0..10) {
println(i)
}
}
使用step图跳过元素
fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}
相当于for-i循环中i = i + 2的效果
使用downTo关键字创造降序区间
fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
这里创建了一个[10, 1]的降序区间
类和对象
一个类,类似下面这样的结构
class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
实例化的时候,并不需要像Java一样添加new关键词
fun main() {
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
继承和构造函数
Kotlin中,只有抽象类可以被继承,在类名前加上open关键词,即可声明一个抽象类。
open class Person {
...
}
Kotlin中的继承类似于C/C++的风格,不是使用extends关键词,而是直接使用冒号。
class Student : Person() {
var sno = ""
var grade = 0
}
关于主构造函数以及初始化结构体
每个类默认都有一个不带参数的主构造函数,主构造函数的特点是没有函数体,如果有需要在构造函数中执行的流程,可以写在init结构中。
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is " + sno)
println("grade is " + grade)
}
}
如果父类的主构造函数包含了需要传入的参数,则直接在继承时的括号中进行传入,例子如下:
open class Person(val name: String, val age: Int) {
...
}
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
...
}
val student = Student("a123", 5, "Jack", 19)
次构造函数
当一个类既有主构造函数,又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。以下是一个例子:
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) {
}
}
val student1 = Student()
val student2 = Student("Jack", 19)
val student3 = Student("a123", 5, "Jack", 19)
次构造函数通过constructor关键字来定义。
一个类中可以没有主构造函数,只使用次构造函数,此时类的声明和Java是类似的。
class Student : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}
在次构造函数中通过super调用父类的构造函数。
由于没有主构造函数,在继承Person类的时候也不需要写括号了
接口
Kotlin的接口和Java几乎完全一致,一个类只能继承一个父类,但是可以实现任意数量的接口。
定义方式如下:
interface Study {
fun readBooks()
fun doHomework()
}
方法只是声明,没有函数体。
实现接口的方式
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中直接使用逗号分隔接口即可实现。
使用override关键字来重写父类或实现接口中的函数。
可以下面的方式调用接口中的方法:
fun main() {
val student = Student("Jack", 19)
doStudy(student)
}
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
Kotlin支持在接口中写默认实现,如果实现该接口的类没有实现默认实现方法,也是没有问题的。
interface Study {
fun readBooks()
fun doHomework() {
println("do homework default implementation.")
}
}
函数的可见性修饰符
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认修饰) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下可见(默认修饰) | 无 |
internal | 无 | 同一模块中的类可见 |
需要使用修饰符的时候,直接加到fun关键词前面即可。
数据类
数据类通常需要重写equals()、hashCode()、toString()这几个方法。其中,equals()方法用于判断两个数据类是否相等。hashCode()方法作为equals()的配套方法,也需要一起重写,否则会导致HashMap、HashSet等hash相关的系统类无法正常工作。toString()方法用于提供更清晰的输入日志,否则一个数据类默认打印出来的就是一行内存地址。
Kotlin直接在一个类前面添加data关键词即可声明一个数据类。
data class Cellphone(val brand: String, val price: Double)
之后Kotlin会自动生成equals()、hashCode()、toString()等固定且无实际逻辑意义的方法。
并且后续判断可以直接使用==进行判断。
fun main() {
val cellphone1 = Cellphone("Samsung", 1299.99)
val cellphone2 = Cellphone("Samsung", 1299.99)
println(cellphone1)
println("cellphone1 equals cellphone2 " + (cellphone1 == cellphone2))
}
单例类
Kotlin创建一个单例类,只需要将class关键词改为object即可。
object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}
调用的时候直接进行类似Java中的静态调用。 Singleton.singletonTest() Kotlin保证全局只创建一个Singleton类实例。
Lambda编程
集合创建与遍历 需要注意的是,下面的例子都可以使用Java的风格来写
- 通过listOf()函数来简化初始化集合写法(创建的事不可变集合) val list = listOf(“Apple”, “Banana”, “Orange”, “Pear”, “Grape”) 并且可以使用for-in来遍历集合
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
for (fruit in list) {
println(fruit)
}
}
- 如果需要创建一个可变集合,则需要使用mutableListOf()函数
fun main() {
val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")
list.add("Watermelon")
for (fruit in list) {
println(fruit)
}
}
-
对应的,set可以使用下面的Lambda方式编程,即setOf()与mutableSetOf()
-
Map的新增来如下用法
val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5
val number = map["Apple"]
- Map也是通过类似的lambda函数进行初始化,mapOf()和mutableMapOf()
val map = mapOf(“Apple” to 1, “Banana” to 2, “Orange” to 3, “Pear” to 4, “Grape” to 5)
这里的to不是关键字,而是infix函数。
下面是对map做遍历的方法,直接将Map的键值对变量一起声明到一个括号里面,遍历的时候会自动赋值给这两个键值对变量。
fun main() {
val map = mapOf("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)
}
}