kotlin 虽然好上手,但是 kotlin 自身也是特性的,纯按照 kotlin 写的代码会让初上手的同学看不懂的。对于我来说一开始 kotlin 比较难习惯的就是对象函数了
kotlin 可是真正的一切皆对象,kotlin 允许你把 method 作为参数使用,既可以作为全局参数使用,也可以在方法中作为参数传入,这点我觉得是 kotlin 这类语言的最大特点,通过对象函数,我们可以不用再像 java 时代写一大堆 interface 啦
但是使用 kotlin 后,怎么让代码如同 java 一样实现代码补全让我着实费了一番劲,本文重点就在于如何实现 kotlin 对象函数的代码补全
java 时代
java 时代我们这样使用接口参数
// 声明一个接口类型
interface CustomeClick {
void click();
}
// 创建一个传入 CustomeClick 类型参数的方法
public void click(CustomeClick click) {
}
// 具体使用我们声明的方法
public void main() {
click(new CustomeClick() {
@Override
public void click() {
}
});
}
在 java 时代我们要频繁的去创建 interface 接口类型,尤其是对于只有一个方法的接口我们使用的最多,声明接口时我们还要取思考接口名叫什么合适,接口放在哪里,费时费力,最后也只是干了一个传参的事
我们按 Command + P 可以看到参数提示:
然后我们 new + 接口名可以自动补全代码:
随后 java 提供了 lambda 表达式来简化代码,但是当时我看就有人说 java 搞了个半拉子,不看好 lambda ,当时我是不懂,用过 kotlin 以后才知道为啥
max(strings, { a, b -> a.length < b.length })
我的习惯就是上面这样的,先看看参数类型,然后 new 一下代码就出来了,然后我们写方法实现就好了,用什么久了就成习惯了,这点让我在最初切换到 kotlin 时极不适应,kotlin 在这点上和 java 有巨大的操作差异,需要适应
kotlin 时代
kotlin 的上位,给我们提供了更便利的写法, kotlin 支持对象函数,可以把方法作为一个参数去使用了,再具体代码上我们可以不用再写 iterface 接口了
首先在方法中声明对象函数限定描述,主要是传什么类型参数,返回什么类型参数
// left: 是函数对象的名字,() 里面限定传入参数 ,-> 限定返回值类型,没有返回值要显示的写 Unit
fun cks(price: Int, left: (name1: String) -> String, right: (name2: String) -> String) { ... }
// 函数对象若是只有一个,并且在最后的位置,可以简写
fun <T> lock(lock: Lock, body: () -> T): T { ... }
kotlin Lambda表达式,函数作为成员变量声明
// 不带返回值写法
val sum = { x: Int, y: Int -> x + y }
// 带返回值写法
val sum: (Int, Int) -> Int = { x, y -> x + y }
val i: Int = sum(1, 2)
我们来看看官方的例子:
// 这是集合对象的过滤函数,可以看到官方的例子是比较复杂的,还涉及到其他一些知识点
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
然后我们来看看 kotlin 的自动提示,自动补全好不好使
同样是 Command + P 我们依然可以参数提示:
但是 kotlin 没有 new 了,我们不能快速的实现代码补全了,只能自己写了,说实话这点我不喜欢
标准写法: { 再次声明参数(在这可以改名,有的系统定义的参数名我甚是不喜欢) + -> + 方法体实现 }
cks(3,
{ nameLeft: String ->
nameLeft + "11"
nameLeft + "22"
},
{ nameRight: String ->
nameRight + "11"
nameRight + "22"
}
)
简写:可以不用再声明参数,直接写方法体实现,参数名由 it 代替,it 这块系统有默认提示的
cks(3,
{
it + "11"
it + "22"
},
{
it + "11"
it + "22"
}
)
函数对象要是有2个参数,那么我们必须重写参数声明,it 只能节省单个参数时的代码,这时我们可以 Command + P 看参数提示,然后照着第一个参数把名字敲出来,系统的自动提示框里有快速补全参数声明的选项
上面是使用函数对象时代码自动补全怎么用的说明,kotlin 当然还支持传统的 iterface 接口参数,这时代码自动补全怎么用
我们使用 object:(接口名){ } 来声明一个匿名实现类,然后在 {} 内使用 Command + Ins 也可以实现代码补全
name( object :Foo{
override fun acg() {
...
}
})
vararg 可变参数
//用 vararg 修饰符标记参数
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
val a = arrayOf(1, 2, 3)
//*a代表把a里所有元素
val list = asList(-1, 0, *a, 4)
//运行代码,得到结果为: [-1, 0, 1, 2, 3, 4]
内嵌函数
kotlin 允许你在函数内部声明作用域仅仅在函数内部的函数
fun foo() {
println("outside")
fun inside() {
println("inside")
}
inside()
}
//调用foo()函数
foo()
outside
inside
泛型
函数中的泛型有必要展示一下,谁知道哪天突然就记不清了呢~
Java代码
<T> void print(T t) { ... }
<T> List<T> printList(T t) { ... }
Kotlin代码
fun <T> printList(item: T) { ... }
fun <T> printList(item: T): List<T> { ... }
tailrec 递归
不写 tailrec 就是死循环,即使他是递归,有退出机会
tailrec fun count(x: Int = 1): Int = if (x == 10) x else count(x - 1)
inline / noinline 内联函数
上面我们说了 kotlin 允许我们把方法作为对象来使用,这样虽说方便了,但是 kotlin 也会生成相应的函数对象,在内存开销上一定性能损失,基本虽有讲这块的文章都有一句话
使用高阶函数会带来一些运行时的效率损失。每一个函数都是一个对象,并且会捕获一个闭包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。这时可以通过内联函数消除这类的开销。
使用 inline 的话就会有特殊的操作,把函数内联到调用处使用,用法很简单,就是在目标 fun 前面加上 inline 限定
inline fun cks(left: (name1: String) -> String, right: (name2: String) -> String) { ... }
inline 的注意点是,避免内联比较大的函数,因为内联可能导致生成的代码增加。如果想禁用一些函数的内联,可以使用 noinline 修饰符要禁用的函数
inline fun cks(left: (name1: String) -> String, noinline right: (name2: String) -> String) { ... }
扩展函数
kotlin 允许我们在类中对已经定义好的类进行扩展,包括方法和属性 ,但是作用域只在当前类内
- 扩展函数 / 扩展属性
// 扩展函数
fun String.any(name: String) {
Log.d("AAA", "扩展函数 - String.any 运行!!!")
}
// 扩展属性,注意必须要写 get/set ,然后扩展属性只能声明在成员变量
var News.name: String
get() = name
set(value) {
name = value
}
- 扩展伴生对象
// 扩展伴生对象
class User {
companion object {
}
}
fun User.Companion.foo() {
println("伴生对象扩展")
}
User.foo()
- 扩展函数经典例题
open class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
fun caller2(d1: D1) {
d1.foo() // 调用扩展函数
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
//调用
C().caller(D())
C1().caller(D())
C().caller(D1())
C().caller2(D1())
C1().caller2(D1())
D.foo in C
D.foo in C1
D.foo in C
D1.foo in C
D.1foo in C1
这个例子非常好,能让我们高明白扩展函数的作用域,尤其是第三个 虽然传入的参数是 C1,但是我们接收的参数类型是 C,所以扩展函数的作用域就在 C 里面,这里子类的多态不起作用,要注意
接口委托
接口委托其实就让我们在类中可以不用实现 A 接口的方法, 而是转接到传入的实现类参数上
//定义一个接口 Base
interface Base {
fun print()
}
//定义一个 ImplBase 实现接口 Base
class ImplBase(val i: Int) : Base {
override fun print() {
println(i)
}
}
//定义一个 Drived 类实现接口 Base
class Drived(b: Base) : Base {
//这里需要 override 接口 Base 里的方法
override fun print() {
}
}
//如果使用委托模式的话,可以把 Base 里的方法委托给 Drived
class Drived(b: Base) : Base by b
//调用 print() 方法
var b = ImplBase(10)
Drived(b).print()
//运行代码,打印结果为 10
网友评论