本文适合有一定 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 注解来修改这个访问的类名。
object 和 companion object 都是⽣成单例对象,然后通过单例对象访问函数和属性的。
// 单例对象
object Tool {
}
class Tool {
// 伴生对象
companion object {
}
}
@JvmStatic
通过这个注解将 object 和 companion 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 create(): T {
return RETROFIT.create(T::class.java)
}
val api = create()
属性委托
有些常⻅的属性 *** 作,我们可以通过委托的⽅式,让它只实现⼀次,例如:
- 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) {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
标准函数
- 返回⾃身:从 apply 和 also 中选
- 作⽤域中使⽤ this 作为参数,选择 apply
- 作⽤域中使⽤ it 作为参数,选择 also
- 不需要返回⾃身:从 run 和 let 中选择
- 作⽤域中使⽤ 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 // 调用
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)