簡述: 今天帶來的是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,歡迎加入咱們~~~