A sequence.

At Google I/O in May, Google officially announced the concept of Kotlin-First as the language of choice for Android developers.

The new language naturally has new features, while keeping the Java programming habits to write Kotlin, it is not impossible, but it always feels awkward.

Recently the public, “Google developers” serial a “the practical Kotlin build the Android | Kotlin migration guide series, has, for example, some small Kotlin coding skills. Since it is a kind of guide, naturally, on the basis of “more and more”, some details are intentionally omitted. At the same time, some examples of scenes may be inappropriate.

I’ll fill in the details here, and today I’ll talk about using Kotlin’s method default parameter feature to accomplish java-like method overloading. A full explanation of how and why this feature is used, as well as a pothole in its use.

Kotlin’s easy method overloading

2.1 How does Kotlin simplify method overloading?

In Java, we can define multiple methods with the same name in the same class, just make sure that each method has a different parameter type or number of parameters. This is Java method overloading.

class Hello {

    public static void hello(a) {

        System.out.println("Hello, world!");

    }



    public static void hello(String name) {

        System.out.println("Hello, "+ name +"!");

    }



    public static void hello(String name, int age) {

        if (age > 0) {

            System.out.println("Hello, "+ name + "(" +age +")!");

        } else {

            System.out.println("Hello, "+ name +"!");

        }

    }

}

Copy the code

In this example, we define three hello() methods with the same name, each with different logical details.

In Kotlin, because it supports in the same method, passing “? Flag nullable arguments and give their default values by ‘=’. Then these three methods can be softened into one method in Kotlin.

object HelloDemo{

    fun hello(name: String = "world", age: Int = 0) {

        if (age > 0) {

            System.out.println("Hello, ${name}(${age})!");

        } else {

            System.out.println("Hello, ${name}!");

        }

    }

}

Copy the code

Called in the Kotlin class, the same effect as the previous Java implementation.

HelloDemo.hello()

HelloDemo.hello("Fragrant ink shadow")

HelloDemo.hello("Fragrant ink shadow".16)

Copy the code

But this method, declared by the Kotlin method parameter default property, is a little different when used in Java classes. Because the HelloDemo class is declared as Object, you need INSTANCE to call its methods in Java.

HelloDemo.INSTANCE.hello("Fragrant ink shadow".16);

Copy the code

It’s easy to call Hello () in Kotlin and optionally ignore arguments, but when used in Java, parameter assignments must be fully explicit.

This is how method declarations with parameter defaults are used in Kotlin and Java, respectively. Let’s see how this works.

2.2 How Kotlin method parameters specify default values

Kotlin’s code can run in a Java virtual machine because it is compiled into Java bytecode, which is recognized by the virtual machine. So we use two conversions (Show Kotlin Bytecode + Decompile) to get the corresponding Java code generated by Kotlin.

public final void hello(@NotNull String name, int age) {

  Intrinsics.checkParameterIsNotNull(name, "name");

  if (age > 0) {

     System.out.println("Hello, " + name + '(' + age + ")!");

  } else {

     System.out.println("Hello, " + name + '! ');

  }

}



// $FF: synthetic method

public static void hello$default(HelloDemo var0, String var1, int var2, int var3, Object var4) {

  if ((var3 & 1) != 0) {

     var1 = "world";

  }



  if ((var3 & 2) != 0) {

     var2 = 0;

  }

  var0.hello(var1, var2);

}

Copy the code

Here a hello() method is generated, along with a synthetic method, hello$default, which handles default arguments. Calling the hello() method in Kotlin selectively replaces it with the synthetic hello() method at compile time.

/ / Kotlin calls

HelloDemo.hello()

HelloDemo.hello("Fragrant ink shadow")

HelloDemo.hello("Fragrant ink shadow".16)



// Compiled Java code

HelloDemo.hello$default(HelloDemo.INSTANCE, (String)null.0.3, (Object)null);

HelloDemo.hello$default(HelloDemo.INSTANCE, "Fragrant ink shadow".0.2, (Object)null);

HelloDemo.INSTANCE.hello("Fragrant ink shadow".16);

Copy the code

Notice at the end of the example, when you overload hello(name,age), it’s actually the same as a Java call, so there’s nothing to say about that.

This is how Kotlin’s methods are overloaded by specifying default parameters, eliminating multiple method overloading codes.

After understanding the principle, it does reduce the amount of code we write, but are there scenarios where we need to explicitly overload these methods? Naturally there is, such as when customizing a View.

Customizable View meets Kotlin

3.1 Constructors are methods

To return to the aforementioned Google developers “the practical Kotlin build the Android | Kotlin migration guide” series of articles, for example is inappropriate.

It uses the word View in this example, and it overloads several methods that are View constructors, and we’re going to be dealing with these three methods a lot when we’re customizing a View.

But the Google engineer’s example here is very misleading. In fact, if you are customizing a View, you will get an error.

For example, we define a DemoView that inherits from EditView.

