函数
参数默认值
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();
}
}