Kotlin总结

Kotlin 总结

本文适合有一定 Java 语言基础的开发者阅读。

函数声明
  • 使用 fun 关键字
  • 函数参数参数类型 是在 参数名 的右边
  • 函数的 返回值函数参数 右边使用 : 分隔,没有返回值可以省略
 // 没有返回值的函数,返回值是 Unit,可以省略
 fun method(x: Int) {
 
 }
    
// 有返回值的函数
fun method(x: Int): Int {
	
}

变量声明
  • var 表示声明可读可写的变量,val 声明只读变量
  • 类型 在 变量名 的右边,中间用 : 分隔,如果满足 类型推断,类型可以省略
  • 创建对象直接调用构造器,不需要使用 new 关键字
var age: Int = 18 // 根据类型推断,Int 可以省略
var age = 18

val name = "chen"
val user = User()

继承类和实现接口

继承类和实现接口都是用 : ,如果类中没有构造器(constructor),需要在父类类名后面加上 ()

class MainActivity: Activity(), View.OnClickListener {

}

空安全设计

Kotlin 中的类型分为 可空类型不可空类型

val editText: EditText // 不可空类型
val editText: EditText? // 可空类型

调用符
  • !! : 强行调用符,可能会发生空指针异常
  • ?. :空安全调用符,不为空时才会调用

lateinit

稍后初始化

  • 修饰 var 可读可写变量
  • 声明的变量是不可空类型
  • 不能有初始值
  • 不能是基本数据类型

类型判断
  • is: 判断属于某种类型
  • !is: 判断不属于某种类型
  • as: 类型强转,失败时抛出异常
  • as?: 类型强转,失败时不会抛出异常,而是会返回 null

获取 Class 对象

使用 类名::class 获取的是 Kotlin 的类型 KClass

使用 类名::class.java 获取的是 Java 的类型 Class


setter/getter

在 Kotlin 声明属性的时候(没有使用 private 修饰),会⾃动⽣成⼀个私有属性和⼀对公开的 setter/getter 函数。


构造器

使⽤ constructor 关键字声明构造器

class User {
	constructor()
}

// 继承了父类的构造
constructor(context: Context): this(context, null)
consstuctor(context: Context, attr: AttributeSet?): super(context, attr)

@JvmFiled 生成属性

通过 @JvmField 注解可以让编译器只⽣成⼀个 public 的成员属性,但是不⽣成对应的 setter/getter 函数。

@JvmField
var age = 15

Any 和 Unit
  • Kotlin 的顶层⽗类是 Any,对应 Java 当中的 Object,但是⽐ Object 少了 wait()/notify() 等函数

  • Kotlin 中的 Unit 对应 Java 中的 void


数组

使⽤ arrayof() 来创建数组,基本数据类型可以使⽤对应的 intArrayOf() 等,可以避免装箱和拆箱带来的性能损耗。


静态函数和属性
  • 顶层函数
  • object
  • companion object(伴生对象)

顶层函数 直接在⽂件中定义函数和属性,会直接⽣成静态的,在 Java 中可以通过 ⽂件名Kt 来访问,同时也可以通过 @file:JvmName 注解来修改这个访问的类名。

objectcompanion object 都是⽣成单例对象,然后通过单例对象访问函数和属性的。

 // 单例对象
 object Tool {
 }

 class Tool {
 	// 伴生对象
	companion object {
	
	}
 }

@JvmStatic

通过这个注解将 objectcompanion object 的内部函数和属性,真正⽣成为静态的,可以在 Java 代码中调用。

object Tool {
    @JvmStatic
    fun method() {
    }
}

匿名内部类

单例对象和匿名内部类都是通过 object 关键字实现。

object Singleton {// 单例

}

// 匿名内部类
object: OnClickListner {

}

字符串模板

通过 ${} 的形式

val age = 1
val str = "age:$age"// 单行字符串

// 多行字符串
val str = """
            第一行
            第二行
            """.trimIndent()

区间

200…299 表示 200 到 299 的区间 (包括299)

for(i in 200..299) {
	println(i)
}

when

Java 中的 switch 的⾼级版,分⽀条件上可以⽀持表达式。

when(value) {
	1,2 -> {
	
	}
	3 -> {
	
	}
	else -> {

	}
}

异常

Kotlin 不需要使⽤ try-catch 强制捕获异常。


声明接⼝/抽象类/枚举/注解
// 声明抽象类
abstract class
// 声明接口
interface
// 声明注解
annotation class
// 声明枚举
enmu class

编译器常量

在静态变量上加上 const 关键字


