Description: This is the third in Kotlin’s series on function calls and function overloading. As you can see from the title, Kotlin has some advantages over Java when it comes to function calls. First throw the following pits (maybe you’ve stepped on them before…) See how we can fill in the gaps and experience the magic of Kotlin.

  • 1. What are the pitfalls of Java function calls?
  • 2. How does Kotlin solve the function call pit?
  • 3. What are the pitfalls of Function overloading in Java?
  • 4. How does Kotlin solve the problem of function overloading?
  • 5. What should be noted about overloaded functions when Java and Kotlin call each other?

First, Java function calls exist problems

Let’s try to remember when we were developing programs in Java, we would often call some methods, and some people would design methods with many parameters, and sometimes the parameter names were not very formal (not to be known by name), and the same parameter types were designed next to each other. This actually causes a lot of trouble for the caller. The prudent programmer will locate the function definition and look at the order in which the arguments are called and the function for each argument. Especially if the method is packaged as a lib, viewing can be a bit cumbersome. And arguments of the same type next to each other are easily mismatched. Let’s take a look at this example (now the requirement is: print the elements of each set with prefix, separator, suffix concatenation)

    // The interface order of the functions designed by Zhang SAN (callers must pass this order)
    static String joinToString(List<Integer> nums, String prex, String sep, String postfix) {
        StringBuilder builder = new StringBuilder(prex);
        for (int i = 0; i < nums.size(); i++) {
            builder.append(i);
            if (i < nums.size() - 1) {
                builder.append(sep);
            }
        }
        builder.append(postfix);
        return builder.toString();
    }

    // Function interface order designed by Li Si (callers must pass in this order)
    static String joinToString(List<Integer> nums, String sep, String prex, String postfix) {
        StringBuilder builder = new StringBuilder(prex);
        for (int i = 0; i < nums.size(); i++) {
            builder.append(i);
            if (i < nums.size() - 1) {
                builder.append(sep);
            }
        }
        builder.append(postfix);
        return builder.toString();
    }

    // The interface order of functions designed by Wang ii (callers must pass in this order)
    static String joinToString(List<Integer> nums, String prex, String postfix, String sep) {
        StringBuilder builder = new StringBuilder(prex);
        for (int i = 0; i < nums.size(); i++) {
            builder.append(i);
            if (i < nums.size() - 1) {
                builder.append(sep);
            }
        }
        builder.append(postfix);
        return builder.toString();
    }  
    
    // If you are asked to modify the concatenation string prefix or delimiter, only from the external call can not know which argument is prefix, delimiter, suffix
    public static void main(String[] args) {
    // The sequence of the three strings passed in behind is easy to pass the wrong, and external callers can hardly know the meaning of each string parameter without looking at the specific function definition, especially some ancient code in the company, may also have to call into the library code, click to see the implementation of certain egg pain.
       // Call an interface
        System.out.println(joinToString(Arrays.asList(new Integer[]{1.3.5.7.9}), "<".",".">"));
       // Call the li si interface
        System.out.println(joinToString(Arrays.asList(new Integer[]{1.3.5.7.9}), ","."<".">"));
      // Call the king 2 interface
        System.out.println(joinToString(Arrays.asList(new Integer[]{1.3.5.7.9}), "<".">".","));    
    }
Copy the code

However, in view of the above problems, careful program monkey has long found that our version of AndroidStudio3.0 has given us a good optimization tip, but there is no such tip before 3.0. As shown in figure

AndroidStudio developer jetBrains has actually incorporated this tip directly into their Kotlin language. They try to make fewer mistakes in the syntax, and focus more on the implementation of the code itself. Let you directly on the level of grammar is more clear, reduce confusion.

How does Kotlin solve the function call pit

Kotlin can solve this problem very well. In the Kotlin function, there is a parameter called named parameter, which allows the function name to be specified at the place where the function is called. In this way, the parameters at the place where the function is called can be matched with the parameters defined by the function.

