聊一下kotlin的构造函数

前言

之前看了Java和kotlin的对比语法后就开始写项目,对于变量和方法的定义,类的继承,接口的实现的写法都能上手,然后在突然需要自定义View的时候,发现自己不会重写构造函数,然后又不得不去网上搜索一下怎么进行构造函数的重载,发现了一些需要注意的地方,记录一下。

我将我整理的构造函数的一些需要记录的方式先写出来,如果能知道这些都是啥那就没必要看这文章了

class Person {}

class Person1() {}

class Person2 private constructor() {}

class Person3(_name: String, _age: Int) {
    fun showName() {
//        println(_name)
    }
}

class Person4(val name: String, val age: Int) {
    fun showName() {
        println(name)
    }
}

class Person5(_name: String, _age: Int) {
    var mName: String? = null

    init {
        val name = _name
        val age = _age
        mName = _name
    }
}

class Person6(val name: String, val age: Int = 18) {}

class Person8(_name: String, _age: Int) {
    constructor(name: String) : this(name, 18)
}


class Person7 @JvmOverloads constructor(val name: String, val age: Int = 18) {}

class Person9 @JvmOverloads constructor(
    _name: String,
    _age: Int = 18,
    _height: Int = 170,
    _weight: Int = 60,
    _sex: Char = '男'
) {}
  • kotlin的类定义是默认自带了空构造函数,实例化对象的时候需要通过Person()就是调用了空的构造函数
  • constructor是构造函数的关键字,声明类的时候,主构造函数可以省略关键字,关键字之前可以加上private,让构造函数私有化

public final class Person {
}
public final class Person1 {
}
public final class Person2 {
   private Person2() {
   }
}
Person person = new Person();
Person1 person1 = new Person1();
//'Person2()' has private access in 'xxx.Person2'
//Person2 person2 = new Person2();

  • 主构造函数中声明的参数,如果不使用val/var关键字声明,是不能作为全局变量在类里面进行访问
  • 不使用val/var关键字声明的变量,可以在init{}代码块中进行访问,从而赋值给类里面的全局变量,init{}代码块中声明的局部变量作用域也只是在init的函数栈中,类的其他地方访问不到

//不使用val/var声明构造函数参数,不会生成类里面的全局属性
public final class Person3 {
   public final void showName() {
   }

   public Person3(@NotNull String _name, int _age) {
      super();
   }
}

//使用了val/var声明的构造函数参数,会在类里面声明成员属性
public final class Person4 {
   @NotNull
   private final String name;
   private final int age;

   public final void showName() {
      String var1 = this.name;
      boolean var2 = false;
      System.out.println(var1);
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public Person4(@NotNull String name, int age) {
      super();
      this.name = name;
      this.age = age;
   }
}

//init代码块类似于主构造函数的函数体
public final class Person5 {
   @Nullable
   private String mName;

   @Nullable
   public final String getMName() {
      return this.mName;
   }

   public final void setMName(@Nullable String var1) {
      this.mName = var1;
   }

   public Person5(@NotNull String _name, int _age) {
      super();
      this.mName = _name;
   }
}
  • kotlin构造函数的变量声明处可以赋值默认值,这个是java没有的特性,kotlin中通过constructor声明次构造函数,通过javac编译后的代码会有函数重载让Java可以调用有默认参数的构造方法,也可以通过@JvmOverloads直接编译出重载构造函数

//编译后生成一个两个参数的构造方法,和一个用于kotlin调用缺省参数的填默认值的方法
public final class Person6 {
   @NotNull
   private final String name;
   private final int age;

   public Person6(@NotNull String name, int age) {
      super();
      this.name = name;
      this.age = age;
   }

   // $FF: synthetic method
   public Person6(String var1, int var2, int var3, DefaultConstructorMarker var4) {
      if ((var3 & 2) != 0) {
         var2 = 18;
      }
      this(var1, var2);
   }
}

//java中没有调用缺省参数的构造方法
//'Person6(java.lang.String, int)' in 'xxx.Person6' cannot be applied to '(java.lang.String)'
// Person6 person6 = new Person6("bitibaba");
val p6 = Person6("bitibab")//kotlin可以直接使用这个特性

//通过@JvmOverloads会生成多个构造函数,Java也可以使用缺省参数的方式进行调用
public final class Person7 {
   @NotNull
   private final String name;
   private final int age;

   @JvmOverloads
   public Person7(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   // $FF: synthetic method
   public Person7(String var1, int var2, int var3, DefaultConstructorMarker var4) {
      if ((var3 & 2) != 0) {
         var2 = 18;
      }

      this(var1, var2);
   }

   @JvmOverloads
   public Person7(@NotNull String name) {
      this(name, 0, 2, (DefaultConstructorMarker)null);
   }
}

//通过一个参数,另一个默认参数不传可以实例化对象
 Person7 person7 = new Person7("bitibaba");

//直接显示的声明一个次构造函数,参数如果过多,没有@JvmOverloads方便
public final class Person8 {
   public Person8(@NotNull String _name, int _age) {
      super();
   }

