Kotlin語(yǔ)言基礎(chǔ)筆記
Kotlin流程控制語(yǔ)句筆記
Kotlin操作符重載與中綴表示法筆記
Kotlin擴(kuò)展函數(shù)和擴(kuò)展屬性筆記
Kotlin空指針安全(null-safety)筆記
Kotlin類(lèi)型系統(tǒng)筆記
Kotlin面向?qū)ο缶幊坦P記
Kotlin委托(Delegation)筆記
Kotlin泛型型筆記
Kotlin函數(shù)式編程筆記
Kotlin與Java互操作筆記
Kotlin協(xié)程筆記
Kotlin官方一直以100% interoperable with Java?作為第一要素,他不是像Scala一樣把類(lèi)庫(kù)都自己實(shí)現(xiàn)一遍,而是通過(guò)擴(kuò)展函數(shù)、函數(shù)編程等特性對(duì)現(xiàn)有的Java進(jìn)行增強(qiáng),同時(shí)保持對(duì)Java的100%兼容。正是這個(gè)特性,我們可以在一個(gè)項(xiàng)目中同時(shí)使用Java和Kotlin,一個(gè)大型的項(xiàng)目,如果換一種語(yǔ)言來(lái)實(shí)現(xiàn)的話,這個(gè)代價(jià)是非常大的。但是對(duì)于一個(gè)Java項(xiàng)目,你可以某一部分使用Kotlin來(lái)實(shí)現(xiàn),然后慢慢地一步步的把整個(gè)項(xiàng)目所有代碼都改成Kotlin實(shí)現(xiàn)。這樣風(fēng)險(xiǎn)就會(huì)小非常多。
1. Kotlin調(diào)用Java
1.1 Kotlin使用Java的集合類(lèi)
val kotlinList = listOf(1, 2, 3, 4)
//Java原生的ArrayList
val javaList = ArrayList<Int>()
for (item in kotlinList) {
javaList.add(item)
}
操作Java原生的集合類(lèi)跟Kotlin中使用類(lèi)沒(méi)有什么區(qū)別。
1.2 調(diào)用Java類(lèi)的getter和setter
假如我們有這樣一個(gè)Person類(lèi)。
package com.dengyin2000.java;
public class Person {
private String name;
private Long id;
private boolean isFemale;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public boolean isFemale() {
return isFemale;
}
public void setFemale(boolean female) {
isFemale = female;
}
}
那么在Kotlin中要怎樣使用呢?
val person = Person()
person.id = 1
person.name = "Denny"
person.isFemale = false
println("Person(id=${person.id}, name=${person.name}, isFemale=${person.isFemale})")
println函數(shù)中的person類(lèi)調(diào)用的是Person類(lèi)的getter,上面屬性設(shè)置實(shí)際上調(diào)用的是Person類(lèi)的setter方法。
1.3 空安全
Java聲明的類(lèi)型在Kotlin中會(huì)被特別對(duì)待并稱為平臺(tái)類(lèi)型(platform types),對(duì)于這種類(lèi)型的空檢查會(huì)放寬,這樣做就使得跟Java的調(diào)用方式一致,但是Java中的任何引用都可能為null,這樣我們使用Kotlin調(diào)用來(lái)自Java的對(duì)象的時(shí)候就有可能會(huì)出現(xiàn)空安全的問(wèn)題。例如:
val person = Person()
person.id = 1
person.isFemale = false
println(person.name.substring(1))
person.name運(yùn)行時(shí)為null,當(dāng)然調(diào)用的時(shí)候會(huì)person.name.substring(1)時(shí)會(huì)發(fā)生異常。
當(dāng)然為了避免null的問(wèn)題,我們可以使用Koltin的安全調(diào)用:
println(person.name?.substring(1)) //打印null
1.4 平臺(tái)類(lèi)型
平臺(tái)類(lèi)型不能在程序中顯式表述,因此在語(yǔ)言中沒(méi)有相應(yīng)語(yǔ)法。 然而,編譯器和 IDE 有時(shí)需要(在錯(cuò)誤信息中、參數(shù)信息中等)顯示他們,所以我們用一個(gè)助記符來(lái)表示他們:
- T! : 表示 T 或者 T?
- (Mutable) Collection<T>! : 表示 “可以可變或不可變、可空或不可空的 T 的 Java 集合”
- Array<(out) T>! : 表示“可空或者不可空的 T(或 T 的子類(lèi)型)的 Java 數(shù)組”
1.5 Kotlin與Java中的類(lèi)型映射
Kotlin 特殊處理一部分 Java 類(lèi)型。這樣的類(lèi)型不是“按原樣”從 Java 加載,而是 映射 到相應(yīng)的 Kotlin 類(lèi)型。 映射只發(fā)生在編譯期間,運(yùn)行時(shí)表示保持不變。怎么理解這句話呢?就是說(shuō)在Kotlin中加載這些Java的類(lèi)型時(shí),編譯器會(huì)轉(zhuǎn)成對(duì)應(yīng)的Kotlin類(lèi)型,這樣就能用到Koltin中對(duì)Java的增強(qiáng)功能(擴(kuò)展函數(shù)等)。
1.5.1 Java的原生類(lèi)型映射到對(duì)應(yīng)的Kotlin類(lèi)型
1.5.2 Java中的一些內(nèi)置類(lèi)型也會(huì)做相應(yīng)的映射
1.5.3 Java的基本類(lèi)型的包裝類(lèi)對(duì)應(yīng)到可空額Kotlin類(lèi)型
1.5.4 類(lèi)型參數(shù)的Java類(lèi)型映射到Kotlin中的平臺(tái)類(lèi)型
例如:List<java.lang.Integer> 在Kotlin會(huì)變成List<Int!>
集合類(lèi)型在 Kotlin 中可以是只讀的或可變的,因此 Java 集合類(lèi)型作如下映射: (下表中的所有 Kotlin 類(lèi)型都在 kotlin.collections包中):
1.5.5 Java的數(shù)組映射
1.6 Kotlin中使用Java泛型
Kotlin 的泛型與 Java 有點(diǎn)不同。當(dāng)將 Java 類(lèi)型導(dǎo)入 Kotlin 時(shí),我們會(huì)執(zhí)行一些轉(zhuǎn)換:和 Java 一樣,Kotlin 在運(yùn)行時(shí)不保留泛型,即對(duì)象不攜帶傳遞到他們構(gòu)造器中的那些類(lèi)型參數(shù)的實(shí)際類(lèi)型。
1.7 Java可變參數(shù)
假如我們有一個(gè)這樣的類(lèi),有一個(gè)可變參數(shù)的靜態(tài)方法:
package com.dengyin2000.java;
public class StringUtils {
public static String connect(String... strings) {
StringBuilder sb = new StringBuilder();
for (String string : strings) {
sb.append(string).append(",");
}
return sb.toString();
}
}
因?yàn)镵otlin并沒(méi)有可變類(lèi)型,所以我們需要使用*
來(lái)傳遞一個(gè)String數(shù)組來(lái)達(dá)到相應(yīng)的目的:
val listOf = arrayOf("Denny", "Deng")
println(StringUtils.connect(*listOf)) //打印Denny,Deng
1.8 Unchecked Exception
在Kotlin中,所有的異常都是Unchecked Exception,也就是說(shuō)編譯器不會(huì)強(qiáng)迫你捕獲任何的異常,但是在Java中你是需要捕獲Checked Exception。如下:
我們需要try catch保護(hù)起來(lái)。
但是在Kotlin中不需要try catch,只是路過(guò)拋出來(lái)你沒(méi)有try catch的話,程序還是會(huì)掛。
1.9 java.lang.Object方法使用
當(dāng) Java 類(lèi)型導(dǎo)入到 Kotlin 中時(shí),類(lèi)型 java.lang.Object 的所有引用都成了 Any。 而因?yàn)?Any 不是平臺(tái)指定的,它只聲明了 toString()、hashCode() 和 equals() 作為其成員, 所以為了能用到 java.lang.Object 的其他成員,你需要如下手段:
1.9.1 wait() / notify()
需要把對(duì)象轉(zhuǎn)成java.lang.Object
來(lái)使用:
val p = Person()
(p as java.lang.Object).wait()
1.9.2 getClass()
要取得對(duì)象的 Java 類(lèi),我們可以在類(lèi)引用上使用 java 擴(kuò)展屬性,它是Kotlin的反射類(lèi)kotlin.reflect.KClass的擴(kuò)展屬性。
val fooClass = foo::class.java
上面的代碼使用了自 Kotlin 1.1 起支持的綁定類(lèi)引用。我們也可以使用 javaClass 擴(kuò)展屬性。
val fooClass = foo.javaClass
1.9.3 clone()
要覆蓋 clone()
,需要繼承 kotlin.Cloneable
:
class Example : Cloneable {
override fun clone(): Any { …… }
}
1.10 Kotlin與Java 的反射
我們可以使用 instance::class.java
、ClassName::class.java
或者 instance.javaClass
通過(guò) java.lang.Class
來(lái)進(jìn)入 Java 的反射類(lèi)java.lang.Class
, 之后我們就可以使用Java中的反射的功能特性了。
1.11 SAM轉(zhuǎn)換
SAM = single abstract method,在Java中被稱為SAM類(lèi)型。例如:Runnable接口。在Kotlin中我們可以這樣創(chuàng)建SAM接口實(shí)例:
val runnable = Runnable { println("SAM") }
1.12 Java使用了Kotlin的關(guān)鍵字
一些 Kotlin 關(guān)鍵字在 Java 中是有效標(biāo)識(shí)符:in、 object、 is等等。
如果一個(gè) Java 庫(kù)使用了 Kotlin 關(guān)鍵字作為方法,我們可以通過(guò)反引號(hào)(`)字符轉(zhuǎn)義它來(lái)調(diào)用該方法。例如我們有個(gè)Java類(lèi),其中有個(gè)is方法:
public class StringUtils {
public static boolean is(String value, String value1) {
return value.equals(value1);
}
}
我們需要在Kotlin中需要這樣調(diào)用:
println(StringUtils.`is`("a", "b")) //打印false
2. Java調(diào)用Kotlin
Kotlin最終還是會(huì)編譯成class,所以要怎么調(diào)Kotlin其實(shí)就是要看Kotlin翻譯成的class是怎樣的。Kotlin可以通過(guò)一些annotation來(lái)調(diào)整Kotlin翻譯成的class結(jié)果。
2.1 Java訪問(wèn)Kotlin屬性
假如我們有下面一個(gè)Kotlin的類(lèi):
class Student{
var id: Long = -1L
var name: String = "Denny"
var isFemale: Boolean = false
}
我們通過(guò)Intellij IDEA的Tools->Kotlin->Show Kotlin Bytecode->Decompile可以看到生成的Java的代碼如下:
public final class Student {
private long id = -1L;
@NotNull
private String name = "Denny";
private boolean isFemale;
public final long getId() {
return this.id;
}
public final void setId(long var1) {
this.id = var1;
}
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public final boolean isFemale() {
return this.isFemale;
}
public final void setFemale(boolean var1) {
this.isFemale = var1;
}
}
看到Java代碼,你應(yīng)該知道怎么調(diào)用了吧。
2.2 Java調(diào)用Kotlin包級(jí)函數(shù)
在package com.dengyin2000.kotlintest1
包里面的StringUtil.kt
文件中生命的所有函數(shù)、屬性、都將編譯成一個(gè)名為com.dengyin2000.kotlintest1.StringUtilKt
的Java類(lèi)的靜態(tài)方法。假如我們有一個(gè)下面的Kotlin文件:
package com.dengyin2000.kotlintest1
fun sayHello() {
println("Hello ${name}")
}
val name:String = "Denny"
fun String.firstChar() :Char{
return this[0]
}
fun main(args: Array<String>) {
println("Denny".firstChar())
}
通過(guò)上面的方式可以看到生成的Java代碼如下:
package com.dengyin2000.kotlintest1;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
public final class StringUtilKt {
@NotNull
private static final String name = "Denny";
public static final void sayHello() {
String var0 = "Hello " + name;
System.out.println(var0);
}
@NotNull
public static final String getName() {
return name;
}
public static final char firstChar(@NotNull String $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
return $receiver.charAt(0);
}
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
char var1 = firstChar("Denny");
System.out.println(var1);
}
}
可以看到String的擴(kuò)展函數(shù)第一個(gè)參數(shù)變成了接受者。如果我們想要修改生成的Java對(duì)象的名稱,我們可以使用@file:JvmName
注解,如下:
@file:JvmName("Strings")
package com.dengyin2000.kotlintest1
fun sayHello() {
println("Hello ${name}")
}
val name:String = "Denny"
fun String.firstChar() :Char{
return this[0]
}
fun main(args: Array<String>) {
println("Denny".firstChar())
}
這樣生成的類(lèi)名變成了Strings。
2.3 實(shí)例字段
如果我們不希望某個(gè)屬性生成getter setter方法,我們希望生成一個(gè)實(shí)例字段的話,我們可以使用@JvmField
注解標(biāo)注Kotlin的屬性。如下:
class Student{
var id: Long = -1L
var name: String = "Denny"
var isFemale: Boolean = false
@JvmField
var grade: Int = 0
}
生成的Java如下:
public final class Student {
private long id = -1L;
@NotNull
private String name = "Denny";
private boolean isFemale;
@JvmField
public int grade;
public final long getId() {
return this.id;
}
public final void setId(long var1) {
this.id = var1;
}
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public final boolean isFemale() {
return this.isFemale;
}
public final void setFemale(boolean var1) {
this.isFemale = var1;
}
}
2.4 靜態(tài)字段
在伴生對(duì)象和命名對(duì)象(object 類(lèi))的屬性上使用@JvmField
的區(qū)別如下:
class Student{
var id: Long = -1L
var name: String = "Denny"
var isFemale: Boolean = false
@JvmField
var grade: Int = 0
companion object {
var teachName = "Sally"
@JvmField
var schoolMaster = "Noah"
}
}
生成的Java代碼如下:
public final class Student {
private long id = -1L;
@NotNull
private String name = "Denny";
private boolean isFemale;
@JvmField
public int grade;
@NotNull
private static String teachName = "Sally";
@JvmField
@NotNull
public static String schoolMaster = "Noah";
public static final Student.Companion Companion = new Student.Companion((DefaultConstructorMarker)null);
public final long getId() {
return this.id;
}
public final void setId(long var1) {
this.id = var1;
}
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public final boolean isFemale() {
return this.isFemale;
}
public final void setFemale(boolean var1) {
this.isFemale = var1;
}
public static final class Companion {
@NotNull
public final String getTeachName() {
return Student.teachName;
}
public final void setTeachName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
Student.teachName = var1;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
調(diào)用方式如下:
String schoolMaster = Student.schoolMaster;
String teachName = Student.Companion.getTeachName();
2.5 靜態(tài)方法
類(lèi)似的只要將伴生對(duì)象或者命名對(duì)象的的方法用@JvmStatic
注釋?zhuān)@樣就能生成Java的靜態(tài)方法。Kotlin代碼如下:
class Student{
var id: Long = -1L
var name: String = "Denny"
var isFemale: Boolean = false
@JvmField
var grade: Int = 0
companion object {
var teachName = "Sally"
@JvmField
var schoolMaster = "Noah"
@JvmStatic
fun sayHello() {
println("Hello world")
}
}
}
生成的Java代碼如下:
public final class Student {
private long id = -1L;
@NotNull
private String name = "Denny";
private boolean isFemale;
@JvmField
public int grade;
@NotNull
private static String teachName = "Sally";
@JvmField
@NotNull
public static String schoolMaster = "Noah";
public static final Student.Companion Companion = new Student.Companion((DefaultConstructorMarker)null);
public final long getId() {
return this.id;
}
public final void setId(long var1) {
this.id = var1;
}
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public final boolean isFemale() {
return this.isFemale;
}
public final void setFemale(boolean var1) {
this.isFemale = var1;
}
@JvmStatic
public static final void sayHello() {
Companion.sayHello();
}
public static final class Companion {
@NotNull
public final String getTeachName() {
return Student.teachName;
}
public final void setTeachName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
Student.teachName = var1;
}
@JvmStatic
public final void sayHello() {
String var1 = "Hello world";
System.out.println(var1);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
這樣調(diào)用
Student.sayHello();
2.6 生成重載方法
通常,如果你寫(xiě)一個(gè)有默認(rèn)參數(shù)值的 Kotlin 函數(shù),在 Java 中只會(huì)有一個(gè)所有參數(shù)都存在的完整參數(shù)簽名的方法可見(jiàn),如果希望向 Java 調(diào)用者暴露多個(gè)重載,可以使用 @JvmOverloads 注解。
該注解也適用于構(gòu)造函數(shù)、靜態(tài)方法等。它不能用于抽象方法,包括在接口中定義的方法。
Kotlin的類(lèi)如下:
class Animal @JvmOverloads constructor(name: String, type: Int = 0){
fun talk(name: String, by: Int = 1) {
}
@JvmOverloads
fun talkTo(name: String, by: Int = 1) {
}
}
生成的Java代碼如下:
public final class Animal {
public final void talk(@NotNull String name, int by) {
Intrinsics.checkParameterIsNotNull(name, "name");
}
// $FF: synthetic method
// $FF: bridge method
public static void talk$default(Animal var0, String var1, int var2, int var3, Object var4) {
if ((var3 & 2) != 0) {
var2 = 1;
}
var0.talk(var1, var2);
}
@JvmOverloads
public final void talkTo(@NotNull String name, int by) {
Intrinsics.checkParameterIsNotNull(name, "name");
}
// $FF: synthetic method
// $FF: bridge method
@JvmOverloads
public static void talkTo$default(Animal var0, String var1, int var2, int var3, Object var4) {
if ((var3 & 2) != 0) {
var2 = 1;
}
var0.talkTo(var1, var2);
}
@JvmOverloads
public final void talkTo(@NotNull String name) {
talkTo$default(this, name, 0, 2, (Object)null);
}
@JvmOverloads
public Animal(@NotNull String name, int type) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
}
// $FF: synthetic method
@JvmOverloads
public Animal(String var1, int var2, int var3, DefaultConstructorMarker var4) {
if ((var3 & 2) != 0) {
var2 = 0;
}
this(var1, var2);
}
@JvmOverloads
public Animal(@NotNull String name) {
this(name, 0, 2, (DefaultConstructorMarker)null);
}
}
生成了兩個(gè)構(gòu)造方法,talkTo也生成了兩個(gè)方法,talk就只有一個(gè);
2.7 可見(jiàn)性
Kotlin 的可見(jiàn)性與Java的可見(jiàn)性的映射關(guān)系如下表所示:
2.8 Kotlin中異常
在Kotlin中是不需要顯示的try catch Checked Exception的,比如下面這個(gè)throwException方法:
class Animal @JvmOverloads constructor(name: String, type: Int = 0){
fun talk(name: String, by: Int = 1) {
}
@JvmOverloads
fun talkTo(name: String, by: Int = 1) {
}
fun throwException() {
throw Exception("hahaha")
}
}
在Java中調(diào)用不需要try catch。
如果你想讓Java調(diào)用時(shí)需要主動(dòng)try catch的話,那你需要使用
@Throws(Exception::class)
注解。這時(shí)候Java調(diào)用方就需要try catch了。