// Kotlin the interface of a function that meets the above three sequential calls, specifically any sequential combination of arguments in the argument list
fun joinToString(nums: List<Int>, prex: String, sep: String, postfix: String): String {
    val builder = StringBuilder(prex)
    for (i in nums.indices) {
        builder.append(i)
        if (i < nums.size - 1) {
            builder.append(sep)
        }
    }
    builder.append(postfix)
    return builder.toString()
}
fun main(args: Array<String>) {
    // Call kotlin function interface, meet the three interface design requirements, and call more explicit
    println(joinToString(nums = listOf(1.3.5.7.9), prex = "<", sep = ",", postfix = ">"))
    // Call kotlin function interface, meet the interface design requirements, and call more explicit
    println(joinToString(nums = listOf(1.3.5.7.9), sep = ",", prex = "<", postfix = ">"))
    // Call kotlin function interface, meet the design requirements of king ii interface, and call more explicit
    println(joinToString(nums = listOf(1.3.5.7.9), prex = "<", postfix = ">", sep = ","))}Copy the code

AndroidStudio3.0 highlights function calls with named parameters more prominently

Conclusion: From the above examples, it can be concluded that Kotlin is more explicit than Java in terms of function calls, and it avoids us to step on some unnecessary pit, no comparison, no harm, do you think Kotlin is more suitable for you than Java?

3. What kind of pit does Java have in function overloading

Whether in Java or C++, there is a term for function overloading. The purpose of function overloading is to meet different functional and business requirements, and then expose the interface of different parameters, including the number of parameter lists, parameter types, and parameter order. That is to say, almost every have a function to correspond to different demand, along with the expansion of after, this kind of the same name function will mountain, and again there is a level between each function call, between function and function argument list differences are subtle, sometimes in the caller will also feel very confused, so code hinting found to have seven or eight of the same method. For example (The Android image-loading framework is something we all like to wrap again to make it easier to call)

// Note: this is my early kotlin code (ugly). Although this looks like Kotlin code, it is not out of the mind of the Java language
This wrapper can be viewed as a direct translation of Kotlin code from Java code.
fun ImageView.loadUrl(url: String) {// imageView. loadUrl is an extension function that can be ignored for the time being
	loadUrl(Glide.with(context), url)
}

fun ImageView.loadUrl(requestManager: RequestManager, url: String) {
	loadUrl(requestManager, url, false)}fun ImageView.loadUrl(requestManager: RequestManager, url: String, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(url).crossFade(isCrossFade).start()
}

fun ImageView.loadUrl(urls: List<String>) {
	loadUrl(Glide.with(context), urls)
}

fun ImageView.loadUrl(requestManager: RequestManager, urls: List<String>) {
	loadUrl(requestManager, urls, false)}fun ImageView.loadUrl(requestManager: RequestManager, urls: List<String>, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(urls).crossFade(isCrossFade).start()
}

fun ImageView.loadRoundUrl(url: String) {
	loadRoundUrl(Glide.with(context), url)
}

fun ImageView.loadRoundUrl(requestManager: RequestManager, url: String) {
	loadRoundUrl(requestManager, url, false)}fun ImageView.loadRoundUrl(requestManager: RequestManager, url: String, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(url).crossFade(isCrossFade).round().start()
}

fun ImageView.loadRoundUrl(urls: List<String>) {
	loadRoundUrl(Glide.with(context), urls)
}

fun ImageView.loadRoundUrl(requestManager: RequestManager, urls: List<String>) {
	loadRoundUrl(requestManager, urls, false)}fun ImageView.loadRoundUrl(requestManager: RequestManager, urls: List<String>, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(urls).crossFade(isCrossFade).round().start()
}
// Where to call
activity.home_iv_top_banner.loadUrl(bannerUrl)
activity.home_iv_top_portrait.loadUrl(portraitUrls)
activity.home_iv_top_avatar.loadRoundUrl(avatarUrl)
activity.home_iv_top_avatar.loadRoundUrl(avatarUrls)

// The constructor method of the Thread class is available in the official library
// Seven or eight. Function overloading is often in line with the requirements of interface extension, but also gradually buried in the pit. If nothing else take this class, even straight
// If you look at the function definition, you have to take the time to understand the relationship between the calls before you can use it. And such functions are particularly troublesome to maintain later.

Copy the code

How does Kotlin solve the problem of function overloading

The overloaded methods used in the above example are actually handled by Kotlin in a single method, and are very convenient to call. There’s actually a function argument in Kotlin called a default parameter. It solves the problem of function overloading, and it is very convenient and simple to use in conjunction with the named parameters we discussed above where the call is made.

