簡述: 今天帶來的是Kotlin淺談系列的第四彈,此次主要聊下Kotlin獨有的新特性,Java不具有的。Kotlin是一種增長許多新功能的語言,容許編寫更簡潔易讀的代碼,這使得咱們的代碼更易於維護。例如使用頂層函數和屬性今後消除Java中的static、中綴表達式調用和解構聲明等。java
概念: 咱們知道在Java中有靜態函數和靜態屬性概念,它們通常做用就是爲了提供一個全局共享訪問區域和方法。咱們通常的習慣的寫法就是寫一個類包裹一些static修飾的方法,而後在外部訪問的直接利用類名.方法名訪問。apache
問題: 咱們都知道靜態函數內部是不包含狀態的,也就是所謂的純函數,它的輸入僅僅來自於它的參數列表,而它的輸出也僅僅依賴於它參數列表。咱們設想一下這樣開發情景,有時候咱們並不想利用實例對象來調用函數,因此咱們通常會往靜態函數容器類中添加靜態函數,如此反覆,這樣無疑是讓這個類容器膨脹。bash
解決: 在Kotlin中則認爲一個函數或方法有時候並非屬於任何一個類,它能夠獨立存在。因此在Kotlin中相似靜態函數和靜態屬性會去掉外層類的容器,一個函數或者屬性能夠直接定義在一個Kotlin文件的頂層中,在使用的地方只須要import這個函數或屬性便可。若是你的代碼還存在不少以"Util"後綴結尾的工具類,是時候去掉了。jvm
在Koltin中根本不須要去定義一些沒有意義包裹靜態函數的容器類,它們都被頂層文件給替代。咱們只須要定義一個Kotlin File,在裏面定義好一些函數(注意: 不須要static關鍵字)。那麼這些函數就能夠當作靜態函數來使用函數
建立一個頂層文件:工具
在頂層文件中定義一個函數:源碼分析
package com.mikyou.kotlin.top
import java.math.BigDecimal
/** * Created by mikyou on 2018/4/10. */
//這個頂層函數不屬於任何一個類,不須要類容器,不須要static關鍵字
fun formateFileSize(size: Double): String {
if (size < 0) {
return "0 KB"
}
val kBSize = size / 1024
if (kBSize < 1) {
return "$size B"
}
val mBSize = kBSize / 1024
if (mBSize < 1) {
return "${BigDecimal(kBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} KB"
}
val mGSize = mBSize / 1024
if (mGSize < 1) {
return "${BigDecimal(mBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} MB"
}
val mTSize = mGSize / 1024
if (mTSize < 1) {
return "${BigDecimal(mGSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} GB"
}
return "${BigDecimal(mTSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} TB"
}
//測試頂層函數,實際上Kotlin中main函數和Java不同,它能夠不存在任何類容器中,能夠直接定義在一個Kotlin 文件中
//另外一方面也解釋了Kotlin中的main函數不須要了static關鍵字,實際上它本身就是個頂層函數。
fun main(args: Array<String>) {
println("文件大小: ${formateFileSize(15582.0)}")
}
複製代碼
從以上代碼能夠看出定義一個頂層函數是否是很簡單,那麼問題來了這個formateFileSize函數定義在一個文件內部,在JVM中是怎麼執行的呢?請接着往下看...測試
經過以上例子咱們思考一下頂層函數在JVM中是怎麼運行的,若是你僅僅是在Kotlin中使用這些頂層函數,那麼能夠不用細究。可是若是你是Java和Kotlin混合開發模式,那麼你就有必要深刻內部原理。咱們都知道Kotlin和Java互操做性是很強的,因此就衍生出了一個問題:在Kotlin中定義的頂層函數,在Java能夠調用嗎?答案確定是能夠的。怎麼調用的,請接着看。ui
要想知道內部調用原理很簡單,咱們只須要把上面例子代碼反編譯成Java代碼就一目瞭然了。這裏科普一下反編譯Kotlin代碼步驟,由於這是查看Kotlin語法糖背後實質很好的方法。this
步驟一: 確認IDE安裝好了Kotlin Plugin
步驟二: 在IDE中打開你須要查看反編譯的代碼文件,而後打開頂部的"Tools",選擇"Kotlin",再選擇"Show Kotlin ByteCode"
步驟三: 左邊是Kotlin的源碼,右邊是Kotlin的ByteCode
步驟四: 點擊右側「Decompile」
package com.mikyou.kotlin.top;
import java.math.BigDecimal;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 9},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000\u001c\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0006\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\b\u0002\u001a\u000e\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003\u001a\u0019\u0010\u0004\u001a\u00020\u00052\f\u0010\u0006\u001a\b\u0012\u0004\u0012\u00020\u00010\u0007¢\u0006\u0002\u0010\b¨\u0006\t"},
d2 = {"formateFileSize", "", "size", "", "main", "", "args", "", "([Ljava/lang/String;)V", "production sources for module Function"}
)
public final class TopExtFileFormatKt {//通常以文件名+"Kt"後綴做爲容器類名
@NotNull
public static final String formateFileSize(double size) {//頂層函數反編譯成Java中靜態函數
if(size < (double)0) {
return "0 KB";
} else {
double kBSize = size / (double)1024;
if(kBSize < (double)1) {
return "" + size + " B";
} else {
double mBSize = kBSize / (double)1024;
if(mBSize < (double)1) {
return "" + (new BigDecimal(String.valueOf(kBSize))).setScale(1, 4).toPlainString() + " KB";
} else {
double mGSize = mBSize / (double)1024;
if(mGSize < (double)1) {
return "" + (new BigDecimal(String.valueOf(mBSize))).setScale(1, 4).toPlainString() + " MB";
} else {
double mTSize = mGSize / (double)1024;
return mTSize < (double)1?"" + (new BigDecimal(String.valueOf(mGSize))).setScale(1, 4).toPlainString() + " GB":"" + (new BigDecimal(String.valueOf(mTSize))).setScale(1, 4).toPlainString() + " TB";
}
}
}
}
}
public static final void main(@NotNull String[] args) {//頂層函數反編譯成Java中靜態函數
Intrinsics.checkParameterIsNotNull(args, "args");
String var1 = "文件大小: " + formateFileSize(15582.0D);
System.out.println(var1);
}
}
複製代碼
經過以上的代碼能夠總結出兩點內容:
想必到這裏你大概猜到了Java中如何調用Kotlin中的頂層函數了吧。調用方式很簡單,就是利用反編譯生成的類做爲靜態函數容器類直接調用對應的函數
package com.mikyou.kotlin.top;
/**
* Created by mikyou on 2018/4/10.
*/
public class TopExtTest {
public static void main(String[] args) {
System.out.println("文件大小: " + TopExtFileFormatKt.formateFileSize(1343553));// Java中調用Kotlin中定義頂層函數,通常是頂層文件名+"Kt"後綴做爲靜態函數的類名調用相應函數
}
}
複製代碼
Kotlin中的頂層函數反編譯成的Java中的容器類名通常是頂層文件名+「Kt」後綴做爲類名,可是也是能夠自定義的。也就是說頂層文件名和生成容器類名沒有必然的聯繫。經過Kotlin中的@file: JvmName("自定義生成類名")註解就能夠自動生成對應Java調用類名,注意須要放在文件頂部,在package聲明的前面
//經過@file: JvmName("FileFormatUtil")註解,將生成的類名修改成FileFormatUtil,而且調用的時候直接調用FileFormatUtil.formateFileSize()便可
//放在文件頂部,在package聲明的前面
@file: JvmName("FileFormatUtil")
package com.mikyou.kotlin.top
import java.math.BigDecimal
/** * Created by mikyou on 2018/4/10. */
//這個頂層函數不屬於任何一個類,不須要類容器,不須要static關鍵字
fun formateFileSize(size: Double): String {
if (size < 0) {
return "0 KB"
}
val kBSize = size / 1024
if (kBSize < 1) {
return "$size B"
}
val mBSize = kBSize / 1024
if (mBSize < 1) {
return "${BigDecimal(kBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} KB"
}
val mGSize = mBSize / 1024
if (mGSize < 1) {
return "${BigDecimal(mBSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} MB"
}
val mTSize = mGSize / 1024
if (mTSize < 1) {
return "${BigDecimal(mGSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} GB"
}
return "${BigDecimal(mTSize.toString()).setScale(1, BigDecimal.ROUND_HALF_UP).toPlainString()} TB"
}
//測試頂層函數,實際上Kotlin中main函數和Java不同,它能夠不存在任何類容器中,能夠直接定義在一個Kotlin 文件中
//另外一方面也解釋了Kotlin中的main函數不須要了static關鍵字,實際上它本身就是個頂層函數。
fun main(args: Array<String>) {
println("文件大小: ${formateFileSize(15582.0)}")
}
複製代碼
而後咱們再來一塊兒看看反編譯成Java代碼變成什麼樣了
package com.mikyou.kotlin.top;
import java.math.BigDecimal;
import kotlin.Metadata;
import kotlin.jvm.JvmName;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 9},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000\u001c\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0006\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\b\u0002\u001a\u000e\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003\u001a\u0019\u0010\u0004\u001a\u00020\u00052\f\u0010\u0006\u001a\b\u0012\u0004\u0012\u00020\u00010\u0007¢\u0006\u0002\u0010\b¨\u0006\t"},
d2 = {"formateFileSize", "", "size", "", "main", "", "args", "", "([Ljava/lang/String;)V", "production sources for module Function"}
)
@JvmName(//注意這裏多了註解
name = "FileFormatUtil"
)
public final class FileFormatUtil {//這裏生成的類名就是註解中自定義生成的類名了
@NotNull
public static final String formateFileSize(double size) {
if(size < (double)0) {
return "0 KB";
} else {
double kBSize = size / (double)1024;
if(kBSize < (double)1) {
return "" + size + " B";
} else {
double mBSize = kBSize / (double)1024;
if(mBSize < (double)1) {
return "" + (new BigDecimal(String.valueOf(kBSize))).setScale(1, 4).toPlainString() + " KB";
} else {
double mGSize = mBSize / (double)1024;
if(mGSize < (double)1) {
return "" + (new BigDecimal(String.valueOf(mBSize))).setScale(1, 4).toPlainString() + " MB";
} else {
double mTSize = mGSize / (double)1024;
return mTSize < (double)1?"" + (new BigDecimal(String.valueOf(mGSize))).setScale(1, 4).toPlainString() + " GB":"" + (new BigDecimal(String.valueOf(mTSize))).setScale(1, 4).toPlainString() + " TB";
}
}
}
}
}
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
String var1 = "文件大小: " + formateFileSize(15582.0D);
System.out.println(var1);
}
}
複製代碼
這樣Java調用自定義類名頂層函數就更加天然,通常建議使用註解修改類名,這樣在Java層調用仍是咱們習慣工具類的命名,徹底沒法感知這個函數是來自Java中仍是Kotlin中定義的,作到徹底透明。
package com.mikyou.kotlin.top;
/** * Created by mikyou on 2018/4/10. */
public class TopExtTest {
public static void main(String[] args) {
System.out.println("文件大小: " + FileFormatUtil.formateFileSize(1343553));// Java中調用Kotlin中定義頂層函數,若是自定義生成類名,直接用定義類名調用。
}
}
複製代碼
中綴調用看起來是很高級概念,實際上原理很簡單,可是它在語法層面簡化了不少,更方便以及更容易理解,讓咱們寫代碼會更加接近天然語言。我我的理解中綴調用實際上就是把原來只有一個參數的函數調用簡化成兩個操做直接使用相似中綴運算符調用,省略了類名或者對象名+"."+函數名調用方式。廢話很少說,直接發波糖
package com.mikyou.kotlin.infix
/** * Created by mikyou on 2018/4/10. */
//普通利用Pair()初始化一個map
fun main(args: Array<String>) {
val map = mapOf(Pair(1, "A"), Pair(2, "B"), Pair(3, "C"))
map.forEach { key, value ->
println("key: $key value:$value")
}
}
//利用to函數初始化一個map
fun main(args: Array<String>) {
val map = mapOf(1.to("A"), 2.to("B"), 3.to("C"))
map.forEach { key, value ->
println("key: $key value:$value")
}
}
//利用to函數中綴調用初始化一個map
fun main(args: Array<String>) {
val map = mapOf(1 to "A", 2 to "B", 3 to "C")//to實際上一個返回Pair對象的函數,不是屬於map結構內部的運算符,可是to在語法層面使用很像中綴運算符調用
map.forEach { key, value ->
println("key: $key value:$value")
}
}
複製代碼
//普通使用字符串對比調用StringUtils.equals(strA, strB)
fun main(args: Array<String>) {
val strA = "A"
val strB = "B"
if (StringUtils.equals(strA, strB)) {//這裏對比字符串是了apache中的StringUtils
println("str is the same")
} else {
println("str is the different")
}
}
//利用中綴調用sameAs對比兩個字符串
fun main(args: Array<String>) {
val strA = "A"
val strB = "B"
if (strA sameAs strB) {//中綴調用 sameAs
println("str is the same")
} else {
println("str is the different")
}
}
複製代碼
//普通調用集合contains方法判斷元素是否在集合中
fun main(args: Array<String>) {
val list = listOf(1, 3, 5, 7, 9)
val element = 2
if (list.contains(element)) {
println("element: $element is into list")
} else {
println("element: $element is not into list")
}
}
//利用中綴調用into判斷元素是否在集合中
fun main(args: Array<String>) {
val list = listOf(1, 3, 5, 7, 9)
val element = 2
if (element into list) {//中綴調用,這樣的寫法,會更加接近咱們天然語言的表達,更容易理解
println("element: $element is into list")
} else {
println("element: $element is not into list")
}
}
複製代碼
中綴調用使用很是簡單,準確來講它使用相似加減乘除運算操做符的使用。調用結構: A (中綴函數名) B 例如: element into list
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
複製代碼
分析: 使用infix關鍵字修飾的函數,傳入A,B兩個泛型對象,「A.to(B)」結構,是一種特殊結構暫時把它叫作帶接收者的結構,以致於後面的this就是指代A,而且函數的參數只有一個,返回的是一個Pair對象,this指代A,that就是傳入的B類型對象。
infix fun <T> T.into(other: Collection<T>): Boolean = other.contains(this)
複製代碼
分析: 使用infix關鍵字修飾的函數,泛型T對象元素是否存在於泛型T集合之中。「T.into(Collection<T>)」結構,實際也是一種帶接收者結構,this也就是指代了into函數前面調用T類型對象。
一、前面所講to, into,sameAs實際上就是函數調用,若是把infix關鍵字去掉,那麼也就純粹按照函數調用方式來。好比1.to("A"), element.into(list)等,只有加了中綴調用的關鍵字infix後,纔可使用簡單的中綴調用例如 1 to "A", element into list等
二、並非全部的函數都能寫成中綴調用,中綴調用首先必須知足一個條件就是函數的參數只有一個。而後再看這個函數的參與者是否是隻有兩個元素,這兩個元素能夠是兩個數,能夠是兩個對象,能夠是集合等。
解構聲明是把一個對象當作一組單獨的變量,有時候咱們把一個對象當作一組單獨的變量管理會變得更加簡單。注意: 支持解構聲明的對象的類必須是數據類(使用data關鍵字修飾的類),由於只有data class纔會生成對應的component()方法(這個會在後續中講解到),data class中的每一個屬性都會有對應的component()方法對應
package com.mikyou.kotlin.destruct
/** * Created by mikyou on 2018/4/10. */
data class Student(var name: String, var age: Int, var grade: Double)
複製代碼
package com.mikyou.kotlin.destruct
/** * Created by mikyou on 2018/4/10. */
fun main(args: Array<String>) {
val student = Student("mikyou", 18, 99.0)
val (name, age, grade) = student//將一個student對象解構成一組3個單獨的變量
println("my name is $name , I'm $age years old, I get $grade score")//解構後的3個變量能夠脫離對象,直接單獨使用
}
複製代碼
解構聲明實際上就是將對象中全部屬性,解構成一組屬性變量,並且這些變量能夠單獨使用,爲何能夠單獨使用,是由於每一個屬性值的得到最後都編譯成經過調用與之對應的component()方法,每一個component()方法對應着類中每一個屬性的值,而後在做用域定義各自屬性局部變量,這些局部變量存儲着各自對應屬性的值,因此看起來變量能夠單獨使用,實際上使用的是局部變量。以下反編譯成的Java代碼
package com.mikyou.kotlin.destruct;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 9},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000\u0014\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005¨\u0006\u0006"},
d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "production sources for module Function"}
)
public final class DestructTestKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Student student = new Student("mikyou", 18, 99.0D);
String name = student.component1();//對應的component1()方法,返回對應就是Student中name屬性,並賦值給建立局部變量name
int age = student.component2();//對應的component2()方法,返回對應就是Student中age屬性, 並賦值給建立局部變量age
double grade = student.component3();//對應的component3()方法,返回對應就是Student中屬性,並賦值給建立局部變量grade
String var6 = "my name is " + name + " , I'm " + age + " years old, I get " + grade + " score";
System.out.println(var6);//注意: 這裏單獨使用的name, age, grade其實是局部變量
}
}
複製代碼
下劃線_ 忽略name屬性例子
package com.mikyou.kotlin.destruct
/** * Created by mikyou on 2018/4/10. */
fun main(args: Array<String>) {
val student = Student("mikyou", 18, 99.0)
val (_, age, grade) = student//下劃線_ 忽略name屬性
println("I'm $age years old, I get $grade score")//解構後的3個變量能夠脫離對象,直接單獨使用
}
//下劃線_ 忽略name屬性 反編譯後Java代碼
package com.mikyou.kotlin.destruct;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata( mv = {1, 1, 9}, bv = {1, 0, 2}, k = 2, d1 = {"\u0000\u0014\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005¨\u0006\u0006"}, d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "production sources for module Function"} )
public final class DestructTestKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Student student = new Student("mikyou", 18, 99.0D);
int age = student.component2();//name會佔用component1()方法,可是沒有生成,因此age從component2()方法開始
double grade = student.component3();
String var5 = "I'm " + age + " years old, I get " + grade + " score";
System.out.println(var5);
}
}
複製代碼
直接不寫name屬性例子
package com.mikyou.kotlin.destruct
/** * Created by mikyou on 2018/4/10. */
fun main(args: Array<String>) {
val student = Student("mikyou", 18, 99.0)
val (age, grade) = student//直接不寫name屬性
println("I'm $age years old, I get $grade score")//解構後的3個變量能夠脫離對象,直接單獨使用
}
//直接不寫name屬性 反編譯後Java代碼
package com.mikyou.kotlin.destruct;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata( mv = {1, 1, 9}, bv = {1, 0, 2}, k = 2, d1 = {"\u0000\u0014\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005¨\u0006\u0006"}, d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "production sources for module Function"} )
public final class DestructTestKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Student student = new Student("mikyou", 18, 99.0D);
String age = student.component1();//直接不寫name,而後age從component
int grade = student.component2();
String var4 = "I'm " + age + " years old, I get " + grade + " score";
System.out.println(var4);
}
}
複製代碼
歡迎關注Kotlin開發者聯盟,這裏有最新Kotlin技術文章,每週會不按期翻譯一篇Kotlin國外技術文章。若是你也喜歡Kotlin,歡迎加入咱們~~~