目录
向KotlinAction中函数的进阶理解
最近工作繁忙,心得体会难以总结;偶得闲时,查阅Kotlin Action,笔录心得。
顶层函数与属性的意义与本质
消除特定一些仅包含静态函数容器类例如JDK中的Collections,其顶层函数与属性不在需要被容器类包裹,只位于代码文件中的顶层,并不属于任何类,并且其顶层函数仍然能成为包内成员,仅仅需要使用import进行导入。
其写法如下:
@file:JvmName("TLFAADemo")
package kotlin_demo.function
/**
* 所谓顶层函数:去除包裹静态函数类容器,使其存在包内,简化类的创建,实则在jvm运行编译的时候还是会创建一个类,
* 如果没有使用注解为该文件命名的时候,该文件名字则为该类名。
* 如下:
* @file:JvmName("TLFAADemo")
* 使用注解将该文件类名编译为TLFAADemo
* */
fun topLevelFunction(): String = "this is a top level function"
/**
* 所谓顶层属性,可以看成是一个类外部属性成员,作为一些单独的数据片使用。
* val 修饰的作用说明是不可变的只能有get方法调出
* var 修饰的作用说明是可变的拥有get和set方法
* const 等价于 public static final的作用
* */
val authorName = "易庞宙"
var canSetValue = "zero"
const val resulut = "易庞宙 is already set"
其运行本质,Kotlin被研发到被开发人员钟爱并运用到开发的过程,其组织本着一种为JVM提供更好的编译这一理念。所以顶层函数运行最终也会编译成为字节码。所以其本质上只是在代码写法上简化了类包裹以及引用的写法而已,这也得益于Kotlin在编译期间做一些东西,使得我们这些开发人员能够使用更为简易可阅读的语言进行编写。
上述其顶层函数的代码的Kotlin字节码
// ================kotlin_demo/function/TLFAADemo.class =================
// class version 50.0 (50)
// access flags 0x31
public final class kotlin_demo/function/TLFAADemo {
@Lkotlin/jvm/JvmName;(name="TLFAADemo") // invisible
// access flags 0x19
public final static topLevelFunction()Ljava/lang/String;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 12 L0
LDC "this is a top level function"
ARETURN
L1
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x1A
private final static Ljava/lang/String; authorName = "易庞宙"
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x19
public final static getAuthorName()Ljava/lang/String;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 21 L0
GETSTATIC kotlin_demo/function/TLFAADemo.authorName : Ljava/lang/String;
ARETURN
L1
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0xA
private static Ljava/lang/String; canSetValue
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x19
public final static getCanSetValue()Ljava/lang/String;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 23 L0
GETSTATIC kotlin_demo/function/TLFAADemo.canSetValue : Ljava/lang/String;
ARETURN
L1
MAXSTACK = 1
MAXLOCALS = 0
// access flags 0x19
public final static setCanSetValue(Ljava/lang/String;)V
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "<set-?>"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 23 L1
ALOAD 0
PUTSTATIC kotlin_demo/function/TLFAADemo.canSetValue : Ljava/lang/String;
RETURN
L2
LOCALVARIABLE <set-?> Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x19
public final static Ljava/lang/String; resulut = "易庞宙 is already set"
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 21 L0
LDC "\u6613\u5e9e\u5b99"
PUTSTATIC kotlin_demo/function/TLFAADemo.authorName : Ljava/lang/String;
L1
LINENUMBER 23 L1
LDC "zero"
PUTSTATIC kotlin_demo/function/TLFAADemo.canSetValue : Ljava/lang/String;
RETURN
MAXSTACK = 1
MAXLOCALS = 0
@Lkotlin/Metadata;(
mv={1, 1, 13},
bv={1, 0, 3},
k=2,
d1={"\u0000\n\n\u0000\n\u0002\u0010\u000e\n\u0002\u0008\u0009\u001a\u0006\u0010\u0009\u001a\u00020\u0001\"\u0014\u0010\u0000\u001a\u00020\u0001X\u0086D\u00a2\u0006\u0008\n\u0000\u001a\u0004\u0008\u0002\u0010\u0003\"\u001a\u0010\u0004\u001a\u00020\u0001X\u0086\u000e\u00a2\u0006\u000e\n\u0000\u001a\u0004\u0008\u0005\u0010\u0003\"\u0004\u0008\u0006\u0010\u0007\"\u000e\u0010\u0008\u001a\u00020\u0001X\u0086T\u00a2\u0006\u0002\n\u0000\u00a8\u0006\n"},
d2={"authorName", "", "getAuthorName", "()Ljava/lang/String;", "canSetValue", "getCanSetValue", "setCanSetValue", "(Ljava/lang/String;)V", "resulut", "topLevelFunction", "JavaDemo"})
// compiled from: TopFunction.kt
}
java调用代码:
public class FuctionRunDemo_java {
public static void main(String[] args) {
System.out.println(TLFAADemo.topLevelFunction());
System.out.println(TLFAADemo.getAuthorName());
}
}
从调用代码以及字节码中可以看出所谓的顶层函数以及顶层属性其实现原理与java写法上市一致。本质上会转换为包裹静态函数以及属性的一个类。
顶层函数及属性的进阶——拓展函数及属性
拓展函数以及属性是为了解决现有的API类或者工具类等一些类,不会转为Kotlin代码抑或着其内部实现不允许进行一些修改的时候,通过使用拓展这一种将函数以及属性直接成为该类的成员属性,只不过它是定义在类的外面。
基本使用:
示例代码:
fun String.lastChar(): Char = this.get(this.length - 1)
/**
* 拓展属性的使用跟拓展函数使用类似
* */
var StringBuilder.lastChar: Char
set(value) {
this.setCharAt(this.lastIndex - 1, value)
}
get() = this.get(this.lastIndex-1)
上述这一行代码则是一种拓展函数的写法:String代表指定拓展的类型,而lastChar()代表拓展的函数名。
调用示例:
Kotlin:
import kotlin_demo.function.*
import kotlin_demo.function.lastChar as last
import kotlin_demo.function.click as extendClick
object FunctionRunDemo {
@JvmStatic
fun main(args: Array<String>) {
/**
* 拓展函数的输出
* */
println("拓展函数的输出")
println(""""result".last() = ${"result".last()}""")
println("""listOf<Int>(1,3,3).joinString() = ${listOf<Int>(1, 3, 3).joinString(sperator = "; ", postfix = " }", prefix = "{ ")}""")
println("""listOf("1", "3", "5").joinToString() = ${listOf("1", "3", "5").joinToString(sperator = "; ", postfix = " }", prefix = "{ ")}""")
println("拓展函数是否可重写示例")
val view :ExampleView = MyButton()
view.click()
view.show()
view.extendClick()
}
}
java:
public class FuctionRunDemo_java {
public static void main(String[] args) {
System.out.println(ExtendedFunctionKt.lastChar("A"));
}
}
在Kotlin中可以通过import后直接使用,而在java中则需使用明式调用了而不能通过类似Kotlin这样的隐式调用。
再来看看一些示例代码:
/**
* 拓展函数的运用:
* 输出集合元素
* */
fun <T> Collection<T>.joinString(sperator: String = ",", prefix: String = "", postfix: String = ""): String {
val result = StringBuilder(prefix)
forEach {
result.append(it.toString())
result.append(sperator)
}
result.append(postfix)
return result.toString().replace("$sperator$postfix", postfix)
}
/**
* 拓展函数的运用:
* 输出指定类型的集合元素的
* */
fun Collection<String>.joinToString(sperator: String = ",", prefix: String = "", postfix: String = ""): String {
val result = StringBuilder(prefix)
forEach {
result.append(it.toString())
result.append(sperator)
}
result.append(postfix)
return result.toString().replace("$sperator$postfix", postfix)
}
/**
* 拓展函数不可重写示例
*/
fun ExampleView.show() {
println("this's ExampleView show")
}
fun ExampleView.click() {
println("extanded click")
}
fun MyButton.show() {
println("this's MyButton show")
}
在上述集合函数拓展示例中,当Collection<Int>类型的集合调用<String>类型的集合或出现一个type mismath的错误其含义是指类型不匹配,说明了在使用拓展函数必须清晰的建立好类型对应的关系否则则会出现这一个问题。
其优劣点
在拓展函数中,接收者类型是可以调用其内部属性以及其内部函数,但需要谨记一点不允许打破其封装性,也就意味着其私有以及保护的发放是无法在拓展中使用的。本质上他就是个静态函数,一个相对高级的语法糖;其无法像可重写的内部成员函数能够复写,意味着其拓展所用的方法与其静态类型一一对应;可能会存在函数引用不一致的问:内部成员函数与拓展函数重名引起、由于无法重写静态类型不匹配导致。
针对重名引起的探讨两种解决方案:修改两函数其中之一的函数名,彻底解决引用不一致的问题;对拓展函数使用明式调用或者利用as关键字为拓展函数设置别名来进行隐式调用。
针对别名的示例代码如下:
import kotlin_demo.function.click as extendClick
open class ExampleView {
open fun click(){
println("成员函数ExampleView click")
}
}
open class MyButton : ExampleView() {
override fun click() {
println("成员函数MyButton click")
}
}
object FunctionRunDemo {
@JvmStatic
fun main(args: Array<String>) {
val view :ExampleView = MyButton()
view.click()
view.show()
view.extendClick()
}
}
总结
利用顶层以及拓展这些概念可以简化一些不必要代码的编写,但是其依然会存在于java混编中明隐式写法上的区别,同时要活用高效语法糖的同时并要清晰的知道如何解决语法糖带来的问题以及其本质上还是编译成静态函数容器去包裹的这一些系列的本质上的东西不可本末倒置。
引用代码重要的注释如下
/**
* 拓展函数的作用在于用以解决,现有的java类是不做任何改动
* 下述类子中
* String 代表的拓展类型
* 在Kotlin代码中通过匹配到接收者对象指定类型即可调用但是必须使用import将函数导入,而在java代码中则有所区别
*
* 在拓展函数中,接收者类型是可以调用其内部属性以及其内部函数,但需要谨记一点是不允许打破其封装性,
* 也就是其私有以及其保护是无法在拓展这一方式中被调用
* 本质上拓展函数就是静态函数,只不过Kotlin使其变为更为简易的语法
*
* 有关拓展函数是否可重写的问题
* 拓展函数并不是类的一部分,它只是类拓展实现多态的一种思维,他是声明在类之外的,尽管它被分别定义为同名不同类型的函数,
* 但实则上有改变量指向的静态类型所决定调用时哪一个拓展方法,而不是实则运行的类型。所以不存在重写这一个说法,
* Kotlin只是把它当作一个静态方法
*
* 有关成员函数于拓展函数使用的冲突问题
* 当一个类的成员函数与拓展函数重名的时候,优先使用成员函数,所以在拓展API类的时候要时常注意,成员属性是否与拓展函数重名,
* 如果重名了可以使用有两种解决方案:
* 拓展的成员函数如果没有真正运用可以为拓展成员函数进行重新命名这一个办法解决;
* 使用as这一个关键字设置别名进行别名引用避免使用拓展函数的时候,函数指向不正确;别名这一办法运用于Kotlin的代码,
* 而由于java代码是显式调用拓展函数所以可以很好的避免这一个问题
*
* */
/**
* 所谓顶层函数:去除包裹静态函数类容器,使其存在包内,简化类的创建,实则在jvm运行编译的时候还是会创建一个类,
* 如果没有使用注解为该文件命名的时候,该文件名字则为该类名。
* 如下:
* @file:JvmName("TLFAADemo")
* 使用注解将该文件类名编译为TLFAADemo
* */
最后附上github源码地址(里面也有一些其他学习的代码示例)源码地址