以下只记录要点
文章目录
常量与变量
常量是 val
变量是 var
全局变量需要在声明时被初始化,如果无法初始化可加上lateinit来声明这个变量可延迟初始化
lateinit var a:String
fun main(args: Array<String>) {
var b = "bbbb"
val c = "cccc"
a = "aaaaaa"
println(a)
}
虽然val是不可变的,但是也不完全等同java的final,比如比如
class Test{
val name = "dean"
get(){
//虽然name是val,但是在取值时仍然能够被修改
return field + " hello"
}
}
空安全设计
var name:String = null
这样会报错,因为name这样声明是不能为空的,如果非要这样需要声明时指定该变量可能为空
加上?解除非空限制
var name:String? = null
但是在使用该变量时需要注意
name.length
直接这样写会报错,因为name可能为空,如果自行用if来判断是否为null,会有现车不安全问题,应该加上?.或者!!.来处理。
用?.代表name不为空时调用length方法, ?: 可加可不加,加上代表name为空时length的值
var length = name?.length ?: 0
用!!.时就会绕开kotlin的空安全设计,和java差不多了,如果name为空,就会抛空指针异常
var length = name!!.length
还有注意如果是可空变量,传值时不能传给不可空参数
var name: String? = "dean"
fun filterName(name: String){
}
filterName(name) 会报错
最后如果是基本类型,尽量不声明为可空,因为声明为可空时会装箱,增加性能开销
var length: Int = 1 不装箱
var length: Int? = 1 装箱
var list = listof(1, 2) 装箱
var array = intArrayof(1, 2) 不装箱
类型检测会自动转换类型
在java中判断类型用instanceof,在判断往后需要手动转换为需要的类型,kotlin不需要
fun getStringLength(any: Any): Int?{
if (any is String){
//如果any是String类型,此时就已经被转换成String了可以当成String使用
return any.length
}
//如果any不是String类型,没有数据转换,即any还是Any类型。
return null
}
一切皆对象
在kotlin中一切皆对象,因为kotlin是基于jvm的,所以和java一样,int值在-128到127之间是用享元模式写的,也就是他们拿到的对象是同一个。
fun main(args: Array<String>) {
val a: Int = 127
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA == anotherBoxedA) //输出true
println(boxedA === anotherBoxedA) //输出true,因为都是从缓存池里面拿的
}
fun main(args: Array<String>) {
val a: Int = 128
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA == anotherBoxedA) //输出true
println(boxedA === anotherBoxedA) //输出false,因为缓存池里面没有,新创建出来的
}
默认类型
整数默认类型是Int,浮点数默认类型是Double
val a = 1000
val b = a / 2 //a和b都是Int
val c = 2.5 //c是Double
数值比较
NaN与其自身相等
NaN比包括正无穷大在内的任何其他元素都大
-0.0小于0.0
if和when可以当做表达式
java中的switch在kotlin中变成了when,并且功能更加强大,条件多变,不仅可以是多种类型,还可以是一些方法的返回值,in / is 等。
除此以外if和when还可以被当做表达式
kotlin不需要java中的三木运算符,因为if可以替代,这边判断代码块里面的最后一行就是b的值
val b = if(a > c){
。。。。
1
}else 2
when 作为表达式必须要有else,除非编译时能够判断出x的所有情况被包含,比如enum
val d = when(x){
is String -> {
println("x is String")
"string 啊"
}
is Int -> {
println("x is Int")
"Int 啊"
}
else -> {
"不知道类型 啊"
}
}
when 后面的判断参数可以省略,那个分支为true就用哪个
fun main(array: Array<String>){
val a = "abc"
when{
a.startsWith("c") -> println("a start with c")
a.startsWith("b") -> println("a start with b")
a.startsWith("a") -> println("a start with a")
}
}
return返回
在内部函数的lambda表达式中直接使用return会直接跳出外层函数
fun foo(){
listOf(1,2,3,4,5).forEach{
//这边直接返回foo的调用者,而不是跳出forEach函数的调用
if(it == 3) return
}
println("foo")
}
如果只是想跳出内部函数可以不用lambda或者使用标签
fun foo(){
listOf(1,2,3,4,5).forEach{
//表示当前循环调过3这种情况
if(it == 3) return@forEach
}
println("foo")
}
如果需要跳出循环,需要显示跳转到外部标签
fun foo(){
run myLoop@{
listOf(1,2,3,4,5).forEach{
if(it == 3) return@myLoop
}
}
println("foo")
}
静态方法、静态常量、单例
如果希望整个项目能够使用的常量或者方法,可以直接现在kt文件顶部,即top level
如果是不同包下同名称的顶级函数也是没关系,调用的时候会根据包名路径来区分
fun dp2dip(){
...
}
fun dp2px(){
...
}
这样在项目的其他地方可以直接使用,就好像这个方法写在本类内部一样
dp2dip()
dp2px()
如果一个类是单例直接在声明时将class改为object
open class A{
open fun aMethod(){
println("a method")
}
}
interface B{
fun bMethod()
}
object C : A(), B {
override fun aMethod() {
super.aMethod()
println("c extends a method")
}
override fun bMethod() {
println("c extends B method")
}
}
调用
C.aMethod()
C.bMethod()
如果只需类有一些静态方法或静态常量则写在companion object里面
class A{
//嵌套对象,外部通过A.D.dMethod来调用
object D{
fun dMethod(){
println("d method")
}
}
}
可以加上伴生对象关键字companion来修饰
open class A{
//这时名称D一般省略,因为companion标识D是A的伴生对象,一个类的伴生对象只能有一个(嵌套对象可以多个),因此写类名也就没必要,外部调用可以直接用 A.LOG_TAG
companion object D {
const val LOG_TAG = "A"
fun staticMethod(){
println("D static method")
}
}
}
object还可以用来创建匿名类
val listener = object: View.OnClickListener(){
override fun onClick(view: View){
}
}
常量const
const只能出现在顶层或者companion object中,并且只能修饰基本数据类型和String类型。
const val LOG_TAG = "aa"
class A{
companion object{
const val LOG_TAG = "aa"
}
}
可变集合和不可变集合
listOf、setOf、mapOf创建的是不可变集合,mutableListOf、mutableSetOf、mutableMapOf创建的是可变集合。但是不可变集合可以通过toMtable来转换成相应的可变集合
不可变指集合的长度和内容
fun main(array: Array<String>){
val list = listOf("a", "b")
list.forEach {
println(it)
}
list.toMutableList()
val list2 = list.toMutableList()
list2.add("c")
list2.forEach {
println(it)
}
}
数组和集合的操作符
- forEach:遍历每个元素
var intArray = intArrayOf(1, 2, 3)
intArray.forEach { println(it) }
输出
1
2
3
- filter:对每个元素进行过滤操作,如果满足条件保留其余去除,最终生成新的集合
var intArray = intArrayOf(1, 2, 3)
intArray.filter { it > 2 }.forEach { println(it) }
输出
3
- map:遍历每个元素并执行给定表达式,形成新的集合
var intArray = intArrayOf(1, 2, 3)
intArray.map { it + 1 }.forEach { println(it) }
输出
2
3
4
- flatMap:遍历每个元素,并为每个元素创建新的集合,最后合并到一个集合中
var intArray = intArrayOf(1, 2)
intArray.flatMap { i -> listOf(i, "i is $i") }.forEach { println(it) }
输出
1
i is 1
2
i is 2
惰性集合Sequence
Sequence当找到需要的结果时,就不会往下处理,没有使用时,也不会处理
fun main(array: Array<String>){
val sequence = sequenceOf(1, 2, 3, 4, 5)
val result = sequence.map {
println("sequence map $it")
it + 1
}.filter {
println("sequence filter $it")
it > 2
}
//当result.first()被调用时,上面代码才会被执行,并且一旦找到了result的第一个元素,上面代码也不会往下执行了
println(result.first())
}
结果
sequence map 1
sequence filter 2
sequence map 2
sequence filter 3
3
对比普通的集合类,每调用一次函数不会生成新的Iterable,Sequence在数据量较大或者数据量未知时,可以减少性能消耗
集合性能对比
同样是1到100_000,平均值计算,intArray快很多
fun main(array: Array<String>){
//array 6ms
println("array start time ${System.currentTimeMillis()}")
val array= Array(100_000){ i -> i * 1 }
var arraySum = 0
array.forEach { arraySum += it }
println(arraySum / array.size)
println("array end time ${System.currentTimeMillis()}")
//intArray 3-4ms
println("int array start time ${System.currentTimeMillis()}")
val intArray = IntArray(100_000){i -> i * 1}
var intArraySum = 0
intArray.forEach { intArraySum += it }
println(intArraySum / intArray.size)
println("int array end time ${System.currentTimeMillis()}")
//list 9ms
println("list start time ${System.currentTimeMillis()}")
val list = List(100_000){i -> i * 1}
var listSum = 0
list.forEach { listSum += it }
println(listSum / list.size)
println("list end time ${System.currentTimeMillis()}")
}
参数默认值、位置参数、命名参数
kotlin允许函数的参数有默认值,在调用时就可以不写
fun printName(name: String = "Dean"){
println("name is $name")
}
调用
printName("mm")
printName()
和dart差不多,可以指定参数传递给哪个,但是要注意位置参数,位置参数全部需要在第一个命名参数之前
fun printPerson(name: String = "Dean", age: Int = 2, sex: String = "male"){
println("name is $name , age is $age , sex is $sex")
}
调用时可以
printPerson()
printPerson("mm", age = 20)
printPerson(age = 20)
但是不可以
printPerson(age = 20, "mm")
原生字符串
和dart差不多,用“”“ 字符串 ”“”,包裹起来的字符串能够原封输出,可以加trimMargin来去除前面的空格
fun main(array: Array<String>){
var text = """
换行啊dadwadwj
换行啊dawidawjoid
换行啊dawidjaowd
""".trimMargin("换行啊")
println(text)
}
高阶函数和lambda
高阶函数:参数或者返回值为函数类型的函数
由于java不能把函数(方法)当成参数传递,所以一般都是使用接口来设置回调,比如
先定义一个接口,这种单抽象方法的接口叫做SAM接口(Single Abstract Method)
public interface PlayListener{
void onPlayerLoaded();
}
然后外面设置这个接口的实现类
public void setPlayListener(PlayListener listener){
this.playListener = listener;
}
最后内部在需要的地方调用
if (playListener != null){
playListener.onPlayerLoaded();
}
设置的时候使用lambda,但是java还是会创建匿名类对象
setPlayListener(() -> {
});
在kotlin中可以直接传递函数,所以这种使用SAM调用函数的方式就没必要了,但是写的时候可以通过lambda创建函数对象来传递
setPlayListener { println("onPlayerLoaded") }
在kotlin中可以通过三种方式来创建函数对象,创建好的函数对象可以被用作函数的参数或者函数的返回值
- 双冒号加函数名
fun add(a: Int, b: Int) = a + b
已经有的函数加上::,就会被自动创建一个函数对象,是对象就可以被当成函数的参数和函数的返回值
val a = ::add
println(a(1, 2))
两种方式都可以产生相同的结果,但是实现方式不同,第一种直接调用函数,第二种是生成对象后通过invoke来调用正在的方法
println(add(1, 2))
println((::add)(1, 2))
- 匿名函数
当函数直接赋值给变量,或者作为参数时,函数名称已经没有用了,kotlin里面强制不写,写了会报错
val a = fun(a: Int) = a + 1
println(a(1))
- Lambda
java中的lambda是纯为了简写,kotlin中增强了功能
上面设置监听时用的是 setPlayListener { println("onPlayerLoaded") }
看一步步衍变,首先可以写成上面说的匿名内部类,其中无返回值Unit可以省略
setPlayListener(fun(): Unit{
println("aaaa")
})
用lambda简化, 由于没有参数->可以省略
setPlayListener({ ->
println("aaaa")
})
当lambda是最后一个参数时可以放到括号外面, 没有参数胜率->
setPlayListener(){
println("aaaa")
}
因为lambda是唯一参数,所以()又可以省略
setPlayListener{
println("aaaa")
}
这时编译时,kotlin会通过上下文,即声明时setPlayListener内所写的参数,来自动判断使用lambda缩写之前的样子,比如参数和返回值
lambda中的return 是返回外层函数的,如果想要返回的是lambda,不需要写return,因为最后一行就是lambda的返回值,详情看这里