6、Kotlin的函数

本节我们讲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) {
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值