在Java項目中,多多少少都存在以Utils結尾的Java類。其內部並沒有任何狀態和實例函數,只有一堆與該名稱相關的靜態屬性或靜態方法。該類只是做爲一種容器存儲着靜態屬性和靜態方法。java
Kotlin認爲,根本不須要建立這些無心義的類。能夠直接將函數放在代碼文件的頂層,不用附屬於任何一個類。android
在com.daqi包中的daqi.kt文件中定義頂層函數joinToString()bash
package com.daqi
@JvmOverloads
fun <T> joinToString(collection: Collection<T>,
separator:String = ",",
prefix:String = "",
postfix:String = ""):String{
val result = StringBuilder(prefix)
for ((index,element) in collection.withIndex()){
if (index > 0)
result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
複製代碼
在Kotlin中,頂層函數屬於包內成員,包內能夠直接使用,包外只須要import該頂層函數,便可使用。app
Kotlin和Java具備很強互操做性,若是讓Java調用頂層函數該怎麼調用呢?先看一下頂層函數編譯成Java是什麼樣的:函數
public final class DaqiKt {
@NotNull
public static final String joinToString(Collection collection, String separator,
String prefix,String postfix) {
}
}
複製代碼
編譯器將頂層函數所在的文件名daqi.kt做爲類名DaqiKt,生成對應的類文件。該kt文件下的全部頂層函數都編譯爲這個類的靜態函數。post
so,若是在Java中調用Kotlin的頂層函數時,須要對其的文件名轉換爲對應的類名,再進行調用。ui
DaqiKt.joinToString(new ArrayList<>(),"",",","");
複製代碼
若是想規定kt文件轉換爲Java類時的類名,可使用@file:JvmName()註解進行修改。將其放在文件的開頭,位於包名以前:this
@file:JvmName("StringUtils")
package com.daqi
fun <T> joinToString(...){
...
}
複製代碼
就可使用特定的類名在Java中調用對應的頂層函數。spa
StringUtils.joinToString(new ArrayList<>(),"",",","");
複製代碼
既然有頂層方法,應該也有頂層屬性。和頂層函數同樣,屬性也能夠放在文件的頂層,不附屬與任何一個類。這種屬性叫頂層屬性。.net
@file:JvmName("StringUtils")
package com.daqi
val daqiField :String = "daqi"
複製代碼
頂層屬性和其餘任意屬性同樣,都提供對應的訪問器(val 變量提供getter,var 變量提供getter 和 setter)。也就是說,當Java訪問該頂層屬性時,經過訪問器進行訪問的。
StringUtils.getDaqiField();
複製代碼
經過反編譯查看其轉換爲Java的樣子:
@NotNull
private static final String daqiField = "daqi";
@NotNull
public static final String getDaqiField() {
return daqiField;
}
複製代碼
頂層屬性被定義爲私有的靜態對象,並配套了一個靜態訪問器方法。
若是須要定義public的靜態變量,能夠用const關鍵字修飾該變量。(僅適用於基礎數據類型和String類型的屬性)
在反編譯的文件中能夠看到,靜態屬性變成public,且沒有了具體的靜態訪問器。
//Kotlin
const val daqiField :String = "daqi"
//Java
public static final String daqiField = "daqi";
複製代碼
Kotlin能夠在無需繼承的狀況下擴展一個類的功能,而後像內部函數同樣直接經過對象進行調用。擴展函數這個特性能夠很平滑與現有Java代碼進行集成。
聲明一個擴展函數,須要用一個接收者類型也就是被擴展的類型來做爲他的前綴。而調用該擴展函數的對象,叫做接收者對象。接收者對象用this表示,this無關緊要。
fun String.lastChar():Char{
return this.get(this.length - 1)
}
//調用擴展函數
"daqi".lastChar()
複製代碼
在擴展函數中,能夠直接訪問被擴展類的方法和屬性。但擴展函數不容許你打破對象的封裝性,擴展函數不能訪問private和 protected的成員。具體什麼意思呢,先定義一個java類:
public class daqiJava {
private String str = "";
public String name = "";
public void daqi(){
}
private void daqi(String name){
}
}
複製代碼
對其該類進行擴展:
public的方法能夠正常訪問,但凡用private 或 protected修飾的屬性或方法,沒法在擴展函數中被調用。
回到Kotlin和Java交互性的問題,Java如何調用擴展函數的呢?這時候又要一波反編譯:
public static final void extensionMethod(daqiJava $receiver,String string) {
}
複製代碼
擴展函數daqiJava#extensionMethod()被轉換爲一個相同名稱的靜態函數。函數第一個參數變成接受者類型,後面纔是原函數的參數列表。也就是說Java調用擴展函數時,須要先傳入對應的接收者對象,再傳入該擴展函數的參數。
在JVM語言的多態中,被重寫方法的調用依據其調用對象的實際類型進行調用。但擴展函數是靜態分發的,即意味着擴展函數是由其所在表達式中的調用者的類型來決定的。
咱們都知道Button是View的子類,同時爲View和Button定義名爲daqi的擴展函數。
val view:View = Button()
view.daqi()
複製代碼
此時調用的是View的擴展函數,即便它實質是一個Button對象。由於擴展函數所在的表達式中,view是View類型,而不是Button類型。
擴展函數與頂層函數相似,在Java層進行調用時,依據其所在的文件名做爲類名,其做爲靜態函數,存儲在該類中。(也支持@file:JvmName("")進行)
在擴展函數中,除了能夠調用接收者類型的成員函數和成員屬性外,還能夠調用該類的擴展函數。
若是一個類的成員函數與擴展函數擁有相同的方法簽名,成員函數會被優先使用。
擴展函數實際上是靜態函數,擴展函數不能被子類重寫。但子類仍能夠調用父類的擴展函數。
擴展屬性不能有初始化器,它們的行爲只能由顯式提供的 getters/setters 定義。由於沒有地方對它進行存儲,不可能給現有的Java對象實例添加額外的屬性。只是用屬性的語法對接受者類型進行擴展。
聲明一個擴展常量:
val String.lastChar:Char
get() = get(length - 1)
複製代碼
聲明一個擴展變量:
var StringBuffer.lastChar:Char
get() = get(length - 1)
set(value:Char){
this.setCharAt(length - 1,value)
}
複製代碼