本节我们讲kotlin的函数,在java中,我们也会称为方法,
1、函数的写法
我们先看最普遍的写法,这三种写法区别仅仅在参数:
fun print(){
print("hello world"); // 无参
}
fun print(str:String){
print("hello world $str"); // 有参
}
fun print1(str:String = "hello"){
print("hello world $str"); // 有参,并且参数有默认值
}
这三种写法其实只在参数上有区别,写法基本一致。跟java也很相似。
在看看不一样的:
fun printMessage() = print("hello world"); // 只有单行语句
fun printMsg(bol: Boolean, Str: String): String = if (bol) "" else "hello world"; // 单行实现,并且有返回值赋值
我们看一下java的编译结果
public static final void printMessage() {
print("hello world");
}
@NotNull
public static final String printMsg(boolean bol, @NotNull String Str) {
Intrinsics.checkParameterIsNotNull(Str, "Str");
return bol ? "" : "hello world";
}
2、内嵌函数
fun doubleFun2() {
val str = "Hello world";
fun say(count: Int) {
println(str);
if (count > 0) {
say(count - 1);
}
}
say(10);
}
内嵌函数就是在函数内部,再定义一个函数,该函数可以直接访问到外部函数的值,并且可被外部函数访问。我们看一下java的编译结果:
public static final void doubleFun2() {
final String str = "Hello world";
<undefinedtype> say$ = new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke(((Number)var1).intValue());
return Unit.INSTANCE;
}
public final void invoke(int count) {
String var2 = str;
boolean var3 = false;
System.out.println(var2);
if (count > 0) {
((<undefinedtype>)this).invoke(count - 1);
}
}
};
say$.invoke(10);
}
我们首先看一下Function1 代表什么
public interface Function0<out R> : Function<R> {
/** Invokes the function. */
public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2): R
}
等等,依次类推,可以发现,区别仅仅在入参数量上,而当我们使用内嵌函数时,kotlin编译器就会根据函数的入参数量,使用相应的FunctionX(X表示X个入参)接口。
而当我们调用的时候,实际上就是调这些function的接口,从java知识我们知道,因为一定要给入参制定一个类型,所以默认情况下,就是所有类的父类——Object。故而每次使用,kotlin都会为我们生成一个入参、返回值都是Object的FunctionX,当调用的时候,进行一次强转即可。
3、 扩展函数
扩展函数时kotlin语言非常灵活的方式,可以让你给java类扩展函数(方法)。比如给String类增加一个方法
fun String.lastChar(): Char = this.get(this.length - 1),
这个编译成java就是:
public static final char lastChar(@NotNull String $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
return $receiver.charAt($receiver.length() - 1);
}
那么我们在其他的文件里面就可以调用,可以直接写成
fun check() {
println("Kotlin".lastChar())
}
再看看他编译的java
public static final void check() {
char var0 = TestKt.lastChar("Kotlin");
boolean var1 = false;
System.out.println(var0);
}
从编译的java文件可以看到,实际上是存在调用关系的,故而如果在不同的包中,就需要import这个函数,例如
import demo.baidu.com.myapplication.lastChar
如果与当前包中存在相同的扩展函数,你也可以给他改名例如:
import demo.baidu.com.myapplication.lastChar as last。
可以看到,你给类增加的方法,其他的kotlin的文件均可以使用。包括java中,我们只要按照java编译的结果进行调用即可:
demo.baidu.com.myapplication.TestKt.lastChar(“Kotlin”);
由此可以kotlin带来了什么,我们不用再为每一个简单的转换,去写大量的Utils(StringUtils)等等,我们已经为这些类扩展了方法,直接再使用时调用即可。
4、高阶函数
高阶函数,从字面理解就是函数嵌函数,但是跟内嵌函数不一样,他是把函数作为参数和返回值的函数。简单举个例子:
fun advancedfun(operation: (Int, Int) -> Int) { // 入参是 lamada表达式(Int, Int) -> Int函数参数。
val result = operation(2, 3) // 执行operation相关操作
println("The result is $result")
}
fun testAdvancedfun(args: Array<String>) {
advancedfun { a, b -> a + b } // 传入函数参数。
advancedfun { a, b -> a * b }
}
先看一下java编译结果:
public static final void advancedfun(@NotNull Function2 operation) {
Intrinsics.checkParameterIsNotNull(operation, "operation");
int result = ((Number)operation.invoke(2, 3)).intValue();
String var2 = "The result is " + result;
boolean var3 = false;
System.out.println(var2);
}
public static final void testAdvancedfun(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
advancedfun((Function2)null.INSTANCE);
advancedfun((Function2)null.INSTANCE);
}
此处又见Function2,所以可以看到高阶函数的本质就是函数式接口,实际上传入的就是Function2接口的实际实现。
5、内联函数
5.1、简介
如果学过C++的话,应该对内联函数有所了解,他可以理解成一个可以被复用的代码段。在编译的过程中,代码段就会直接在相应的函数中copy一份,这样虽然会增加编译文件大小,但是可以减少在运行时的函数调用,是一种提高调用效率的实现。
先看一个简单的例子
fun testInlineFun() {
print("testInlineFun1")
inlineFunc1(2)
}
inline fun inlineFunc1(params1: Int) {
print("inlineFunc1 $params1")
}
我们看一下java编译的结果:
public static final void testInlineFun() {
print("testInlineFun1");
int params1$iv = 2;
int $i$f$inlineFunc1 = false;
print("inlineFunc1 " + params1$iv);
}
public static final void inlineFunc1(int params1) {
int $i$f$inlineFunc1 = 0;
print("inlineFunc1 " + params1);
}
5.2、内联函数妙用
需要明确的是,kotlin实际是不推荐像5.1那样书写内联函数的,原因有二,其一,JVM本来会根据函数调用进行内联优化,并且不增加生成class文件大小,其二,带来编译class文件增大带来的入栈出栈次数的减少不明显。kotlin的内联函数,更多地是为了解决lambda函数在传递过程的损耗,我们其实在lambda函数一节中看到,lambda实际的原理,会生成class,以及进行调用。故而我们更多的时候,是在使用lambda作为参数传递时,才使用内联函数,并且内联函数的方法体,也不能过长。
所以绝大多数内联函数都是带lamda的参数的(实际上就是函数,这个我们在高阶函数中描述过)。举个简单的例子:
fun testInlineFun2() {
print("testInlineFun2")
inlineFunc2(2) { it + 1 }
}
inline fun inlineFunc2(params1: Int, inner: (Int) -> Int) {
val result = inner(params1)
print("inlineFunc1 $result")
}
看一下java编译的结果
public static final void testInlineFun2() {
print("testInlineFun2");
int params1$iv = 2;
int $i$f$inlineFunc2 = false;
int var3 = false;
int result$iv = params1$iv + 1;
print("inlineFunc1 " + result$iv);
}
public static final void inlineFunc2(int params1, @NotNull Function1 inner) {
int $i$f$inlineFunc2 = 0;
Intrinsics.checkParameterIsNotNull(inner, "inner");
int result = ((Number)inner.invoke(params1)).intValue();
print("inlineFunc1 " + result);
}
可以看到,内联之后,直接把函数实现,就在原函数里面实现了。但是需要注意的函数要直接传入,不能把函数赋值给变量后,再把变量传入,例如:
inline fun inlineFunc2(params1: Int, inner: (Int) -> Int) {
val result = inner(params1)
print("inlineFunc1 $result")
}
fun testInlineFun3() {
print("testInlineFun2")
val testfun: (Int) -> Int = { it -> it + 1 }
inlineFunc2(2, testfun)
}
这时候,我们看到的结果就是
public static final void testInlineFun3() {
print("testInlineFun2");
Function1 testfun = (Function1)null.INSTANCE;
int params1$iv = 2;
int $i$f$inlineFunc2 = false;
int result$iv = ((Number)testfun.invoke(Integer.valueOf(params1$iv))).intValue(); // 调用函数inlineFunc2。
print("inlineFunc1 " + result$iv);
}
所以内联函数,它本身还是函数,当他不按照固定的方式传入,他就是普通函数。
那么内联函数的那个函数参数是什么类型的函数呢?默认情况下也是内联函数,所以一旦你把内联函数参数再传给你一个非内联函数的话,就会报错,例如:
fun testInlineFun4() {
print("testInlineFun2")
inlineFunc4(2) { it + 1 }
}
inline fun inlineFunc4(params1: Int, inner: (Int) -> Int) {
val result = inner(params1)
checkFun(params1) // 参数可以传递
inlineFunc5(inner) // 内联函数传递给了非内联函数(默认参数类型不是内联函数)
print("inlineFunc4 $result")
}
fun inlineFunc5(inner: (Int) -> Int) {
print("inlineFunc5")
}
fun checkFun(params: Int) {
print("checkFun")
}
故而我们可以把函数inlineFunc4 改成:
inline fun inlineFunc4(params1: Int, noinline inner: (Int) -> Int) {
val result = inner(params1)
checkFun(params1)
inlineFunc5(inner)
print("inlineFunc4 $result")
}
这样,这个函数就不会被内联调用了,变成了普通函数,或者把inlineFunc5改成内联函数:
inline fun inlineFunc5(inner: (Int) -> Int) {
print("inlineFunc5")
}
此时,就会产生连锁的内联,编译结果如下:
public static final void testInlineFun4() {
print("testInlineFun2");
int params1$iv = 2;
int $i$f$inlineFunc4 = false;
int var3 = false;
int result$iv = params1$iv + 1;
checkFun(params1$iv);
int $i$f$inlineFunc5 = false;
print("inlineFunc5");
print("inlineFunc4 " + result$iv);
}
6、简易的写法
get set函数
var age : Int
get() = 10
set(value) { }
可以很简单生成方法:
public static final int getAge() {
return 10;
}
public static final void setAge(int value) {
}