第3章 Kotlin語言基礎(chǔ)
3.2 聲明變量和值
在Kotlin中,一切都是對象。所以,變量也是對象 (即任何變量都是根據(jù)引用類型來使用)
變量分為 var(可變的)和 val(不可變的)
盡量在Kotlin中首選使用 val 不變值,好處:可預(yù)測的行為、線程安全
3.5 流程控制語句
3.5.2 when表達(dá)式
正常格式
fun cases(obj: Any) {
when (obj) {
1 -> print("第一項")
"hello" -> print("這個是字符串hello")
is Long -> print("這是一個Long類型數(shù)據(jù)")
!is String -> print("這不是String類型的數(shù)據(jù)")
else -> print("else類似Java中的default")
}
}
如果我們有很多分支需要相同的方式處理,則可以把多個分支條件放在一起,用逗號分隔
fun switch(x: Any) {
when (x) {
-1, 0 -> print("x == -1 or x == 0")
1 -> print("x == 1")
2 -> print("x == 2")
else -> {
print("x is neither 1 nor 2")
}
}
}
我們可以用任意表達(dá)式(而不只是常量)作為分支條件
fun switch(x: Any) {
val s = "123"
when (x) {
-1, 0 -> print("x == -1 or x == 0")
1 -> print("x == 1")
2 -> print("x == 2")
parseInt(s) -> print("x is 123")
else -> {
print("x is neither 1 nor 2")
}
}
}
我們也可以堅測一個值在in或者不在!in一個區(qū)間或者集合中:
val x = 1
val validNumbers = arrayOf(1, 2, 3)
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
第4章 基本數(shù)據(jù)類型與類型系統(tǒng)
4.8 類型檢測與類型轉(zhuǎn)換
4.8.2 as運(yùn)算符
as運(yùn)算符用于執(zhí)行引用類型的顯式類型轉(zhuǎn)換。
如果要轉(zhuǎn)換的類型與指定的類型兼容,轉(zhuǎn)換就會成功進(jìn)行;
如果類型不兼容,使用as?運(yùn)算符就會返回值null
第5章 集合類
5.1 集合類是什么
5.1.2 集合類是一種數(shù)據(jù)結(jié)構(gòu)
編程的本質(zhì):數(shù)據(jù)結(jié)構(gòu) + 算法(信息的邏輯結(jié)構(gòu)及其基本操作)
5.1.3 連續(xù)存儲和離散存儲
連續(xù)存儲:數(shù)組,操作數(shù)據(jù)時,根據(jù)離首地址的偏移量直接存取相應(yīng)位置上的數(shù)據(jù);
但是在數(shù)組中任意位置上插入一個元素,就需要先把后面的元素集體向后移動位置,為其空出存儲空間
離散存儲:鏈表,與上面相反
選擇:查找較多最好用數(shù)組,添加或刪除比較多最好選鏈表。
5.2 Kotlin集合類簡介
Kotlin的集合類分:可變集合類(Mutable)與不可變集合類(Immutable)
集合類有3種:list、set、map
5.3 List
有很多擴(kuò)展函數(shù)可以用一用
5.4 Set
5.5 Map
第6章 泛型
協(xié)變、逆變、in、out
6.1 泛型(Generic Type)
6.1.1 為什么要有類型參數(shù)
由于我們不能籠統(tǒng)地把集合類中所有的對象視作Object,然后在使用的時候各自作強(qiáng)制類轉(zhuǎn)換。
所以,引入類型參數(shù)解決這個類型安全使用的問題。
6.2 型變(Variance)
6.2.1 Java的類型通配符
Java泛型的通配符有兩種形式。我們使用
- 子類型上界限定符
? extends T指定類型參數(shù)的上限(該類型必須是類型T或者它的子類型) - 超類型下界限定符
? super T指定類型參數(shù)的下限(該類型必須是類型T或者它的父類型)
我們稱之為類型通配符(Type Wildcard)。默認(rèn)的上界(如果沒有聲明)是Any?下界是Nothing。
示例代碼
class Animal {
public void act(List<? extends Animal> list) {
for (Animal animal : list) {
animal.eat();
}
}
public void eat() {
System.out.println("Eating");
}
}
示例類型的層次關(guān)系,如圖
對象層次類圖:

集合類泛型層次類圖:

List<? extends Animal>是List<Animal>,List<Dog>等的父類型,對于任何的List<X>這里的X只要是Animal的子類型,那么List<? extends Animal>就是List<X>的父類型。
使用通配符List<? extends Animal>的引用,我們不可以往這個List中添加Animal類型以及其子類型的元素。如圖,Java編譯器是不允許的。

因為對于set方法,編譯器無法知道具體的類型,所以會拒絕這個調(diào)用。但是,如果是get方法形式的調(diào)用,則是允許的:
List<? extends Animal> list1 = new ArrayList<>();
List<Dog> list4 = new ArrayList<>();
list4.add(new Dog());
animal.act(list4);
list1 = list4;
animal.act(list1);
我們這里把引用變量List<? extends Animal> list1直接賦值List<Dog> list4,因為編譯器知道可以把返回對象轉(zhuǎn)換為一個Animal類型。
相應(yīng)的,? super T超類型限定符的變量類型List<? super ShepherDog>的層次結(jié)構(gòu)如下

如果把一個對象為聲明、使用兩部分的話,
泛型:側(cè)重于類型的聲明的代碼復(fù)用,用于定義內(nèi)部數(shù)據(jù)類型的參數(shù)化。
通配符:側(cè)重于使用上的代碼復(fù)用,通配符則用于定義使用的對象類型的參數(shù)化。
6.2.2 協(xié)變(convariant)與逆變(contravariant)
在Java中數(shù)組是協(xié)變的,下面的代碼可以正確編譯:
Integer[] ints = new Integer[3];
ints[0] = 0;
ints[1] = 1;
Number[] numbers = new Number[3];
numbers = ints;
for (Number n : numbers) {
System.out.println(n);
}
在Java中,因為Integer是Number的子類型,數(shù)組類型Integer[]也是Number[]的子類型。
而另一方面,泛型不是協(xié)變的。編譯器報錯提示如下:

使用通配符,任然是報錯的:

