淺談Kotlin語法篇之如何讓函式更好地呼叫(三)

極客熊貓發表於2018-04-02

簡述: 今天帶來的是Kotlin淺談系列第三彈,這講來聊下函式呼叫和函式過載問題,看到標題就知道Kotlin在函式呼叫方面有優於Java的地方。先丟擲以下幾個坑(估計曾經的你踩過...),看我們怎麼去一步步填坑,從中你會體驗Kotlin這門語言魅力。

  • 1、Java在函式呼叫方面存在怎樣的坑?
  • 2、Kotlin是怎樣去解決函式呼叫的坑?
  • 3、Java在函式過載方面存在怎樣的坑?
  • 4、Kotlin是怎樣去解決函式過載帶來的坑?
  • 5、Java與Kotlin互相呼叫時過載函式應該注意哪些問題?

一、Java函式呼叫存在的問題

我們嘗試回憶一下在用Java開發程式的過程中,經常會去呼叫一些方法,有的人設計方法的引數有很多,而且引數命名有時候還不太規範(不能達到見名知意),並且設計的時候幾個相同的引數型別還是挨著的。這個實際上給呼叫者帶來了很大的困擾和麻煩,謹慎的程式猿會定位到這個函式定義的地方,大概看了下引數的呼叫的順序以及每個引數的函式。特別當這個方法被打包成一個lib的時候,檢視就比較麻煩。而且相同型別引數挨著很容易對應錯。我們來看下這個例子(現在需求是: 給每個集合中的元素按照字首、分割符、字尾拼接列印)

    //張三設計的函式介面順序(呼叫者必須按照這個順序傳)
    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之前是沒有這個提示的。如圖

淺談Kotlin語法篇之如何讓函式更好地呼叫(三)

AndroidStudio工具開發商jetBrains實際上把這個提示是直接融入了他們開發的Kotlin語言中,他們儘量讓在語法的層面上少犯錯誤,少走彎路,更加註重於程式碼本身的實現;讓你直接在語法的層面上就更加明確,減少疑惑性。

二、Kotlin是怎樣去解決函式呼叫的坑

針對以上遇到的問題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語法篇之如何讓函式更好地呼叫(三)

總結: 通過以上的例子,可以得出Kotlin在函式呼叫方面確實是比Java明確,也避免我們去踩一些不必要的坑,沒有對比就沒有傷害,相比Java你是否覺得Kotlin更加適合你呢。

三、Java在函式過載方面存在怎樣的坑

無論是在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只需要一個方法就能解決實現,並且呼叫的時候非常方便。實際上在Kotlin中還存在一種函式引數叫做預設值引數。它就可以解決函式過載問題,並且它在呼叫的地方結合我們上面所講的命名引數一起使用會非常方便和簡單。

//學完命名引數和預設值引數函式,立即重構後的樣子
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的預設值函式完美解決函式過載問題,而命名函式解決了函式呼叫問題,並且實現任意順序指定引數名呼叫函式的引數。

五、Java與Kotlin互相呼叫時過載函式應該注意哪些問題

5.1 使用@JvmOverloads註解解決Java呼叫Kotlin過載函式問題

由於在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);
  }
複製程式碼

5.2 Kotlin呼叫Java能使用命名引數和預設值引數嗎?

注意: 不能 在Kotlin中函式使用命名引數即使在Java過載了很多構造器方法或者普通方法,在Kotlin中呼叫Java中的方法是不能使用命名引數的,不管你是JDK中的函式或者是Android框架中的函式都是不允許使用命名引數的。

到這裡我們是不是發現在Kotlin確實讓我們的函式呼叫變得更加簡單,明確呢,趕快試試吧

淺談Kotlin語法篇之如何讓函式更好地呼叫(三)

歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~

相關文章