简述: 今天带来的是Kotlin浅谈系列第三弹,这讲来聊下函数调用和函数重载问题,看到标题就知道Kotlin在函数调用方面有优于Java的地方。先抛出如下几个坑(估计曾经的你踩过...),看咱们怎么去一步步填坑,从中你会体验Kotlin这门语言魅力。java
咱们尝试回忆一下在用Java开发程序的过程当中,常常会去调用一些方法,有的人设计方法的参数有不少,并且参数命名有时候还不太规范(不能达到见名知意),而且设计的时候几个相同的参数类型仍是挨着的。这个实际上给调用者带来了很大的困扰和麻烦,谨慎的程序猿会定位到这个函数定义的地方,大概看了下参数的调用的顺序以及每一个参数的函数。特别当这个方法被打包成一个lib的时候,查看就比较麻烦。并且相同类型参数挨着很容易对应错。咱们来看下这个例子(如今需求是: 给每一个集合中的元素按照前缀、分割符、后缀拼接打印)app
//张三设计的函数接口顺序(调用者必须按照这个顺序传)
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();
}
//李四设计的函数接口顺序(调用者必须按照这个顺序传)
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();
}
//王二设计的函数接口顺序(调用者必须按照这个顺序传)
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();
}
//假如如今叫你修改一下,拼接串前缀或分隔符,仅从外部调用是没法知道哪一个参数是前缀、分隔符、后缀
public static void main(String[] args) {
//后面传入的三个字符串顺序很容易传错,而且外部调用者若是不看具体函数定义根本很难知道每一个字符串参数的含义,特别公司中一些远古代码,可能还得打成库的代码,点进去看实现确定蛋疼。
//调用张三接口
System.out.println(joinToString(Arrays.asList(new Integer[]{1, 3, 5, 7, 9}), "<", ",", ">"));
//调用李四接口
System.out.println(joinToString(Arrays.asList(new Integer[]{1, 3, 5, 7, 9}), ",", "<", ">"));
//调用王二接口
System.out.println(joinToString(Arrays.asList(new Integer[]{1, 3, 5, 7, 9}), "<", ">", ","));
}
复制代码
然而针对以上问题,细心的程序猿早就发现,咱们AndroidStudio3.0的版本给咱们作了个很好的优化提示,可是在3.0以前是没有这个提示的。如图框架
AndroidStudio工具开发商jetBrains实际上把这个提示是直接融入了他们开发的Kotlin语言中,他们尽可能让在语法的层面上少犯错误,少走弯路,更加注重于代码自己的实现;让你直接在语法的层面上就更加明确,减小疑惑性。ide
针对以上遇到的问题Kotlin能够很好的解决,在Kotlin函数中有这么一种参数叫作命名参数,它能容许在函数调用的地方指定函数名,这样就能很好使得调用地方的参数和函数定义参数一一对应起来,不会存在传递参数错乱问题。函数
//kotlin一个函数的接口知足以上三种顺序调用的接口,准确来讲是参数列表中任意参数顺序组合的调用
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>) {
//调用kotlin函数接口,知足张三接口设计需求,且调用更加明确
println(joinToString(nums = listOf(1, 3, 5, 7, 9), prex = "<", sep = ",", postfix = ">"))
//调用kotlin函数接口,知足李四接口设计需求,且调用更加明确
println(joinToString(nums = listOf(1, 3, 5, 7, 9), sep = ",", prex = "<", postfix = ">"))
//调用kotlin函数接口,知足王二接口设计需求,且调用更加明确
println(joinToString(nums = listOf(1, 3, 5, 7, 9), prex = "<", postfix = ">", sep = ","))
}
复制代码
AndroidStudio3.0带命名参数的函数调用高亮提示更加醒目工具
总结: 经过以上的例子,能够得出Kotlin在函数调用方面确实是比Java明确,也避免咱们去踩一些没必要要的坑,没有对比就没有伤害,相比Java你是否以为Kotlin更加适合你呢。post
不管是在Java或者C++中都有函数重载一说,函数重载目的为了针对不一样功能业务需求,而后暴露不一样参数的接口,包括参数列表个数,参数类型,参数顺序。也就是说几乎每一个不一样需求都得一个函数来对应,随着之后的扩展,这个类中的相同名字函数会堆成山,并且每一个函数之间又存在层级调用,函数与函数之间的参数列表差异有时候也是细微的,因此在调用方也会感受很疑惑,代码提示发现有七八相同的方法。举个例子(Android图片加载框架咱们都习惯于再次封装一次,以便调用方便)优化
//注意:这是我早期写出来kotlin代码(很丑陋),虽然这个看起来是kotlin代码,可是并无脱离Java语言的思想
//束缚,也没有利用起kotlin的新特性,这个封装彻底能够看作是直接从java代码翻译过来的kotlin代码。
fun ImageView.loadUrl(url: String) {//ImageView.loadUrl这个属于扩展函数,后期会介绍,暂时能够先忽略
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()
}
//调用的地方
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)
//以上的代码,相信不少人在Java中看到有不少吧,先不说我的写的,就拿官方库中的Thread类的构造器方法就有
//七八个。说明函数重载每每在符合需求接口扩展的时候,也在渐渐埋下了坑。不说别的就拿这个类来讲,即便直
//接看函数定义,你也得花时间去理清里面的调用关系,而后才能放心去使用。并且这样函数之后维护起来特别麻烦。
复制代码
针对以上例子的那么重载方法,实际上交给kotlin只须要一个方法就能解决实现,而且调用的时候很是方便。实际上在Kotlin中还存在一种函数参数叫作默认值参数。它就能够解决函数重载问题,而且它在调用的地方结合咱们上面所讲的命名参数一块儿使用会很是方便和简单。ui
//学完命名参数和默认值参数函数,当即重构后的样子
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()
}
}
//调用的地方
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)
复制代码
总结: 在Kotlin中,当调用一个Kotlin定义的函数时,能够显示地标明一些参数的名称,并且能够打乱顺序参数调用顺序,由于能够经过参数名称就能惟必定位具体对应参数。经过以上代码发现kotlin的默认值函数完美解决函数重载问题,而命名函数解决了函数调用问题,而且实现任意顺序指定参数名调用函数的参数。this
因为在Java中是没有默认值参数的概念,当咱们须要从Java中调用Kotlin中的默认值重载函数的时候,必须显示的指定全部参数值。可是这个绝对不是咱们想要,不然Kotlin就失去了重载的意义了不能和Java彻底互操做。因此在Kotlin给出了另外一个方案就是使用@JvmOverloads注解这样就会自动生成多个重载方法供Java调用。能够经过Kotlin代码来看下如下例子
@JvmOverloads
fun <T> joinString( collection: Collection<T> = listOf(),
separator: String = ",",
prefix: String = "",
postfix: String = ""
): String {
return collection.joinToString(separator, prefix, postfix)
}
//调用的地方
fun main(args: Array<String>) {
//函数使用命名参数能够提升代码可读性
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)))
}
复制代码
在Kotlin中参数的默认值是被编译到被调用的函数中的,而不是调用的地方,因此改变了默认值后须要从新编译这个函数。咱们能够从以下反编译代码能够看出Kotlin是把默认值编译进入了函数内部的。
// $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();//默认值空集合
}
if((var4 & 2) != 0) {
var1 = ",";//默认值分隔符“,”
}
if((var4 & 4) != 0) {
var2 = "";//默认前缀
}
if((var4 & 8) != 0) {
var3 = "";//默认后缀
}
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() {
return joinString$default((Collection)null, (String)null, (String)null, (String)null, 15, (Object)null);
}
复制代码
注意: 不能 在Kotlin中函数使用命名参数即便在Java重载了不少构造器方法或者普通方法,在Kotlin中调用Java中的方法是不能使用命名参数的,无论你是JDK中的函数或者是Android框架中的函数都是不容许使用命名参数的。
到这里咱们是否是发如今Kotlin确实让咱们的函数调用变得更加简单,明确呢,赶快试试吧
欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不按期翻译一篇Kotlin国外技术文章。若是你也喜欢Kotlin,欢迎加入咱们~~~