// After learning named parameters and default parameter functions, immediately refactor the appearance
fun ImageView.loadUrl(requestManager: RequestManager = Glide.with(context)
					  , url: String = ""
					  , urls: List<String> = listOf(url)
					  , isRound: Boolean = false
					  , isCrossFade: Boolean = false) {
	if (isRound) {
		ImageLoader.newTask(requestManager).view(this).url(urls).round().crossFade(isCrossFade).start()
	} else {
		ImageLoader.newTask(requestManager).view(this).url(urls).crossFade(isCrossFade).start()
	}
}
// Where to call
activity.home_iv_top_banner.loadUrl(url = bannerUrl)
activity.home_iv_top_portrait.loadUrl(urls = portraitUrls)
activity.home_iv_top_avatar.loadUrl(url = avatarUrl, isRound = true)
activity.home_iv_top_avatar.loadUrl(urls = avatarUrls, isRound = true)
Copy the code

Conclusion: In Kotlin, when a function defined by Kotlin is called, the names of some arguments can be explicitly identified and the order of the arguments can be scrambled because the arguments can be uniquely identified by their names. The above code shows that Kotlin’s default functions solve the problem of function overloading perfectly, while the named functions solve the problem of function calling and implement the arbitrary order of parameter names to call the parameters of the function.

What should be noted when Java and Kotlin call each other when overloaded functions

5.1 Using the @jvMoverloads annotation to solve the problem of Java calling Kotlin overloaded functions

Since there is no concept of default parameters in Java, when we need to call Kotlin’s default overload function from Java, we must specify all parameter values. But this is definitely not what we want, otherwise Kotlin would lose the point of overloading and not be able to fully interoperate with Java. So another solution Kotlin came up with was to use the @jvMoverloads annotation so that multiple overloaded methods are automatically generated for Java to call. You can look at the following example with the Kotlin code

@JvmOverloads
fun <T> joinString(
        collection: Collection<T> = listOf(),
        separator: String = ",",
        prefix: String = "",
        postfix: String = ""
): String {
    return collection.joinToString(separator, prefix, postfix)
}
// Where to call
fun main(args: Array<String>) {
    // Functions use named arguments to improve code readability
    println(joinString(collection = listOf(1.2.3.4), separator = "%", prefix = "<", postfix = ">"))
    println(joinString(collection = listOf(1.2.3.4), separator = "%", prefix = "<", postfix = ">"))
    println(joinString(collection = listOf(1.2.3.4), prefix = "<", postfix = ">"))
    println(joinString(collection = listOf(1.2.3.4), separator = "!", prefix = "<"))
    println(joinString(collection = listOf(1.2.3.4), separator = "!", postfix = ">"))
    println(joinString(collection = listOf(1.2.3.4), separator = "!"))
    println(joinString(collection = listOf(1.2.3.4), prefix = "<"))
    println(joinString(collection = listOf(1.2.3.4), postfix = ">"))
    println(joinString(collection = listOf(1.2.3.4)))}Copy the code

The default values of arguments in Kotlin are compiled into the function being called, not where it is called, so changing the default values requires recompiling the function. We can see from the decompiled code below that Kotlin compiled the default values into the function.

  // $FF: synthetic method
  // $FF: bridge method
  @JvmOverloads
  @NotNull
  public static String joinString$default(Collection var0, String var1, String var2, String var3, int var4, Object var5) {
     if((var4 & 1) != 0) {
        var0 = (Collection)CollectionsKt.emptyList();// The default value is empty collection
     }

     if((var4 & 2) != 0) {
        var1 = ",";// Default separator ","
     }

     if((var4 & 4) != 0) {
        var2 = "";// Default prefix
     }

     if((var4 & 8) != 0) {
        var3 = "";// The default suffix
     }

     return joinString(var0, var1, var2, var3);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix) {
     return joinString$default(collection, separator, prefix, (String)null.8, (Object)null);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString(@NotNull Collection collection, @NotNull String separator) {
     return joinString$default(collection, separator, (String)null, (String)null.12, (Object)null);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString(@NotNull Collection collection) {
     return joinString$default(collection, (String)null, (String)null, (String)null.14, (Object)null);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString(a) {
     return joinString$default((Collection)null, (String)null, (String)null, (String)null.15, (Object)null);
  }
Copy the code

5.2 Can Kotlin call Java with named and Default parameters?

Note: You cannot use named arguments in Kotlin functions, even if Java overloads many constructor methods or ordinary methods. You cannot use named arguments in Kotlin when calling Java methods, whether you are JDK functions or Android framework functions.

Do we find that Kotlin really makes our function calls easier and more explicit? Try it out

Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~