Kotlin上手(一)
Kotlin上手(一)
系列筆記為學習極客時間張濤講解Kotlin的筆記。
本篇筆記主要學習了從Java過渡到Kotlin的幾個注意點
1.最基本語法:
1.1Kotlin的變數:
Kotlin的檔案是以.kt結尾,Kotlin的程式碼不需要;結尾
var表示變數,val表示不可變的變數(不是常量),Kotlin的變數名在前面,變數名寫在後面,中間冒號隔開,如果變數型別編譯器可以推斷出來,那麼可以不用寫明型別,同時Kotlin是具有空安全的,通過?和!!可以實現這種空安全的轉換。
例如:
val name1:String = null//錯誤,String型別不為空,不能賦值null val name1:String? = null//正確:String?型別可以為空(String與String?是兩種不同型別) val name2:String = name1//錯誤:name2是String型別不可以為空,而name1可能為空,不能賦值 val name2:String = name1!! //正確:後面的!!指明開發者已經確保了name1不可能是null,可以賦值(如果執行時出現null,丟擲相關的異常)
1.2kotlin的函式:
fun關鍵字指明Kotlin的函式變數,後面跟函式名,引數列表同變數寫法一樣,先寫變數名後寫變數型別,返回值最後寫。
fun functionName(str:String,num:Int):String{ if(num==0){ println(str) } return str }
2.Kotlin與Java的互相呼叫:
2.1.Java直接呼叫Kt函式
kotlin的函式可以直接寫在檔案裡面,不用寫在類裡面,但是編譯kt檔案之後,實際上最終還是編譯成public static修飾
//Util.kt fun pln(str:String){ println("$str") } //Main.java public static void main(args[] String){ UtilKt.echo("$args[0]"); }
其中$是Kotlin語法的轉義符號之一,可以直接在字串中插入變數名或者一段程式碼(用{}括起來),如果要在字串中使用$符號,則可以
println("${'$'}name")//output:$name
2.2.匿名類物件:
匿名內部類主要是針對那些不能建立例項的抽象類和介面而來的,在Kotlin中使用object 關鍵字建立匿名類物件:
首先看下Java和Kotlin的匿名類物件寫法上的區別:
//在Java中建立和使用匿名內部類 public interface AInterface { void sayMsg(String msg); void doMsg(String msg); } AInterface aInterface = new AInterface() { @Override public void sayMsg(String msg) { System.out.println("sayMsg"+msg); } @Override public void doMsg(String msg) { System.out.println("doMsg"+msg); } }; aInterface.sayMsg("B"); aInterface.doMsg("B"); //在Kotlin中建立和使用匿名內部類 interface KtInterface{ fun sayBye(msg:String?) fun doBye(msg:String?) } val bye = object: KtInterface{ override fun doBye(msg: String?) { println("doBye$msg") } override fun sayBye(msg: String?) { println("sayBye$msg") } } bye.sayBye(" bye") bye.doBye(" Bye")
如果要在Java中呼叫Kotlin中的匿名類物件,則如下:類名加.INSTANCE
而Kotlin的單例寫法之一,也正是直接用object關鍵字實現。
//在Test.kt檔案中 object Test{ fun sayMessage(msg:String?){ println(msg) } } //在MainTest.java檔案中 public static void main(String[] args){ Test.INSTANCE.sayMessage("Hello"); }
如果不通過INSTANCE來能不能呼叫到sayMessage方法?直接通過Test呼叫
Test.sayMessage("Hello")
可以通過@JVMStatic 註解實現,該註解最終會把對應方法在編譯成public static修飾。
2.3.傳遞Class物件:
在Java中傳遞一個Class物件的方法是直接
ClassName.class
而Kotlin如果要傳遞一個Java的class物件則是
ClassName::class.java
因為Kotlin的Class物件與Java的Class物件並不相同,Kt有著自己的Class型別:KClass,如果使用的是KClass那麼直接
ClassName:kotlin
示例程式碼如下:
fun printClass(clazz:Class<MainTest>){ println("Class Name:"+clazz.simpleName); } fun printClass(clazz:KClass<Test>){ println(clazz.simpleName) } //呼叫 printClass(MainTest::class.java) printClass(Test::class)
2.4.關鍵字上的衝突:
如果在Java程式碼中定義的變數或常量名使用到了Kotlin的關鍵字,需要藉助一對單引號''解決這個衝突,如下:
//關鍵字衝突 public static String object = "object"; //Kotlin中呼叫 println(MainTest.`object`);
3.新手易踩坑
3.1.Kotlin沒有封裝類
Kotlin中對於基本資料型別是沒有封裝類這一說的,這一點不同於Java的int->Interger,float->Float......
首先看如下程式碼:
//用Java定義了一個介面,兩個同名方法,引數型別一個是基本資料型別,另一個是它的封裝類 public interface NoBoxInterface { void printNum(int num); void printNum(Integer num); } //用Kotlin去實現它: class NoBoxImpl:NoBoxInterface{ override fun printNum(num: Int) {} }
上面的程式碼中,我們只會要也只能實現一個方法,因為Kotlin中也不存在Integer這個型別。
擴充套件一下:
碰巧是在搜Kotlin有沒有封箱這一說的時候看到的,來看下面這個例子:
簡單一點說,裝箱就是自動將基本資料型別轉換為包裝器型別;拆箱就是自動將包裝器型別轉換為基本資料型別。這是在Java1.5中引入的特性,目的是為了節省記憶體和提高虛擬機器對整型資料的處理能力。
eg:
//自動裝箱 Integer total = 99; //自定拆箱 int totalprim = total;
val num1:Int = 127 val num2:Int? = num1 val num3:Int? = num1 fun main(args:Array<String>){ println(num2== num3) println(num2===num3)//kotlin中===比較的是地址,換言之,比較的是它倆是不是同一個物件 } //output:true true
當num1>127的時候
//output:true false
可能會有點疑惑為什麼舉這個例子,跟裝箱有什麼關係,為什麼大於與小於等於127輸出的結果不一樣?
開啟Idea的Tool->Kotlin->Show Kotlin ByteCode可以檢視對應的位元組碼檔案,然後decomplie可以反編譯成java程式碼
Int反編譯之後對應到int,Int?反編譯之後對應到Integer。在用Integer裝箱的時候,我們用到了它的valueOf方法
Integer i = Integer.valueOf(8);
去看下valueOf就能知道為和如此了:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //low=-128 high=127
正是這裡,當整型值在-128到127之間的時候,Integer是不會去建立新物件的,而是從IntegerCache中讀取,這個類是Integer的內部靜態類,存放了-128到127之間的全部Integer物件。
回到最初的例子,正是因為上面這一點,當數值在這個限定範圍裡面的時候,num3和num2指向的都是同一個Integer物件,當超過這個範圍,就會去建立新的Integer物件,使得===的結果為false。
3.2.Kotlin型別空值敏感
這一點在筆記開頭已經講過了
//java程式碼 String format(String str) { return str.isEmpty() ? null : str; } //在kotlin中呼叫 fun function(str: String) { val fmt1 = format(str) val fmt2: String = format(str) val fmt3: String? = format(str) } fmt2這一句會丟擲NullPointException,而fmt3不會。
3.3.Kotlin沒有靜態變數與靜態方法
網上很多部落格說直接用object關鍵字修飾的類,它的方法就是static方法;或者companion object{}修飾的就是靜態成員,個人認為這種理解是錯誤的。
來看下面一段程式碼:
class StaticTest { var num0 = 1 fun method(){ println("$num0") } companion object { //注意:@JvmField var num1 = 1 //注意:@JvmStatic fun staticMethod(){ println("$num1") } } }
在上面的程式碼中,我先註釋了兩個個註解,然後又恢復它們,通過檢視編譯好的位元組碼檔案:
//沒加註解 private static I num1 public final staticMethod()V //加了註解 public static I num1 public final static staticMethod()V
可以看出,對於位於object或者companion object{}修飾的域裡面的變數而言 ,kotlin的確把它們看做了static域 ,但是方法卻不一樣,只有在加了@JVMStatic註解之後(注意:這個註解只能在有object修飾的域裡面才能使用),staticMethod才被編譯成了static屬性的方法。
那麼加與不加有什麼區別?畢竟你都可以直接通過ClassName.MethodName來呼叫它,我個人覺得這更像是一種前面說到過的,Kotlin單例的寫法。
如果要給一個變數或者一個方法附上“Static”屬性(打引號是指明在Kotlin中沒有Static這一說,只是能夠達到與Java的Static關鍵字相同的效果),可以通過以下方式來實現:
下面我援引Kotlin doc來說明如何在Kotlin中達到Static效果
3.3.1.Static欄位
@JvmField lateinit const
@JVMField
class Key(val value: Int) { companion object { @JvmField val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value } } }
// Java Key.COMPARATOR.compare(key1, key2); // public static final field in Key class
lateinit
object Singleton { lateinit var provider: Provider }
// Java Singleton.provider = new Provider(); // public static non-final field in Singleton class
const
// file example.kt object Obj { const val CONST = 1 } class C { companion object { const val VERSION = 9 } } const val MAX = 239
int c = Obj.CONST; int d = ExampleKt.MAX; int v = C.VERSION;
3.3.2.Static方法
-
@JVMStatic
註解
class C { companion object { @JvmStatic fun foo() {} fun bar() {} } }
//在Java中呼叫 C.foo(); // works fine C.bar(); // error: not a static method C.Companion.foo(); // instance method remains C.Companion.bar(); // the only way it works
注意Companion,如果是直接object修飾類,則是INSTANCE,如下:
object Obj { @JvmStatic fun foo() {} fun bar() {} }
Obj.foo(); // works fine Obj.bar(); // error Obj.INSTANCE.bar(); // works, a call through the singleton instance Obj.INSTANCE.foo(); // works too