   public Person8(@NotNull String name) {
      this(name, 18);
   }
}
  • 在Java中,当构造函数参数过多时,需要按照顺序依次传入对应的参数,在kotlin中,可以通过显示赋值的方式,就不必按照参数的顺序进行赋值,更加的方便

默认参数怎么实现的

上面的kotlin默认参数特性使用起来非常的方便,而且通过参数名无视参数顺序的方法赋值也很方便,实现起来也非常的简单

class Person9(
    _name: String,
    _age: Int = 18,
    _height: Int = 170,
    _weight: Int = 60,
    _sex: Char = '男'
) {}

//使用kotlin进行实例化
val p1 = Person9("xiaobiti")
val p2 = Person9("xiaobiti",12)
val p3 = Person9("xiaobiti", 12,155)
val p4 = Person9("xiaobiti", 12,155,45)
val p5 = Person9("xiaobiti", 12,155,45,'女')
val p6 = Person9("xiaobiti", _age = 12, _sex = '女')

//编译后
public final class Person9 {
   public Person9(@NotNull String _name, int _age, int _height, int _weight, char _sex) {
      super();
   }

   // $FF: synthetic method
   public Person9(String var1, int var2, int var3, int var4, char var5, int var6, DefaultConstructorMarker var7) {
      if ((var6 & 2) != 0) {
         var2 = 18;
      }

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

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

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

      this(var1, var2, var3, var4, var5);
   }
}

new Person9("xiaobiti", 0, 0, 0, '\u0000', 30, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 0, 0, '\u0000', 28, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 155, 0, '\u0000', 24, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 155, 45, '\u0000', 16, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 155, 45, '女');
new Person9("xiaobiti", 12, 0, 0, '女', 12, (DefaultConstructorMarker)null);

通过看编译后的代码,发现在使用缺省参数的特性的时候,没有@JvmOverloads注释,只生成了一个满参数的构造函数和另一个构造方法,而使用默认参数的构造函数调用最终都调用了生成的$FF: synthetic method方法,这个方法中经过一系列的if校验后,还是调用的满参数的构造函数方法,那么这个if判断是在做什么,通过var6这个最后一个参数进行&运算,判断对应的位置是否有设置

会对构造函数的参数有一个序号值,为2的n次方,这个n就是代表参数的位置,通过与运算,就很好的通过一个标志位去判断哪些参数没有设置值,从而设置对应的默认值

这个很好理解,构造函数比如有n个参数,如果每个参数都有默认值的情况,那么调用构造函数对于每个位置的参数就会有赋值/没赋值这两种情况,通过二进制表示,再进行&运算,就能判断出是否赋值

class Person9(
    _name: String,// 2^0=1
    _age: Int = 18, //2^1=2
    _height: Int = 170,//2^2=4
    _weight: Int = 60,//2^3=8
    _sex: Char = '男'//2^4=16
) {}
//举个栗子
//类似这个调用,缺省了第二位的参数,那就是00010
val p7 = Person9("xiaobiti", _height = 155, _weight = 45, _sex = '女')

  // $FF: synthetic method
public Person9(String var1, int var2, int var3, int var4, char var5, int var6, DefaultConstructorMarker var7) {
     //2的2进制就是10-> 00010&10=00010  !=0 判断命中,第二个参数_age就赋值默认参数18
      if ((var6 & 2) != 0) {
         var2 = 18;
      }

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

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

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

      this(var1, var2, var3, var4, var5);
   }
}

//如下就能知道
//00100=4
val p8 = Person9("xiaobiti", _age = 12, _weight = 45, _sex = '女')
//01000=8
val p9 = Person9("xiaobiti", _age = 12, _height = 155, _sex = '女')
//10000=16
val p10 = Person9("xiaobiti", _age = 12, _height = 155, _weight = 45)

//编译后
new Person9("xiaobiti", 12, 0, 45, '女', 4, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 155, 0, '女', 8, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 155, 45, '\u0000', 16, (DefaultConstructorMarker)null);

//回头看前面的栗子
val p1 = Person9("xiaobiti") //11110
val p2 = Person9("xiaobiti", 12) // 11100
val p3 = Person9("xiaobiti", 12, 155) // 11000
val p4 = Person9("xiaobiti", 12, 155, 45)// 10000

//验证反编译后
new Person9("xiaobiti", 0, 0, 0, '\u0000', 30, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 0, 0, '\u0000', 28, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 155, 0, '\u0000', 24, (DefaultConstructorMarker)null);
new Person9("xiaobiti", 12, 155, 45, '\u0000', 16, (DefaultConstructorMarker)null);

kotlin还是编译器的加持,让写的缺省参数在编译的时候识别出缺的参数的位置,然后再把对应的位置的二进制赋值为1,生成一个匹配值,通过系列的if判断,可以将缺的值赋值为默认值
通过上面的内容,在自定义View的时候,就可以通过注解重载构造函数

//传统的java写法
public class MyJavaView extends View {

    public MyJavaView(Context context) {
        this(context,null);
    }

    public MyJavaView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyJavaView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
//会重写构造函数,并且调用多参数的构造函数

//kotlin写法,加上注解
class MyKtView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) :View(context, attrs, defStyleAttr) {
}

//反编译后
public final class MyKtView extends View {
   @JvmOverloads
   public MyKtView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
   }

   // $FF: synthetic method
   public MyKtView(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 MyKtView(@NotNull Context context, @Nullable AttributeSet attrs) {
      this(context, attrs, 0, 4, (DefaultConstructorMarker)null);
   }

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


小结

这些就是我看了Java转kotlin的文章后没有理解到的点,如果路过的大佬有补充的欢迎评论讨论哟,跪谢

猜你喜欢

转载自blog.csdn.net/liujun3512159/article/details/127815997