标签

在 Java 中通过 类名.this 获取⽬标类引⽤

在 Kotlin 中通过 this@类名 获取⽬标类引⽤


内部类

在 Kotlin 当中,内部类默认是静态内部类

通过 inner 关键字声明为嵌套内部类


可⻅性修饰符

默认的可⻅性修饰符是 public

新增的可⻅性修饰符 internal 表示当前模块可⻅


注释

注释中可以在任意地⽅使⽤ [] 来引⽤⽬标,代替 Java 中的 @param@link 等。


open/final

Kotlin 中的类和函数,默认是被 final 修饰的 (abstract 和 override 例外),如果需要被继承或重写,需要在前面加上 open 关键字。


次级构造
class MyTextView: TextView {
	constructor(context: Context): super(context)
}

主构造器
class MyTextView constructor(context: Context): TextView(context) {
}

// 如果没有被可⻅性修饰符 注解标注,那么 constructor 可以省略
class MyTextView(context: Context): TextView(context) {
}

// 成员变量初始化可以直接访问到主构造参数
class MyTextView(context: Context): TextView(context) { 
  val color = context.getColor(R.color.red)// 
}

init 代码块

主构造不能包含任何的代码,初始化代码可以放到 init 代码块中

class MyTextView(context: Context): TextView(context) {
  init {
    ...
    // 在初始化的时候,初始化块会按照它们在「⽂件中出现的顺序」执⾏
    // 这时使用 paint 会报错
  }
  val paint = Paint() // 会在 init 之后执行
}

构造属性

在主构造参数前⾯加上 var/val 使构造参数同时成为成员变量

class MyTextView(var context: Context): TextView(context) {
 
}

data class

在 class 前面加上 data, 数据类同时会⽣⽣成 toString()/ hashCode() / equals() / copy() / componentN() 等方法。

相等性
  • ==: 结构相等(相当于 Java 的 equals 方法 )
  • === :地址值相等 (相当于 Java 的 == )

解构

可以把⼀个对象 解构 成很多变量。

val (code, message, body) = response

对应的代码:

val code = response.component1()
val message = response.component2()
val body = response.component3()

Elvis 操作符

可以通过 ?: 的操作来简化 if null 的操作

val name = user.name?: "未知"

operator

通过 operator 修饰 特定函数名 的函数,例如 plus、get,可以达到重载运算符的效果。


lambda

如果函数的最后⼀个参数是 lambda,那么 lambda 表达式可以放在圆括号之外。


循环
	// 通过标准函数 repeat()
    repeat(1000) {
    	
    }
    
    // 区间
    for(i in 0..99) {
      
    }
    
    // until 不包括右边界
    for(i in 0 until 1000) {
      
    }

infix

必须是成员函数或扩展函数,必须只能接受⼀个参数,并且不能有默认值。

// until 源码
public infix fun Int.until(to: Int): IntRange {
    if (to <= Int.MIN_VALUE) return IntRange.EMPTY
    return this .. (to - 1).toInt()
}

嵌套函数

Kotlin 中可以在函数中继续声明函数。

// 内部函数可以访问外部函数的参数
// 每次调⽤时,会产⽣⼀个函数对象
fun method1() {
    fun method2() {
        
    }
}

函数简化

可以通过符号 = 简化原本直接 return 的函数

fun add(a: Int, b: Int) = a + b

函数参数默认值

可以通过函数参数默认值来代替 Java 的函数重载

@JvmOverloads // 可以在 Java 中调用重载方法
fun add(a: Int, b: Int = 1) = a + b

扩展函数
  • 扩展函数可以为任何类添加上⼀个函数,从⽽代替⼯具类
  • 扩展函数和成员函数相同时,成员函数优先被调⽤
  • 扩展函数是静态解析的,在编译时就确定了调⽤函数(没有多态)

函数类型

函数类型由 传⼊参数类型返回值类型 组成,⽤ -> 连接,传⼊参数需要⽤ (),如果返回值为 Unit 不能省略,函数类型实际是⼀个接⼝,我们传递函数的时候可以通过 ::函数名,或者 匿名函数 或者使⽤ lambda


inline(内联函数)
  • 内联函数配合函数类型,可以减少 函数类型 ⽣成的对象。

  • 使⽤ inline 关键字声明的函数是 内联函数 ,在 编译时 会将 内联函数 中的函数体直接插⼊到调⽤处。


noinline(内联函数部分参数禁⽤内联)

noinline 可以禁⽌部分参数参与内联编译。

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {

}

reified(具体化的类型参数)

