kotlin - 函数(对象函数/代码补全)

2019-02-22  本文已影响15人  前行的乌龟

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

上一篇下一篇

猜你喜欢

热点阅读