【Kotlin】 自学(五)-函数进阶

260 阅读6分钟

高阶函数

高阶函数定义

参数类型包含函数类型或者返回值类型是函数类型的函数为高阶函数,简单来说就是函数里面套函数

高阶函数举例

	/**
 * Performs the given [action] on each element.
 */
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
    for (element in this) action(element)
}

高阶函数调用

	val intArray = IntArray(5) { it + 1 }
    //forEach 需要函数类型是 (Int) -> Unit ,println有个重载类型就是 (Int) -> Unit
    intArray.forEach(::println)
    //forEach 只有一个参数,所以可以把括号移到外边,同时因为括号里面什么都没有,所以可以直接省略,就是说只有一个Lambda表达式作为参数可以省略小括号
    intArray.forEach {
        println("Hello $it")
    }

高阶函数使用实例

	//计算0到10之间斐波那契时间
    //参数是一个Lambda表达式,()直接省略
	cost {
    	//fibonacciNext 类型是() -> Long
        val fibonacciNext = fibonacci()
        for (i in 0..10) {
            println(fibonacciNext())
        }
    }
	
}

	//这个是一个高级函数,入参() -> Unit
	inline fun cost(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("${System.currentTimeMillis() - start}ms")
	//这个是一个高阶函数,出参 () -> Long
	fun fibonacci(): () -> Long {
    var first = 0L
    var second = 1L
    return {
        val next = first + second
        val current = first
        first = second
        second = next
        current
    }
}

内联函数

内联函数定义

inline标记的函数就是内联函数,其原理就是:在编译时期,把调用这个函数的地方用这个函数的方法体进行替换

我们看下刚才forEach这个函数 我们调用的使用是这样的

	 val ints = intArrayOf(1, 2, 3, 4)
    ints.forEach {
        println("Hello $it")
    }

它其实就相当于下面

	 for (element in ints) {
        println("Hello $element")
    }

使用内联函数会减少方法压栈,出栈,进而减少资源消耗,提升性能 我们来验证下

	fun main() {


    //region cost
    cost {
        println("Hello")
    }
    //endregion

}

 inline fun cost(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println(System.currentTimeMillis() - start)
}

我们反编译下这段代码,我们找下main函数 我们再把inline关键字去掉,我们在反编译下

内联高阶函数return

	 val ints = intArrayOf(1, 2, 3, 4)
    ints.forEach {
        if(it == 3) return@forEach
        println("Hello $it")
    }

    for (element in ints) {
        if(element == 3) continue
        println("Hello $element")
    }

使用标签,相当于continue,跳出这一次内联函数调用

有用的高阶函数

函数名介绍推荐度
letval r = X.let{ x -> R}***
runval r = X.run{ this:X -> R}*
alsoval x = X.also{ x -> Unit}***
applyval x = X.apply{ this:X -> R}*
useval r = Closeable.use{ x -> R}***

这里letrun一组,alsoapply一组,use一组,我们用代码看下

  • letrun:返回表达式结果
  • alsoapply:返回Receiver
  • use:自动关闭资源

然后我们单独看下use的使用,我们假设想读取build.gradle这个文件里面的内容,我们就可以这么写

	 File("build.gradle").inputStream().reader().buffered()
        .use {
            println(it.readLines())
        }
       

这个写起来比Java简洁多了,我们也不需要try...catch{},手动关闭流,为什么呢?我们看下use这个函数

	/**
 * Executes the given [block] function on this resource and then closes it down correctly whether an exception
 * is thrown or not.
 *
 * @param block a function to process this [Closeable] resource.
 * @return the result of [block] function invoked on this resource.
 */
@InlineOnly
@RequireKotlin("1.2", versionKind = RequireKotlinVersionKind.COMPILER_VERSION, message = "Requires newer compiler version to be inlined correctly.")
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

我们发现原来在Java中需要写的那些代码Kotlin中使用这个函数都可以帮我们省略

集合变换与序列

集合我们最常见的使用就是对它进行遍历,Java或者kotlin最常见的就是for循环

	 for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
	 for (i in 0..10) {
        println(i)
    }

    for (e in list) {
        println(e)
    }

上面是传统写法,现在流行的是下面这种写法

Java写法

	 list.forEach((e) -> {
            System.out.println(e);
        });

Kotlin写法

	    list.forEach {
      
        println(it)
    }

这里要注意下,这种写法不能使用continue或者break,那如果我们遇到这方面需求怎么办呢?就需要使用我们下面的变换了

函数名说明
filter保留满足条件元素
map集合中所有元素一一映射到其他元素构成新集合
flatMap集合中所有元素一一映射到新集合并合并这些集合得到新集合

我们分别使用下

先使用Filter,过滤掉奇数保留偶数

Java8开始可以使用

	 	List<Integer> list = new ArrayList<Integer>();
        list.addAll(Arrays.asList(1, 2, 3, 4));

        list.stream().filter(e -> e % 2 == 0)

Kotlin使用

	 val list = listOf(1, 2, 3, 4)

    list.filter {
        it % 2 == 0
    }

使用Map,把[1,2,3,4]转换成[3,5,7,9] Java8开始可以使用

	 	List<Integer> list = new ArrayList<Integer>();
        list.addAll(Arrays.asList(1, 2, 3, 4));

        list.stream()
                .map(e -> e * 2 + 1)

Kotlin使用

	 val list = listOf(1, 2, 3, 4)

      list.map {
        it * 2 + 1
    }

这里需要说一下,Kotlin中还可以使用序列,怎么使用呢?类似java,使用.asSequence(),比如上面的map我们就可以这样写

	list.asSequence()
            .map {
                it * 2 + 1
            }

这俩的区别就是一个是饿汉式,一个是懒汉式,我们打印下就知道了 我们先打印下Java中的例子

	 List<Integer> list = new ArrayList<Integer>();
        list.addAll(Arrays.asList(1, 2, 3, 4));

        list.stream()
                .filter(e -> {
                    System.out.println("Java filter: " + e);
                    return e % 2 == 0;
                })
                .map(e -> {
                    System.out.println("Java map: " + e);
                    return e * 2 + 1;
                })
                .forEach(e -> {
                    System.out.println("Java forEach: " + e);
                });

说明只要符合条件就会往下走,走完之后再去查看下一个,而不是一下子先把filter打印完,接着我们先看下不使用序列的Kotlin打印出来是什么样的

	 list
            .filter {
                println("kotlin filter: $it")
                it % 2 == 0
            }.map {
                println("Kotlin map: $it")
                it * 2 + 1
            }.forEach {
                println("Kotlin forEach: $it")
            }

接着我们就看下使用序列的结果

	 list.asSequence()
            .filter {
                println("kotlin filter: $it")
                it % 2 == 0
            }.map {
                println("Kotlin map: $it")
                it * 2 + 1
            }.forEach {
                println("Kotlin forEach: $it")
            }

使用flatMap,这个稍微麻烦点,我们把数组[1,2,3,4]转换成[0,0,1,0,1,2,0,1,2,3],什么意思呢?就是说把原来数组的每个元素转换成一个新数组,拿到这个新数组的元素,在把他们拼接成一个新数组,比如3,它的数组长度是3,元素就是[0,1,2],4,长度是4,元素就是[0,1,2,3]

Java8开始可以使用

        list.stream().flatMap(e -> {
            ArrayList<Integer> integers = new ArrayList<>(e);
            for (int i = 0; i < e; i++) {
                integers.add(i);
            }
            return integers.stream();
        })
                .forEach(e -> {
                    System.out.println("Java forEach: " + e);
                });

Kotlin使用

     list.asSequence()
            .flatMap {
                (0 until it).asSequence()
            }
            .joinToString().let(::println)

上面说的都是集合的变换,下面讲的就是集合的聚合

函数名说明
sum所有元素求和
reduce将元素按照规则聚合,结果与元素类型一致
fold给定初始化值,将元素按照规则聚合,结果与初始化值类型一致

先看下sum

	 val list = listOf(1, 2, 3, 4)

    println(list.sum())

这里说一下fold,这个弄会了,reduce就会了

	 val initial = "Kotlin,"
    val message = list.fold(initial) { acc, i ->
        acc.plus(i)
    }
    println(message)

zip操作

	 val zip = list.zip(listOf("x", "y", "z"))
    println(zip.joinToString())

SAM转换

SAM定义:Single Abstract Method

我们知道在Java8中,匿名内部类可以写成Lambda表达式

	 ExecutorService executor = Executors.newSingleThreadExecutor();
     	//匿名内部类写法
        executor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("run in executor.");
            }
        });
		//Lambda表达式写法
        executor.submit(() -> System.out.println("run in executor."));

所以说javalambda表达式是没有类型的,必须有一个接口来接收它,并且这个接口只有一个抽象方法,这就是JavaSAM,Kotlin中也可以这样做

	 val executor: ExecutorService = Executors.newSingleThreadExecutor()
	//匿名内部类
    executor.submit(object : Runnable {
        override fun run() {
            println("run in executor.")
        }
    })
	//简写
    executor.submit(Runnable {
        println("run in executor.")
    })

	//Lambda表达式 ()->Unit 类型的Lambda转成Runnable
    executor.submit { println("run in executor.") }

SAM转换支持对比

JavaKoltin
Java接口支持支持
Kotlin接口支持不支持
Java方法支持支持
Kotlin函数支持不支持(1.3支持)
抽象类不支持不支持

这里有个坑,就是添加和移除listener的时候,我们需要一个相同的对象

	 val onEvent = object : EventManager.OnEventListener {
        override fun onEvent(event: Int) {
            println("onEvent $event")
        }
    }
    
    eventManager.addOnEventListener(onEvent)

    eventManager.removeOnEventListener(onEvent)