因为内联函数的存在,我们可以通过配合 inline + reified 达到 真泛型 的效果。

val RETROFIT = Retrofit.Builder()
	.baseUrl("xxx")
	.build()
	
inline fun <reified T> create(): T {
	return RETROFIT.create(T::class.java)
}

val api = create<API>()

属性委托

有些常⻅的属性操作,我们可以通过委托的⽅式,让它只实现⼀次,例如:

  • lazy 延迟属性:值只在第⼀次访问的时候计算
  • observable 可观察属性:属性发⽣改变时的通知
  • map 集合:将属性存在⼀个map中

对于⼀个只读属性(即 val 声明的),委托对象必须提供⼀个名为 getValue() 的函数

对于⼀个可变属性(即 var 声明的),委托对象同时提供 setValue()、getValue() 函数


类委托

可以通过类委托的模式来减少继承。

类委托的,编译器会优先使⽤⾃身重写的函数,⽽不是委托对象的函数。

// 创建接口
interface Base {   
    fun print()
}

// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10
}

标准函数
  • 返回⾃身:从 applyalso 中选
    • 作⽤域中使⽤ this 作为参数,选择 apply
    • 作⽤域中使⽤ it 作为参数,选择 also

  • 不需要返回⾃身:从 runlet 中选择
    • 作⽤域中使⽤ this 作为参数,选择 run
    • 作⽤域中使⽤ it 作为参数,选择 let

高阶函数

参数或返回值为 函数类型 的函数。

// 函数类型的参数
fun a(funParam: (Int) -> String): String {
  return funParam(1)
}

// 普通函数
fun b(param: Int): String {
  return param.toString()
}

// 函数类型返回值
fun c(param: Int): (Int) -> Unit {
  ...
}

对于一个声明好的函数,不管是要把它作为参数传递给函数,还是要把它赋值给变量,都需要在函数名的左边加上双冒号才行。

a(::b)
val d = ::b

::method (双冒号 + 函数名)

双冒号的写法叫做函数引用(Function Reference),Kotlin 中函数可以作为参数的本质,是函数在 Kotlin 里可以作为对象存在,因为对象才能被作为参数传递。一个函数名的左边加上双冒号,它就表示一个指向对象的引用,但这个对象不是函数本身,而是和这个函数具有相同功能的对象。

b(1) // 调用函数,实际上调用 b.invoke(1)
d(1) // 实现 b(1) 的等价操作,实际上会调用 (::b).invoke(1)
(::b)(1) // 实现 b(1) 的等价操作,实际上会调用 (::b).invoke(1)

匿名函数

要传一个函数类型的参数,或者把一个函数类型的对象赋值给变量,除了用双冒号来拿现成的函数使用,还可以直接把这个函数挪过来写:

// 这种写法的话,内部函数b 的名字其实就没有用了,可以省略掉
a(fun b(param: Int)): String { 
	return param.toString()
})
// 这种写法叫做匿名函数
a(fun(param: Int)): String {
	return param.toString()
})

// 函数b 的名字就没有意义的,可以省略掉
val d = fun b(param: Int): String {
  return param.toString()
}
// 匿名函数
val d = fun(param: Int): String {
  return param.toString()
}

在 Java 中设计一个回调是这样的

public interface OnClickListener {
	void onClick(View v);
}
public void setOnClickListener(OnClickListener listener) {
	this.listener = listener;
}

// 使用的时候
view.setOnClickListener(new OnClickListener(){
	@Override
	void onClick(View v) {
	}
})

到了 Kotlin 就可以使用 匿名函数 改写了

fun setOnClickListener(onClick: (View) -> Unit) {
    this.onClick = onClick
}

// 使用的时候
view.setOnClickListener(fun(v: View): Unit) {
}

// 匿名函数还能更简化一点
view.setOnClickListener({ v: View ->

})

Lambda 表达式

如果 Lambda 是函数的最后一个参数,还可以把 Lambda 写在括号的外面:

view.setOnClickListener(){ v: View ->
  ...
}

// 如果 Lambda 是函数的唯一参数,可以直接把括号去掉
view.setOnClickListener{ v: View ->
  ...
}

// 如果 Lambda 是单参数,可以省略不写,用 it 代替
view.setOnClickListener{
  ...
}

匿名函数赋值给变量

val b = fun(param: Int): String {
  return param.toString()
}

// 也可以简写成 Lambda 的形式
val b = {	param: Int ->
	return param.toString()
}

// 不能省略 Lambda 的参数类型,无法从上下文推断出参数类型
val b = {
  it.toString()// it 报错
}

