一、变量和方法
变量
Kotlin中定义一个变量,只允许在变量前声明两种关键字:val
和var
。
val
(value的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋
值,对应Java中的final变量。
var
(variable的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新
赋值,对应Java中的非final变量。
Kotlin 可以不需要定义类型,会自动推导类型。
val a = 22
println("a = " + a)
如果要对一个变量延迟赋值,需要显式地声明变量类型。
var b: Int = 0
相对于Java中的基本数据类型,Kotlin全部使用了对象数据类型。
Kotlin | Int | Long | Short | Float | Double | Boolean | Char | Byte |
---|---|---|---|---|---|---|---|---|
Java | int | long | short | float | double | boolean | char | byte |
可见性修饰符
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
internal | 无 | 同一模块中的类可见 |
方法
关键字fun
用来声明方法,参数的声明格式是“参数名: 参数类型”,括号后面的用于声明返回类型。
fun maxNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
Kotlin的if
语句可以有返回值,返回值是每个条件的最后一行代码。
当方法中只有一行代码时,可以直接将这行代码写在方法定义的尾部,中间用等号连接。
fun maxNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
when
相对于Java中的switch语句,Kotlin中使用when语句
fun getScore(name: String) = when (name) {
"Tom" -> 93
"Sam" -> 84
"Jack" -> 52
else -> 0
}
循环语句
区间: Kotlin中引入了区间的使用,创建两端闭区间的关键字是..
,相当于数学中的[1, 10]
val range = 1..10
for循环取消了 for-i 循环,使用 for-in 循环
for(i in 1..10) {
println(i)
}
until
关键字,表示区间左闭右开,相当于数学中的[0, 10)
val ru = 0 until 10
step
关键字,递增的步数,相当于for-i中的 i = i + 2
for(i in 0 until 10 step 2) {
println(i)
}
downTo
关键字,降序区间,相当于for(i = 5; i >= 1; i--)
for (i in 5 downTo 1) {
println(i)
}
二、对象
class Person {
}
val p = Person()
继承和构造方法
Kotlin中任何一个非抽象类默认都是不可以被继承的,想要被继承要在前面加上open关键字
open class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
- Java继承的关键字是extends,而在Kotlin中变成了一个冒号
- kotlin的构造函数分为 主构造函数 和 次构造函数
- 主构造函数的特点是没有函数体,直接定义在类名的后面即可。比如Student这种写法
- 如果想在主构造函数中编写一些逻辑,Kotlin提供了一个
init
结构体 - 像java一样子类中的构造函数必须调用父类中的构造函数,但是主构造函数没有构造方法,所以 Person 后面的括号表示调用哪一个构造方法
class Student(val sno: String, val grade: Int) : Person(){
var sno = ""
var grade = 0
init {
println("grade is " + grade)
}
}
主构造函数中显示指明参数
open class Person2 (val name: String, val age: Int){
}
主构造函数中声明成val或者var的参数将自动成为该类的字段,比如上面父类中的name和age,所以下面子类中的name和age不能加任何关键字,否则和父类冲突。
- 次构造函数是通过constructor关键字来定义的
- 所有的次构造方法都必须调用主构造方法(包括间接调用)
class Student2(val son: String, val grade: Int, name: String, age: Int) : Person2(name, age) {
constructor(name: String, age: Int) : this("", 0, name, age) {
}
}
少见的特殊情况
一种非常特殊的情况:类中只有次构造函数,没有主构造函数,这种不常用
在次构造方法中还是要用super
调用了父类的构造方法
class Student3 : Person2 {
constructor(name: String, age: Int) : super(name, age) {
}
}
方法的参数默认值
在定义方法时可以给任意参数设定默认值,这样就不会强制要求为此参数传值,在没有传值的情况下会自动使用参数的默认值。
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
当设定默认值的参数是最后面的参数时,可以直接省略掉
printParams(5)
当设定默认值的参数不是最后面的时候,可以通过键值对方式来传参调用。
printParams(num = 99, str = "abc")
printParams(str = "abc")
给方法设定参数默认值和键值对方式传参,很大程度上替代类的次构造方法。
接口
interface Study {
fun readBook()
fun doHomeWork()
}
Java中继承的关键字是extends
,实现接口的关键字是implements
,而Kotlin中统一使用冒号:
,中间用逗号进行分隔。
class Student4(name: String, age: Int) : Person2(name, age), Study {
override fun readBook() {
println(name + " is reading")
}
override fun doHomeWork() {
println(name + " doing homework")
}
}
数据类
data
关键字表示是一个数据类,会自动生成equals()、hashCode()、toString()等
data class CellPhone(val brand: String, val price: Double)
使用举例
val phone1 = CellPhone("Xiaomi", 1999.5)
val phone2 = CellPhone("Xiaomi", 1999.5)
println(phone1)
println(phone1 == phone2)
输出
CellPhone(brand=Xiaomi, price=1999.5)
true
单例
object Singleton {
fun singletonTest() {
println("singletonTest is called")
}
}
关键字object
表示创建单例类,不加class
关键字。
Singleton.singletonTest()
调用虽然看上去像是静态方法的调用,但其实Kotlin在背后自动帮我们创建了一个Singleton类的实例。
三、集合
List
使用listOf()
方法创建一个不可变的集合,只能读取不能增删改
val list = listOf("Apple", "Banana", "Orange", "Pear")
for (fruit in list) {
println(fruit)
}
使用mutableListOf()
函数创建一个可变的集合
val list2 = mutableListOf("Apple", "Banana", "Orange", "Pear")
list2.add("Grape")
Map
Kotlin中的Map推荐使用一种类似于数组下标的语法结构
val map = HashMap<String, Int>()
//添加
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
//读取
val number = map["Banana"]
//遍历
for ((fruit, number) in map) {
println(fruit + number)
}
简化用法,可以直接传入初始化的键值对组合来完成对Map集合的创建。
val map2 = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)
Lambda表达式
Lambda就是一小段可以作为参数传递的代码。
语法结构:
{ 参数名1: 参数类型, 参数名2: 参数类型 -> 函数类体 }
示例:找到字符串集合中最长的那个
这里用到的maxByOrNull()
就是一个普通方法,它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。
val list = listOf("Apple", "Banana", "Pear", "Watermelon")
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxByOrNull(lambda)
Kotlin规定,当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面。所以简化为
val maxLengthFruit = list.maxByOrNull() { fruit: String -> fruit.length }
如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型,简化:
val maxLengthFruit = list.maxByOrNull { fruit -> fruit.length }
当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替,简化:
val maxLengthFruit = list.maxByOrNull { it.length }
函数式API
map()
集合中的map()
函数是最常用的一种函数式API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式中指定,最终生成一个新的集合。
示例:让集合中的字符串都变成大写
val list = listOf("Apple", "Banana", "Pear", "Watermelon")
val newList = list.map { it.uppercase()}
for (fruit in newList) {
println(fruit)
}
filter()
filter
函数用来过滤集合中的数据。
示例:输出长度小于等于5的字符串并大写
val list = listOf("Apple", "Banana", "Pear", "Watermelon")
val list2 = list.filter { it.length <= 5 }
.map { it.uppercase() }
any()和all()
any
函数用于判断集合中是否至少存在一个元素满足指定条件,all
函数用于判断集合中是否所有元素都满足指定条件。
val list = listOf("Apple", "Banana", "Pear", "Watermelon")
val any = list.any { it.length <= 5 }
val all = list.all { it.length <= 5 }
println("any is " + any + ", all is " + all)
输出结果
any is true, all is false
Java函数式API的使用
如果我们在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。
示例:
创建子线程Java写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("running");
}
}).start();
翻译成Kotlin写法,由于Kotlin舍弃了new
关键字,因此创建匿名类实例改用object
关键字。
Thread(object : Runnable{
override fun run() {
println("running")
}
}).start()
简化:
Thread {
println("running")
}.start()
同理点击事件监听简写:
button.setOnClickListen {
}
四、空指针检查
Kotlin默认所有的参数和变量都不可为空,当需要为空时,类名的后面加上一个问号表示可空类型。
例如Int
表示不可为空的整型,而Int?
就表示可为空的整型;String
表示不可为空的字符串,而String?
就表示可为空的字符串。
判空辅助工具
操作符?.
表示当对象为空时则什么都不做
a?.doSomeing()
操作符?:
表示如果左边的结果不为空就返回左边,否则就返回右边的结果。
val c = a ?: b
非空断言!!
表示相信对象不会为空,强行编译通过。
a!!.doSomeing()
let
let
函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中。
obj.let { o ->
//编写逻辑代码
}
结合?.
进行非空判断
study?.let {
it.readBook()
it.doHomework()
}
五、其他
标准函数with、run和apply
Kotlin的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数。
with
with
函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式。会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。
示例:拼接字符串
val array = listOf("Apple", "Pear", "Banana", "Grape")
val result = with(StringBuilder()) {
append("start eat\n")
for (s in array) {
append(s).append("\n")
}
append("end").toString()
}
println(result)
Lambda表示中有StringBuilder
对象的上下文,所以可以直接调用方法,最后一行返回结果。
run
run
函数通常不会直接调用,而是要在某个对象的基础上调用,其他方面和with
函数是一样的。
val array = listOf("Apple", "Pear", "Banana", "Grape")
val result = StringBuilder().run {
append("start eat\n")
for (s in array) {
append(s).append("\n")
}
append("end").toString()
}
println(result)
apply
apply
和run
差不多,只是无法指定返回值,而是会自动返回调用对象本身。
val array = listOf("Apple", "Pear", "Banana", "Grape")
val result = StringBuilder().apply {
append("start eat\n")
for (s in array) {
append(s).append("\n")
}
append("end")
}
println(result.toString())
静态方法
Kotlin却极度弱化了静态方法这个概念,非常推荐使用单例类的方式来实现。
如果只是要在普通类中定义静态方法可以使用companion object
里面定义方法。
class Fish {
companion object {
fun doAction() {
}
}
}
companion object
实际上不是真正的静态方法,当在Java代码中时无法调用的。
真正的静态方法有两种方式 注解 和 顶层方法
注解:
companion object {
@JvmStatic
fun doAction() {
}
}
给 单例类 或 companion object 中的方法加上@JvmStatic
注解,那么Kotlin编译器就会将这些方法编译成真正的静态方法。
顶层方法
顶层方法指的是那些没有定义在任何类中的方法,直接写在kotlin文件中的。
在kotlin代码中任何位置都可以直接调用,在Java代码中要使用文件名来调用。
const
const
关键字是用来定义常量,只有在单例类、companion object或顶层方法中才可以使用。
官方文档:用于修饰在编译时已知的只读属性。
使用
const val PARAM_DATA = "param_data"
翻译成Java为:
public static final String PARAM_DATA = "param_data";
lateinit
lateinit
关键字是用来延迟初始化。
private lateinit var adapter: ChatMsgAdapter
判断变量是否已经被初始化,::变量.isInitialized
if (!::adapter.isInitialized) {
adapter = ChatMsgAdapter(list)
}