Kotlin learning之函数

函数

参数默认值

java的重载方法常见,比如我们在Android中自定义View的时候,就需要重载它的三个或者四个构造方法。在Kotlin中,我们可以通过指定参数的默认值避免无用的代码。

import android.content.Context
import android.util.AttributeSet
import android.view.View

class CusView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

我们可以看到CusView类的构造方法中attrs的默认值为null,defStyleAttr默认值为0,也就是说如果不指定attrs和defStyleAttr的值,那么就会把对应的属性设置为默认值。
kotlin中调用的时候可以采取以下几种方式:

        val cusView1 = CusView(this)
        val cusView2 = CusView(this,null)
        val cusView3 = CusView(this,null,0)
        val cusView4 = CusView(this,defStyleAttr = 0) // 可以指定命名参数,避免混淆

实现原理

为了能够更加清楚的展示原理,我们写一个稍微复杂的kotlin重载方法:

class Kotlin {
    fun f(a: Int = 0, b: String = "hello", c: Int = 99, d: String = "hh", e: Float = 1.0f) {

    }
}

看下对应的Java Code:

public final class Kotlin {
   public final void f(int a, @NotNull String b, int c, @NotNull String d, float e) {
      Intrinsics.checkParameterIsNotNull(b, "b");
      Intrinsics.checkParameterIsNotNull(d, "d");
   }

   // $FF: synthetic method
   public static void f$default(Kotlin var0, int var1, String var2, int var3, String var4, float var5, int var6, Object var7) {
      if ((var6 & 1) != 0) {
         var1 = 0;
      }

      if ((var6 & 2) != 0) {
         var2 = "hello";
      }

      if ((var6 & 4) != 0) {
         var3 = 99;
      }

      if ((var6 & 8) != 0) {
         var4 = "hh";
      }

      if ((var6 & 16) != 0) {
         var5 = 1.0F;
      }

      var0.f(var1, var2, var3, var4, var5);
   }
}


这里面一共有两个方法,一个是f,如果我们的参数全部都有指定值时,则会调用这个方法;另外一个方法是
$default,如果调用方法的参数没有全部都有指定值(即有的参数使用了默认值)时,会调用这个方法,在该方法内部使用了位运算来表示那个参数要使用默认值。具体来说,假设一共有啊a、b、c、d、e五个参数都指定了默认值,那么在调用该方法的时候会构造一个var6,这个参数的二进制的后五位的每一位都对应了一个参数,如果指定了参数,那么这一位为0,否则为1。举例来说调用f(a = 0, b = "bb", d = "ff"),由于a、b、d都指定了参数,那么第1位、第2位、第4位为0,第3位与第五位为1,那么var6的值为10100即为20。如下代码:

    fun ff() {
        f(a = 0, b = "bb", d = "ff")
    }

对应的java code:

 public final void ff() {
      f$default(this, 0, "bb", 0, "ff", 0.0F, 20, (Object)null);
   }

@JvmOverloads

细心的读者可能会发现,CusView的构造方法由@JvmOverloads来修饰,这个注解的作用是什么呢?
首先来看下不加这个注解时,kotlin代码对应的java code:

public final class CusView extends View {
   private HashMap _$_findViewCache;

   public CusView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      Intrinsics.checkParameterIsNotNull(context, "context");
      super(context, attrs, defStyleAttr);
   }

   // $FF: synthetic method
   public CusView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) {
      if ((var4 & 2) != 0) {
         var2 = (AttributeSet)null;
      }

      if ((var4 & 4) != 0) {
         var3 = 0;
      }

      this(var1, var2, var3);
   }
   // 。。。有省略
}

那么这样就会有一个问题,java代码调用kotlin代码的时候,就必须指定全部参数,不能省略部分参数。因为并没有重载方法。那么如果需要重载方法,那么就需要加上@JvmOverloads注解。这样编译器就会生成Java重载方法。如下:

public final class CusView extends View {
   @JvmOverloads
   public CusView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      Intrinsics.checkParameterIsNotNull(context, "context");
      super(context, attrs, defStyleAttr);
   }

   // $FF: synthetic method
   @JvmOverloads
   public CusView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) {
      if ((var4 & 2) != 0) {
         var2 = (AttributeSet)null;
      }

      if ((var4 & 4) != 0) {
         var3 = 0;
      }

      this(var1, var2, var3);
   }

   @JvmOverloads
   public CusView(@NotNull Context context, @Nullable AttributeSet attrs) {
      this(context, attrs, 0, 4, (DefaultConstructorMarker)null);
   }

   @JvmOverloads
   public CusView(@NotNull Context context) {
      this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null);
   }
}

扩展函数

扩展函数是类的成员函数,只不过它是定义在类外。

fun String.firstChar():Char= if (length==0) ' ' else get(0)
fun main() {
    println("hello".firstChar())
}

// output:h

我们自己定义了一个String的扩展函数firstChar,获取String的第一个字符。这样就相当于把String类动态添加了一个功能。是不是感觉跟代理模式有些相似?

原理

扩展函数是在当前类下增加了一个静态方法,以扩展类作为参数。

public final class LearnKotlinKt {
   public static final void main() {
      char var0 = firstChar("hello");
      System.out.println(var0);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }

   public static final char firstChar(@NotNull String $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      return $receiver.length() == 0 ? ' ' : $receiver.charAt(0);
   }
}


所以如果想从Java代码中调用扩展函数,需要通过类名.方法名方式调用。

现在问题来了,如果定义的扩展函数与类中原本的函数方法重名,那么实际调用会使用哪一个方法呢?
答案是原有函数会覆盖扩展函数
我们来看下:


class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }


// 如果调用C类型的foo方法,则会输出member。

扩展函数能够覆盖吗?

如果父类和子类同时定义了一个扩展函数,那么子类的扩展函数能够覆盖父类吗?
我们看下实际例子:

open class Parent

class Stub : Parent()

fun Parent.say() = print("I am parent")
fun Stub.say() = print("I am stub")

fun main() {
    var p: Parent = Stub()
    p.say()
}

// output: "I am parent"

这里我们可以看到被调用的函数是由静态类型决定的,看下对应的java code 就更加直观了。


// Parent.java
import kotlin.Metadata;

@Metadata(
   mv = {1, 1, 13},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\b\u0016\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002¨\u0006\u0003"},
   d2 = {"LParent;", "", "()V", "LearnKotlin"}
)
public class Parent {
}
// Stub.java
import kotlin.Metadata;

@Metadata(
   mv = {1, 1, 13},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002¨\u0006\u0003"},
   d2 = {"LStub;", "LParent;", "()V", "LearnKotlin"}
)
public final class Stub extends Parent {
}
// HelloKt.java
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 13},
   bv = {1, 0, 3},
   k = 2,
   d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u001a\n\u0010\u0002\u001a\u00020\u0001*\u00020\u0003\u001a\n\u0010\u0002\u001a\u00020\u0001*\u00020\u0004¨\u0006\u0005"},
   d2 = {"main", "", "say", "LParent;", "LStub;", "LearnKotlin"}
)
public final class HelloKt {
   public static final void say(@NotNull Parent $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      String var1 = "I am parent";
      System.out.print(var1);
   }

   public static final void say(@NotNull Stub $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      String var1 = "I am stub";
      System.out.print(var1);
   }

   public static final void main() {
      Parent p = (Parent)(new Stub());
      say(p);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值