class DemoView(

        context: Context, 

        attrs: AttributeSet? = null.

        defStyleAttr: Int = 0

) : EditText(context, attrs, defStyleAttr) {

}

Copy the code

This custom DemoView, when used in AN XML layout, will compile without error, but when run, you will get a NoSuchMethodException.

Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]

Copy the code

What’s the problem?

When LayoutInflater creates a control, the DemoView(Context, AttributeSet) overloaded method is not found, so an error is reported.

It’s easy to understand why Kotlin is using methods with default values. In fact, Kotlin will eventually compile and generate a composite method to handle the default values of methods. Unlike Java method overloading, this method does not have multiple method overloading.

So understand that Kotlin’s method specifying default parameters is not equivalent to Java method overloading. It can only be said that they are similar in some cases.

3.2 using the @ JvmOverloads

So back to the question here, how do you get Kotlin to actually generate overloaded methods at compile time for custom views or other scenarios that require Java method overloading?

So that’s where the @JvmOverloads come in.

When Kotlin uses a method with a default value, the @jvMoverloads annotation is added, meaning that multiple overloaded methods of the method are kept and exposed at compile time.

In fact, when we customize the View, AS gives us enough hints to automatically generate the @jvMoverloads constructor.

The code that AS helped us complete is AS follows:

class DemoView @JvmOverloads constructor(

        context: Context, 

        attrs: AttributeSet? = null.

        defStyleAttr: Int = 0

) : AppCompatEditText(context, attrs, defStyleAttr) {

}

Copy the code

Check the compiled code with ‘Kotlin Bytecode + Decompile’ to verify @jvmoverloads.

@JvmOverloads

public DemoView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

  Intrinsics.checkParameterIsNotNull(context, "context");

  super(context, attrs, defStyleAttr);

}



// $FF: synthetic method

public DemoView(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 DemoView(@NotNull Context context, @Nullable AttributeSet attrs) {

  this(context, attrs, 0.4, (DefaultConstructorMarker)null);

}



@JvmOverloads

public DemoView(@NotNull Context context) {

  this(context, (AttributeSet)null.0.6, (DefaultConstructorMarker)null);

}

Copy the code

As you can see, @jvMoverloads will generate the overloaded method as expected, leaving the compositing method in place to meet the default parameter requirement when used in Kotlin.

Did you think this was the end of it? No, if you generate the code exactly AS the AS tells you when you customize the View, it won’t crash, but you’ll get some unknown error.

3.3 Do not generate code directly with AS in View

When you customize a View, rely on the AS prompt to generate code and encounter some unknown errors. For example, in the example in this article, we want to implement a subclass of EditView and generate the code with the AS hint.

What could go wrong?

In the EditView scenario, you’ll notice that the focus is gone and the soft keyboard doesn’t pop up when you click.

So why is this a problem?

The reason is that AS handles the default values of the parameters in the automatically generated code.

When generating overloaded methods through AS when customizing a View, it treats parameter defaults like this.

  1. Object encountered, default value null.
  2. When an underlying data type is encountered, the default value is the default value for the underlying data type. For example, Int is 0 and Boolean is false.

In this scenario, the defStyleAttr parameter is of type Int, so the default value will be 0, but that’s not what we want.

In Android, when a View is laid out using an XML file, it calls a two-parameter Context Context (AttributeSet Attrs) constructor, and it calls a three-parameter constructor internally, And pass a default defStyleAttr, which is not 0.

Now that we’ve found the problem, it’s easy to solve. Let’s see how the custom View’s parent class implements the two-parameter constructor, passing defStyleArrt as the default.

So let’s look at the implementation in AppCompatEditText.

public AppCompatEditText(Context context, 

                         AttributeSet attrs)
 
{

    this(context, attrs, R.attr.editTextStyle);

}

Copy the code

Modify the default value of defStyleAttr in DemoView.

class DemoView @JvmOverloads constructor(

        context: Context,

        attrs: AttributeSet? = null.

        defStyleAttr: Int = R.attr.editTextStyle

) : AppCompatEditText(context, attrs, defStyleAttr) {

}

Copy the code

At this point, the constructor overload problem of using default arguments in custom Views has also been solved.

In the case of custom views, you could of course override multiple constructor methods to achieve a similar effect, but now that you know how it works, feel free to use it.

Four. Summary moment

Here’s how Kotlin uses default parameters to reduce method overloading code, and what to look for.

Understanding how this works and what to pay attention to can help us make better use of Kotlin’s features. Finally, we summarize the knowledge points of this article:

  1. Kotlin can accomplish a Java equivalent of “method overloading” by specifying default values for a method’s parameters.
  2. If you want to preserve Java’s overloaded methods, use@JvmOverloadsAnnotation tag, which automatically generates all overloaded methods for the method.
  3. When you customize a View, you need to specify parametersdefStyleAttrShould not be 0.

That’s all for today. Do you have any questions about the content of this article? Welcome to comment.

Did this article help you? Message, forward, favorites is the biggest support, thank you!


“Growth”, will get the learning materials I prepared.