Kotlin bytecode on default parameters


#1

Hi I’m having an argument with someone on my team about the bytecode generated when I use default parameters on the constructor and when I don’t. When I use it, Kotlin creates another constructor, and we are arguing if we should keep the simple implementation but with that extra constructor, or if we should create a much more boilerplate and harder do maintain code to avoid the creation of it. And I’m also curious about what is this second constructor, and what it does, can someone help me?

---------------The simple implementation of class is:------------------

open class School (@PrimaryKey
var code: String = “”,
var id: Int = 0,
var name: String ="",
var active: Boolean = true,
var state: State = State(),
var yearDivision: String = “”,
var schoolNetwork: String = “”,
@RealmAutoMigration.MigratedList(listType = Discipline::class)
var disciplines: RealmList = RealmList()): RealmObject()

And the bytecode generated creates this

public School2(@NotNull UUID id, @NotNull String name, @NotNull String code, @NotNull RealmList disciplines, @NotNull SchoolSystem schoolSystem) {
Intrinsics.checkParameterIsNotNull(id, “id”);
Intrinsics.checkParameterIsNotNull(name, “name”);
Intrinsics.checkParameterIsNotNull(code, “code”);
Intrinsics.checkParameterIsNotNull(disciplines, “disciplines”);
Intrinsics.checkParameterIsNotNull(schoolSystem, “schoolSystem”);
super();
String var10001 = id.toString();
Intrinsics.checkExpressionValueIsNotNull(var10001, “id.toString()”);
this.id = var10001;
var10001 = id.toString();
Intrinsics.checkExpressionValueIsNotNull(var10001, “id.toString()”);
this.id = var10001;
this.name = name;
this.code = code;
this.disciplines = disciplines;
this.schoolSystem = schoolSystem;
this.createdDate = new Date();
}

// $FF: synthetic method
public School2(UUID var1, String var2, String var3, RealmList var4, SchoolSystem var5, int var6, DefaultConstructorMarker var7) {
if((var6 & 1) != 0) {
UUID var10000 = UUID.randomUUID();
Intrinsics.checkExpressionValueIsNotNull(var10000, “UUID.randomUUID()”);
var1 = var10000;
}

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

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

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

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

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

}

public School2() {
this((UUID)null, (String)null, (String)null, (RealmList)null, (SchoolSystem)null, 31, (DefaultConstructorMarker)null);
}

----------More complex implementation of the same class--------------

open class School2(
id: UUID,
name: String,
code: String,
disciplines: RealmList,
schoolSystem: SchoolSystem) : RealmObject() {

@PrimaryKey
var id: String = id.toString()
    private set

@Required
var name: String

@Required
var code: String

@RealmAutoMigration.MigratedList(listType = Discipline2::class)
var disciplines: RealmList<Discipline2>

var schoolSystem: SchoolSystem

var createdDate: Date
    private set

init {
    this.id = id.toString()
    this.name = name
    this.code = code
    this.disciplines = disciplines
    this.schoolSystem = schoolSystem
    this.createdDate = Date()
}

constructor() : this(
        UUID.randomUUID(),
        "",
        "",
        RealmList<Discipline2>(),
        SchoolSystem()
)

}

The bytecode generated is:

public School2(@NotNull UUID id, @NotNull String name, @NotNull String code, @NotNull RealmList disciplines, @NotNull SchoolSystem schoolSystem) {
Intrinsics.checkParameterIsNotNull(id, “id”);
Intrinsics.checkParameterIsNotNull(name, “name”);
Intrinsics.checkParameterIsNotNull(code, “code”);
Intrinsics.checkParameterIsNotNull(disciplines, “disciplines”);
Intrinsics.checkParameterIsNotNull(schoolSystem, “schoolSystem”);
super();
String var10001 = id.toString();
Intrinsics.checkExpressionValueIsNotNull(var10001, “id.toString()”);
this.id = var10001;
var10001 = id.toString();
Intrinsics.checkExpressionValueIsNotNull(var10001, “id.toString()”);
this.id = var10001;
this.name = name;
this.code = code;
this.disciplines = disciplines;
this.schoolSystem = schoolSystem;
this.createdDate = new Date();
}

public School2() {
UUID var10001 = UUID.randomUUID();
Intrinsics.checkExpressionValueIsNotNull(var10001, “UUID.randomUUID()”);
this(var10001, “”, “”, new RealmList(), new SchoolSystem());
}


#2

There are two aspects to this. Should you worry about the synthetic method for the default parameters. Basically no, you shouldn’t really worry about it. It is one of those “premature optimization” cases. In the case that there is a speed issue you can add the appropriate overloaded constructor and avoid calling the synthetic one with default parameters. If you can’t live with it’s existence after that use something like proguard to strip it. Any potential (measure first) performance improvements are likely to be outweighed by programmer convenience.

The other part is whether or not you should use default parameters. In this particular case I would say you should not use it. The problem is that default constructing will not create a valid school (assuming that a school should have valid codes, states, etc.). You also seem to overuse string for things that should probably be objects (a schoolNetwork may be an object allowing the querying of all schools in it…). As to the UUID, it probably is more robust not to automatically create a new one if it is missing.


#3

Thank you for your reply pdvrieze.
About the second point, I noticed that the School with default parameters was an earlier version of our class. The actual format is the School2 with that properties. About using default parameters, I’m following the Realm documentation to implement RealmObjects in kotlin
Realm Docs

About the synthetic method, that’s what I was thinking, that I should not worry about it. Different from inline functions bytecode generated, at least in my opinion. My question was about this method and it’s implementation.