val b:(Int) -> String = {
  // Lambda 的返回值不是用 return 来返回,而是直接取最后一行
  //return it.toString()// it 可以被推断出是 Int 类型
  it.toString()
}

Kotlin 的匿名函数和 Lambda 表达式的本质

匿名函数本质上不是函数,而是一个对象,一个函数类型的对象,它和 双冒号加函数名 是一类东西。所以才可以直接把它当做函数的参数来传递以及赋值给变量。

同理,Lambda 也是一个函数类型的对象。


对比 Java 的 Lambda

Java8 开始引用了对 Lambda 的支持,对于单抽象方法的接口(简称: SAM 接口,Single Abstract Method 接口),Java8 允许用 Lambda 表达式来创建匿名类对象,但它本质上还是创建一个匿名类对象,只是一种简化写法。

而 Kotlin 里的 Lambda 和 Java 本质上是不同的,因为 Kotlin 的 Lambda 是实实在在的函数类型对象。

另外,Kotlin 是不支持使用 Lambda 的方式来简写匿名类对象的,因为 Kotlin 有函数类型的参数,所以这种单函数接口的写法就直接没有必要了。

不过当和 Java 交互的时候,Kotlin 是支持这种写法的,当你的函数参数是 Java 的单抽象方法的接口的时候,依然可以使用 Lambda 来写参数。但这其实也不是 Kotlin 增加了功能,而是对于来自 Java 的单抽象方法的接口,Kotlin 会为它们额外创建一个把参数替换为函数类型的桥接方法,让你可以间接地创建 Java 的匿名类对象。


扩展函数

扩展函数写在哪都可以,但写的位置不同,作用域就也不同。所谓作用域就是说你能在哪些地方调用到它。最简单的写法就是把它写成 Top Level 也就是顶层的,让它不属于任何类,这样你就能在任何类里使用它。

package com.chen.customview.ext

fun String.method(i: Int) {
    ...
}

"test".method(2)// 使用

除了写成 Top Level 的,扩展函数也可以写在某个类里,然后你就可以在这个类里调用这个函数,但必须使用那个前缀类的对象来调用它。

class Utils {

    fun String.method(i: Int) {
       ...
    }
    
    "test".method(2)// 在当前类中可以调用
}

扩展函数的引用

普通函数可以被指向,扩展函数也可以被指向。

fun String.method(i: Int) {
	...
}

String::method()
// 扩展函数的引用也可以被调用,直接调用或用 invoke()
(String::method)("chen", 1)
String::method.invoke("chen", 1)
// 以上两句都等价于
"chen".method(1)


// 扩展函数的引用赋值给变量
val a: String.(Int) -> Unit = String::method
// 调用
"chen".a(1)
a("chen", 1)
a.invoke("chen", 1)

有无 Receiver 的变量的互转

当拿着一个函数的引用去调用的时候,都需要把 Receiver (接受者或调用者) 作为第一个参数穿进去。在这种调用方式下,增加一个函数参数,让我们把第一个参数的位置填上调用者。这样,我们就可以用函数的引用来调用成员函数和扩展函数了。

(String::method)("chen", 1) // 等价于 "chen".method(1)
(Int::toFloat)(1) // 等价于 1.toFloat()

既然有 Receiver 的函数可以以无 Receiver 的方式来调用,那它也可以赋值给无 Receiver 的函数类型的变量。

val b: (String, Int) -> Unit = String.method

在 Kotlin 里,每一个有 Receiver 的函数(其实就是成员函数和扩展函数),它的引用都可以赋值给两种不同的函数类型变量:一种是有 Receiver 的,一种是没有 Receiver 的。

val a: String.(Int) -> Unit = String::method
val b: (String, Int) -> Unit = String::method


// 同样,这两种类型的变量也可以相互赋值来进行转换
val c: String.(Int) -> Unit = b
val d: (String, Int) -> Unit = a


// 这两种类型的变量可以互相赋值来转换,
// 那么一个普通的无 Receiver 的函数也可以直接赋值给有 Receiver 的变量
fun method2(s: String, i: Int) {
  ...
}

val e: (String, Int) -> Unit = ::method2
val f: String.(Int) -> Unit = ::method2
"chen".method2(1) // 报错,不允许调用
"chen".f(1) // 可以调用

扩展属性(Extension Property)

声明的属性左边写上类名加点,这样就是一个扩展属性了。

val Float.dp
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this, Resources.getSystem().displayMetrics
    )

val Int.dp
    get() = this.toFloat().dp
    
    
val RADIUS = 200.dp // 调用

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值