5

Consider the following code:

class Foo(val bar: String, val baz: Boolean = true) {
    constructor(bar: String) : this(bar, false)
}

Without the addition of a secondary constructor, I could call Foo("") because the second argument has a default value. This would result in baz being true.

With the addition of a secondary constructor, I can still call Foo(""), except that now baz is false.

Why does Kotlin not see this as a duplicate constructor signature, since they can both be called with the same arguments?

5
  • You don't need bar, it just complicates the example. Interesting. You could still call Foo("", true). It seems it would be useful if it warned that the secondary constructor is meaningless because of the default value. Commented Sep 10, 2019 at 15:13
  • my guess is: because it's not implemented yet ;-) from the bytecode-point of view I see 3 constructors... one for Foo(String, Boolean), a synthetic Foo(String, DefaultConstructorMarker) which uses the default value and Foo(String)... so these are 3 distinct constructors... maybe that's coming... on the other hand I don't know when I ever had such a constellation... and also cost/benefit ratio is probably not so good... Commented Sep 10, 2019 at 15:31
  • If you use the 'first' or the 'second' constructor, you always need to provide at least 1 parameter, i.e for bar. In the second constructor, you are always delegating to the primary constructor and you are already providing it with a default value which is false. Reading from decompiled code, it appears that the compiler generated a synthetic method and where it is providing all signatures with their values. So it does not matter which constructor you call, in the end, all conditions are being met. Commented Sep 10, 2019 at 16:54
  • @series0ne does my post answer your question? :) Commented Oct 6, 2019 at 15:30
  • 1
    @WilliMentzel yes :) Commented Oct 7, 2019 at 19:21

1 Answer 1

6

If you take a look at the bytecode there are actually three constructors generated, as Roland already pointed out.

public Foo(@NotNull String bar, boolean baz) { ... }
public Foo(String var1, boolean var2, int var3, DefaultConstructorMarker var4) { ... }
public Foo(@NotNull String bar) { ... }

So, there are no duplicate constructor signatures. Now one might ask how Kotlin chooses which overload to take judging from the call-site only.

The overall rationale is that the most specific function/constructor will be chosen from the overload candidates.

This is what the Kotlin language specification says about it:

  • For each candidate, we count the number of default parameters not specified in the call (i.e., the number of parameters for which we use the default value);

  • The candidate with the least number of non-specified default parameters is a more specific candidate;


I know that you intended this to only be an example, but if something like this happens in a real-world situation one should avoid it like the Kotlin Language Documentation (page 76) states:

If you have an object with multiple overloaded constructors that don't call different superclass constructors and can't be reduced to a single constructor with default argument values, prefer to replace the overloaded constructors with factory functions.

class Foo2(val bar: String, val baz: Boolean = true) {
    companion object {
        fun factoryCreate(s: String) = Foo2(s, false)
    }
}

In this case it will always be clear right away (without thinking about the overloading resolution rules) what baz will be after creation.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.