逆變與協(xié)變
Animal類型(簡記為F,F(xiàn)ather)是Dog類型(簡記為C,Child)的父類型,我們簡記為F<|C
而List,List的類型,我們記為f(F),f(C)
當(dāng)F<|C時,如果有f(F)<|f(C),那么f叫做協(xié)變(Convariant);
當(dāng)F<|C時,如果有f(C)<|f(F),那么發(fā)叫做逆變(Contravariance)
協(xié)變和逆變都是類型安全的。
<? extends T>實現(xiàn)了泛型的協(xié)變
List<? extends Nubmer> list = new ArrayList<>()
這里? extends Number表示是Number類或其子類,即表示類型的上界為Number,簡記為C
這里C<|Number,這個關(guān)系成立:List<C> <| List<Number>。即:
List<? extends Nubmer> list1 = new ArrayList<Interger>()
List<? extends Nubmer> list2 = new ArrayList<Float>()
但這里不能向list1、list2添加null以外的任意對象
list1.add(null)
list1.add(new Integer(1)); //error
list2.add(new Float(2)); //error
為了保護(hù)類型一致,禁止向List<? extends Number>添加任意對象,不過可以添加null
<? super T>實現(xiàn)了泛型的逆變
List<? super Nubmer> list = new ArrayList<>()
? super Number通配符表示Number類或其父類,即表示的類型下界為Number。這里的父類型是? super Number,子類型C是Number。即當(dāng)F<|C,有f(C)<|f(F),這就是逆變
PECS:producer-extends,consumer-super, Get and Put Principle.
6.3 Kotlin的泛型特色
Kotlin引入生產(chǎn)者和消費(fèi)者的概念,即前面講的PECS。
生產(chǎn)者是我們?nèi)プx取數(shù)據(jù)的對象,消費(fèi)者是我們寫入數(shù)據(jù)的對象
6.3.1 out T和in T
Kotlin,只能保證讀取數(shù)據(jù)時類型安全的對象叫做生產(chǎn)者,用out T標(biāo)記,
只能保證寫入數(shù)據(jù)安全時類型安全的對象叫做消費(fèi)者,用in T標(biāo)記
可以這么記
out T等價于? extens T``in T等價于? super T,此外,還有*等價于?
第7章 面向?qū)ο缶幊蹋∣OP)
7.9 單例模式(Singleton)與伴生對象(companion object)
7.9.2 object對象
kotlin中沒有靜態(tài)屬性和方法,但是也提供了實現(xiàn)類似于單例的功能,我們可以使用關(guān)鍵字object聲明一個object對象
object AdminUser {
val username: String = "admin"
val password: String = "admin"
fun getTimestamp() = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
fun md5Password() = EncoderByMd5(password + getTimestamp())
}
為了更加直觀的了解object對象的概念,我們把上面的object User的代碼反編譯成Java代碼:
public final class User {
@NotNull
private static final String username = "admin";
@NotNull
private static final String password = "admin";
public static final User INSTANCE;
@NotNull
public final String getUsername() {
return username;
}
@NotNull
public final String getPassword() {
return password;
}
private User() {
INSTANCE = (User)this;
username = "admin";
password = "admin";
}
static {
new User();
}
}
從上面的反編譯代碼,我們可以直觀了解Kotlin的object背后的一些原理。
7.13 委托(Delegation)
7.13.2 類的委托(Class Delegation)
就像支持單例模式的object對象一樣,Kotlin 在語言層面原生支持委托模式。
interface Subject {
fun hello()
}
class RealSubject(val name: String) : Subject {
override fun hello() {
val now = Date()
println("Hello, REAL $name! Now is $now")
}
}
class ProxySubject(val sb: Subject) : Subject by sb {
override fun hello() {
println("Before ! Now is ${Date()}")
sb.hello()
println("After ! Now is ${Date()}")
}
}
fun main(args: Array<String>) {
val subject = RealSubject("World")
subject.hello()
println("-------------------------")
val proxySubject = ProxySubject(subject)
proxySubject.hello()
}
在這個例子中,委托代理類ProxySubject繼承接口Subject,并將其所有共有的方法委托給一個指定的對象sb:
class ProxySubject(val sb: Subject) : Subject by sb
ProxySubject的超類型Subject中的by sb表示sb將會在ProxySubject中內(nèi)部存儲。
(注:對這個還不是很理解,自測了下,加不加by sb,log都一樣)
7.13.3 委托屬性(Delegated Properties)
通常對于屬性類型,我們是在每次需要的時候手動聲明它們:
class NormalPropertiesDemo {
var content: String = "NormalProperties init content"
}
那么這個content屬性將會很“呆板”。屬性委托賦予屬性富有變化的活力。
例如:
- 延遲屬性(lazy properties): 其值只在首次訪問時計算
- 可觀察屬性(observable properties): 監(jiān)聽器會收到有關(guān)此屬性變更的通知
- 把多個屬性儲存在一個映射(map)中,而不是每個存在單獨(dú)的字段中。
第8章 函數(shù)式編程(FP)
第9章 輕量級線程:協(xié)程
9.1 協(xié)程簡介
協(xié)程提供了一種避免阻塞線程并用更簡單、更可控的操作替代線程阻塞的方法:協(xié)程掛起。
協(xié)程主要是讓原來要使用“異步+回調(diào)方式”寫出來的復(fù)雜代碼, 簡化成可以用看似同步的方式寫出來(對線程的操作進(jìn)一步抽象)。
9.13 協(xié)程與線程比較
區(qū)別:協(xié)程是編譯器級的,而線程是操作系統(tǒng)級的。
協(xié)程是用戶空間下的線程。
線程是搶占式的,而協(xié)程是非搶占式的。
線程是協(xié)程的資源。
9.14 協(xié)程的好處
協(xié)程依靠user-space調(diào)度,而線程、進(jìn)程則是依靠kernel來進(jìn)行調(diào)度。
線程、進(jìn)程間切換都需要從用戶態(tài)進(jìn)入內(nèi)核態(tài),而協(xié)程的切換完全是在用戶態(tài)完成,且不像線程進(jìn)行搶占式調(diào)度,協(xié)程是非搶占式的調(diào)度。
協(xié)程的程序只在用戶空間內(nèi)切換上下文,不再陷入內(nèi)核來做線程切換,這樣可以避免大量的用戶空間和內(nèi)核空間之間的數(shù)據(jù)拷貝,降低了CPU的消耗。
使用協(xié)程,我們不再需要像異步編程時寫那么一堆callback函數(shù),代碼結(jié)構(gòu)不再支離破碎,整個代碼邏輯上看上去和同步代碼沒什么區(qū)別,簡單,易理解,優(yōu)雅。
9.15 協(xié)程的內(nèi)部機(jī)制
9.15.1 基本原理
協(xié)程完全通過編譯技術(shù)實現(xiàn)(不需要來自 VM 或 OS 端的支持),掛起機(jī)制是通過狀態(tài)機(jī)來實現(xiàn),其中的狀態(tài)對應(yīng)于掛起調(diào)用。
第10章 Kotlin與Java操作
Java調(diào)用Kotlin
Java可以調(diào)用Kotlin代碼,但是要多用一些注解語法。
@JvmName 注解修改生成的Java類的類名 (不建議修改,推薦Kotlin默認(rèn)的命名生成規(guī)則)
@JvmField 注解標(biāo)注Kotlin中的屬性字段,表示這個一個實例字段,不會生成getters/setter方法
@JvmStatic 注解靜態(tài)方法,在相應(yīng)的類中生成靜態(tài)方法
這些注解語法是編譯器為了更加方便Java調(diào)用Kotlin代碼提供的一些技巧,使在Java中調(diào)用Kotlin代碼更加自然優(yōu)雅些。
JvmOverloads注解,生成額外的重載函數(shù)給Java調(diào)用
Throws(Exception::class),讓Kotlin的異常變成受檢的,讓Java編譯器可以檢查到。(在Kotlin中,所有異常都是非受檢的,在運(yùn)行時,這個異常還是拋出來的)
Kotlin與Java對比
打印日志
- Java
System.out.print("Java");
System.out.println("Java");
- Kotlin
print("Kotlin")
println("Kotlin")
其實,Kotlin中的println函數(shù)是一個內(nèi)聯(lián)函數(shù),它其實就是通過封裝java.lang.System類的System.out.println來實現(xiàn)的。
@kotlin.internal.InlineOnly
public inline fun print(message: Any?) {
System.out.print(message)
}
常量與變量
- Java
String name = "KotlinVSJava";
final String name = "KotlinVSJava";
- Kotlin
var name = "KotlinVSJava"
val name = "KotlinVSJava"
null聲明
- Java
String otherName;
otherName = null;
- Kotlin
var otherName : String?
otherName = null
空判斷
- Java
if (text != null) {
int length = text.length();
}
- Kotlin
text?.let {
val length = text.length
}
// 或者
val length = text?.length
在Kotlin中,我們只使用一個問號安全調(diào)用符號就省去了Java中煩人的if - null 判斷。
字符串拼接
- Java
String firstName = "Jack";
String lastName = "Chen";
String message = "My name is: " + firstName + " " + lastName;
- Kotlin
val firstName = "Jack"
val lastName = "Chen"
val message = "My name is: $firstName $lastName"
Kotlin中使用$和${}(花括號里面是表達(dá)式的時候)占位符來實現(xiàn)字符串的拼接,這個比在Java中每次使用加號來拼接要方便許多。
換行
- Java
String text = "First Line\n" +
"Second Line\n" +
"Third Line";
- Kotlin
val text = """
|First Line
|Second Line
|Third Line
""".trimMargin()
三元表達(dá)式
- Java
String text = x > 5 ? "x > 5" : "x <= 5";
- Kotlin
val text = if (x > 5)
"x > 5"
else "x <= 5"
操作符
- java
final int andResult = a & b;
final int orResult = a | b;
final int xorResult = a ^ b;
final int rightShift = a >> 2;
final int leftShift = a << 2;
- Kotlin
val andResult = a and b
val orResult = a or b
val xorResult = a xor b
val rightShift = a shr 2
val leftShift = a shl 2
類型判斷和轉(zhuǎn)換(顯式)
- Java
if (object instanceof Car) {
}
Car car = (Car) object;
- Kotlin
if (object is Car) {
}
var car = object as Car
類型判斷和轉(zhuǎn)換 (隱式)
- Java
if (object instanceof Car) {
Car car = (Car) object;
}
- Kotlin
if (object is Car) {
var car = object // Kotlin智能轉(zhuǎn)換
}
Kotlin的類型系統(tǒng)具備一定的類型推斷能力,這樣也省去了不少在Java中類型轉(zhuǎn)換的樣板式代碼。
Range區(qū)間
- Java
if (score >= 0 && score <= 300) { }
- Kotlin
if (score in 0..300) { }
更靈活的case語句
- Java
public String getGrade(int score) {
String grade;
switch (score) {
case 10:
case 9:
grade = "A";
break;
case 8:
case 7:
case 6:
grade = "B";
break;
case 5:
case 4:
grade = "C";
break;
case 3:
case 2:
case 1:
grade = "D";
break;
default:
grade = "E";
}
return grade;
}
- Kotlin
fun getGrade(score: Int): String {
var grade = when (score) {
9, 10 -> "A"
in 6..8 -> "B"
4, 5 -> "C"
in 1..3 -> "D"
else -> "E"
}
return grade
}
for循環(huán)
- Java
for (int i = 1; i <= 10 ; i++) { }
for (int i = 1; i < 10 ; i++) { }
for (int i = 10; i >= 0 ; i--) { }
for (int i = 1; i <= 10 ; i+=2) { }
for (int i = 10; i >= 0 ; i-=2) { }
for (String item : collection) { }
for (Map.Entry<String, String> entry: map.entrySet()) { }
- Kotlin
for (i in 1..10) { }
for (i in 1 until 10) { }
for (i in 10 downTo 0) { }
for (i in 1..10 step 2) { }
for (i in 10 downTo 1 step 2) { }
for (item in collection) { }
for ((key, value) in map) { }
更方便的集合操作
- Java
final List<Integer> listOfNumber = Arrays.asList(1, 2, 3, 4);
final Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "Jack");
map.put(2, "Ali");
map.put(3, "Mindorks");
- Kotlin
val listOfNumber = listOf(1, 2, 3, 4)
val map = mapOf(1 to "Jack", 2 to "Ali", 3 to "Mindorks")
遍歷
- Java
// Java 7
for (Car car : cars) {
System.out.println(car.speed);
}
// Java 8+
cars.forEach(car -> System.out.println(car.speed));
// Java 7
for (Car car : cars) {
if (car.speed > 100) {
System.out.println(car.speed);
}
}
// Java 8+
cars.stream().filter(car -> car.speed > 100).forEach(car -> System.out.println(car.speed));
- Kotlin
cars.forEach {
println(it.speed)
}
cars.filter { it.speed > 100 }
.forEach { println(it.speed)}
方法(函數(shù))定義
- Java
void doSomething() {
// 實現(xiàn)
}
void doSomething(int... numbers) {
// 實現(xiàn)
}
- Kotlin
fun doSomething() {
// 實現(xiàn)
}
fun doSomething(vararg numbers: Int) {
// 實現(xiàn)
}
帶返回值的方法(函數(shù))
- Java
int getScore() {
// logic here
return score;
}
- Kotlin
fun getScore(): Int {
// logic here
return score
}
// 單表達(dá)式函數(shù)
fun getScore(): Int = score
另外,Kotlin中的函數(shù)是可以直接傳入函數(shù)參數(shù),同時可以返回一個函數(shù)類型的。
constructor 構(gòu)造器
- Java
public class Utils {
private Utils() {
// 外部無法來調(diào)用實例化
}
public static int getScore(int value) {
return 2 * value;
}
}
- Kotlin
class Utils private constructor() {
companion object {
fun getScore(value: Int): Int {
return 2 * value
}
}
}
// 或者直接聲明一個object對象
object Utils {
fun getScore(value: Int): Int {
return 2 * value
}
}
JavaBean與Kotlin數(shù)據(jù)類
這段Kotlin中的數(shù)據(jù)類的代碼:
data class Developer(val name: String, val age: Int)
對應(yīng)下面這段Java實體類的代碼:
- Java
public final class Developer {
@NotNull
private final String name;
private final int age;
@NotNull
public final String getName() {
return this.name;
}
public final int getAge() {
return this.age;
}
public Developer(@NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
this.age = age;
}
@NotNull
public final String component1() {
return this.name;
}
public final int component2() {
return this.age;
}
@NotNull
public final Developer copy(@NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
return new Developer(name, age);
}
// $FF: synthetic method
// $FF: bridge method
@NotNull
public static Developer copy$default(Developer var0, String var1, int var2, int var3, Object var4) {
if((var3 & 1) != 0) {
var1 = var0.name;
}
if((var3 & 2) != 0) {
var2 = var0.age;
}
return var0.copy(var1, var2);
}
public String toString() {
return "Developer(name=" + this.name + ", age=" + this.age + ")";
}
public int hashCode() {
return (this.name != null?this.name.hashCode():0) * 31 + this.age;
}
public boolean equals(Object var1) {
if(this != var1) {
if(var1 instanceof Developer) {
Developer var2 = (Developer)var1;
if(Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
return true;
}
}
return false;
} else {
return true;
}
}
}