kotlin copy方法中的位运算

1,288 阅读1分钟

问题

在使用数据类copy函数时,copy函数可以接收这个类的各种参数组合,编译器怎么实现的?它会为每种情况都会生成一个单独的copy函数吗?

源码

data class U(val p1: String,val p2:String,val p3:String,val p4:String)
fun main() {
    val u = U("p1","p2","p3","p4")
    val copy = u.copy()
}

反编译

//删除了部分代码
public final class T2Kt {
   public static final void main() {
      U u = new U("p1", "p2", "p3", "p4");
      U copy = U.copy$default(user, (String)null, (String)null, (String)null, (String)null, 15, (Object)null);
   }
}

public final class U {

    public final U copy(@NotNull String p1, @NotNull String p2, @NotNull String p3, @NotNull String p4) {
        return new U(p1, p2, p3, p4);
    }

    public static U copy$default(U var0, String var1, String var2, String var3, String var4, int var5, Object var6) {
        if ((var5 & 1) != 0) {
            var1 = var0.p1;
        }

        if ((var5 & 2) != 0) {
            var2 = var0.p2;
        }

        if ((var5 & 4) != 0) {
            var3 = var0.p3;
        }

        if ((var5 & 8) != 0) {
            var4 = var0.p4;
        }

        return var0.copy(var1, var2, var3, var4);
    }

}

当使用copy无参方法时,编译器帮我们自动生成一个var5=15,15的二进制为1111,在copy$default()方法中和1(0001),2(0010),4(0100),8(1000) 操作结果全部不为0,所以每个参数值都会使用原对象的值,即没有参数值发生变化。

那只输入p1:u.copy(p1="c1"),编译器给我们生成的数字是多少呢?

14(1110),和1与操作后为0,var1就不会被原来对象中的p1覆盖,而使用我们传入的参数值。和2,4,8与操作全不为0,参数使用原来对象中的值。

那输入p1,p2呢?:u.copy(p1="c1",p2="c2"),编译器给我们生成的数字是多少呢?

12(1100),与1,2与操作后为0,所以结合代码来看就是p1,p2使用我们传入的参数值,p3,p4对应值被原对象覆盖。

总结

所以,从上面几个例子就可以得出规律: 4个参数可以用4个二进制位来表示,(0,0,0,0)->(p4,p3,p2,p1)。0代表输入了该位置参数,1代表未输入参数使用原对象值。只是二进制位顺序和参数顺序是反的,第1个二进制位代表了p4,最后一个二进制位代表p1。

例子:

  • 1111->():没有输入
  • 1011->(p3) :只输入p3
  • 0111->(p4) :只输入p4
  • 0011->(p3,p4) :输入p3,p4

U类一共有4个参数,那输入参数的情况就有2^4=16种。下面是所有情况:

输入参数状态值二进制
151111
p1141110
p2131101
p1,p2121100
p3111011
p1,p3101010
p2,p391001
p1,p2,p381000
p470111
p1,p460110
p2,p450101
p1,p2,p440100
p3,p430011
p1,p3,p420010
p2,p3,p410001

当然还有种状态是 u.copy(p1="c1",p2="c2",p3="c3",p4="c4"),所有的参数都被重新赋值。那这种状态值根据上面的规律那就是0(0000)。但你反编译代码后会发现并不是这样,编译器会生成直接调用copy函数而不是copy$default函数,因为不需要再判断哪一位参数是否需要赋值了,所以直接调用copy函数 。

kotlin中参数可以任意调整位置比如u.copy(p2="c2",p1="c1"),那这算一种情况吗? 反编译后可以看见它的状态值还是12,编译器会先把他们的顺序调整为参数的顺序。

public static final void main() {
   U u = new U("p1", "p2", "p3", "p4");
   Object var2 = null;
   Object var3 = null;
   String var4 = "c1";
   String var5 = "c2";
   U.copy$default(u, var4, var5, (String)var3, (String)var2, 12, (Object)null);
}