第3章 Kotlin語言基礎
3.2 聲明變量和值
在Kotlin中,一切都是對象。所以,變量也是對象 (即任何變量都是根據引用類型來使用)
變量分爲 var
(可變的)和 val
(不可變的)
儘量在Kotlin中首選使用 val
不變值,好處:可預測的行爲、線程安全
3.5 流程控制語句
3.5.2 when表達式
正常格式
fun cases(obj: Any) {
when (obj) {
1 -> print("第一項")
"hello" -> print("這個是字符串hello")
is Long -> print("這是一個Long類型數據")
!is String -> print("這不是String類型的數據")
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")
}
}
}
我們可以用任意表達式(而不只是常量)作爲分支條件
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
一個區間或者集合中:
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章 基本數據類型與類型系統
4.8 類型檢測與類型轉換
4.8.2 as運算符
as
運算符用於執行引用類型的顯式類型轉換。
如果要轉換的類型與指定的類型兼容,轉換就會成功進行;
如果類型不兼容,使用as?
運算符就會返回值null
第5章 集合類
5.1 集合類是什麼
5.1.2 集合類是一種數據結構
編程的本質:數據結構 + 算法(信息的邏輯結構及其基本操作)
5.1.3 連續存儲和離散存儲
連續存儲:數組,操作數據時,根據離首地址的偏移量直接存取相應位置上的數據;
但是在數組中任意位置上插入一個元素,就需要先把後面的元素集體向後移動位置,爲其空出存儲空間
離散存儲:鏈表,與上面相反
選擇:查找較多最好用數組,添加或刪除比較多最好選鏈表。
5.2 Kotlin集合類簡介
Kotlin的集合類分:可變集合類(Mutable)與不可變集合類(Immutable)
集合類有3種:list、set、map
5.3 List
有很多擴展函數可以用一用
5.4 Set
5.5 Map
第6章 泛型
協變、逆變、in、out
6.1 泛型(Generic Type)
6.1.1 爲什麼要有類型參數
由於我們不能籠統地把集合類中所有的對象視作Object,然後在使用的時候各自作強制類轉換。
所以,引入類型參數解決這個類型安全使用的問題。
6.2 型變(Variance)
6.2.1 Java的類型通配符
Java泛型的通配符有兩種形式。我們使用
- 子類型上界限定符
? extends T
指定類型參數的上限(該類型必須是類型T或者它的子類型) - 超類型下界限定符
? super T
指定類型參數的下限(該類型必須是類型T或者它的父類型)
我們稱之爲類型通配符(Type Wildcard)。默認的上界(如果沒有聲明)是Any?
下界是Nothing
。
示例代碼
class Animal {
public void act(List<? extends Animal> list) {
for (Animal animal : list) {
animal.eat();
}
}
public void eat() {
System.out.println("Eating");
}
}
示例類型的層次關係,如圖
對象層次類圖:
集合類泛型層次類圖:
List<? extends Animal>
是List<Animal>
,List<Dog>
等的父類型,對於任何的List<X>
這裏的X
只要是Animal的子類型,那麼List<? extends Animal>
就是List<X>
的父類型。
使用通配符List<? extends Animal>
的引用,我們不可以往這個List
中添加Animal
類型以及其子類型的元素。如圖,Java編譯器是不允許的。
因爲對於set
方法,編譯器無法知道具體的類型,所以會拒絕這個調用。但是,如果是get
方法形式的調用,則是允許的:
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
,因爲編譯器知道可以把返回對象轉換爲一個Animal
類型。
相應的,? super T
超類型限定符的變量類型List<? super ShepherDog>
的層次結構如下
如果把一個對象爲聲明、使用兩部分的話,
泛型:側重於類型的聲明的代碼複用,用於定義內部數據類型的參數化。
通配符:側重於使用上的代碼複用,通配符則用於定義使用的對象類型的參數化。
6.2.2 協變(convariant)與逆變(contravariant)
在Java中數組是協變的,下面的代碼可以正確編譯:
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
的子類型,數組類型Integer[]
也是Number[]
的子類型。
而另一方面,泛型不是協變的。編譯器報錯提示如下:
使用通配符,任然是報錯的:
逆變與協變
Animal類型(簡記爲F,Father)是Dog類型(簡記爲C,Child)的父類型,我們簡記爲F<|C
而List,List的類型,我們記爲f(F),f©
當F<|C時,如果有f(F)<|f©,那麼f叫做協變(Convariant);
當F<|C時,如果有f©<|f(F),那麼發叫做逆變(Contravariance)
協變和逆變都是類型安全的。
<? extends T>
實現了泛型的協變
List<? extends Nubmer> list = new ArrayList<>()
這裏? extends Number
表示是Number類或其子類,即表示類型的上界爲Number,簡記爲C
這裏C<|Number
,這個關係成立: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
爲了保護類型一致,禁止向List<? extends Number>
添加任意對象,不過可以添加null
<? super T>
實現了泛型的逆變
List<? super Nubmer> list = new ArrayList<>()
? super Number
通配符表示Number類或其父類,即表示的類型下界爲Number。這裏的父類型是? super Number
,子類型C是Number。即當F<|C,有f©<|f(F),這就是逆變
PECS:producer-extends,consumer-super, Get and Put Principle.
6.3 Kotlin的泛型特色
Kotlin引入生產者和消費者的概念,即前面講的PECS。
生產者是我們去讀取數據的對象,消費者是我們寫入數據的對象
6.3.1 out T
和in T
Kotlin,只能保證讀取數據時類型安全的對象叫做生產者,用out T
標記,
只能保證寫入數據安全時類型安全的對象叫做消費者,用in T
標記
可以這麼記
out T
等價於? extens T``in T
等價於? super T
,此外,還有*
等價於?
第7章 面向對象編程(OOP)
7.9 單例模式(Singleton)與伴生對象(companion object)
7.9.2 object對象
kotlin中沒有靜態屬性和方法,但是也提供了實現類似於單例的功能,我們可以使用關鍵字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
中內部存儲。
(注:對這個還不是很理解,自測了下,加不加by sb
,log都一樣)
7.13.3 委託屬性(Delegated Properties)
通常對於屬性類型,我們是在每次需要的時候手動聲明它們:
class NormalPropertiesDemo {
var content: String = "NormalProperties init content"
}
那麼這個content屬性將會很“呆板”。屬性委託賦予屬性富有變化的活力。
例如:
- 延遲屬性(lazy properties): 其值只在首次訪問時計算
- 可觀察屬性(observable properties): 監聽器會收到有關此屬性變更的通知
- 把多個屬性儲存在一個映射(map)中,而不是每個存在單獨的字段中。
第8章 函數式編程(FP)
第9章 輕量級線程:協程
9.1 協程簡介
協程提供了一種避免阻塞線程並用更簡單、更可控的操作替代線程阻塞的方法:協程掛起。
協程主要是讓原來要使用“異步+回調方式”寫出來的複雜代碼, 簡化成可以用看似同步的方式寫出來(對線程的操作進一步抽象)。
9.13 協程與線程比較
區別:協程是編譯器級的,而線程是操作系統級的。
協程是用戶空間下的線程。
線程是搶佔式的,而協程是非搶佔式的。
線程是協程的資源。
9.14 協程的好處
協程依靠user-space調度,而線程、進程則是依靠kernel來進行調度。
線程、進程間切換都需要從用戶態進入內核態,而協程的切換完全是在用戶態完成,且不像線程進行搶佔式調度,協程是非搶佔式的調度。
協程的程序只在用戶空間內切換上下文,不再陷入內核來做線程切換,這樣可以避免大量的用戶空間和內核空間之間的數據拷貝,降低了CPU的消耗。
使用協程,我們不再需要像異步編程時寫那麼一堆callback函數,代碼結構不再支離破碎,整個代碼邏輯上看上去和同步代碼沒什麼區別,簡單,易理解,優雅。
9.15 協程的內部機制
9.15.1 基本原理
協程完全通過編譯技術實現(不需要來自 VM 或 OS 端的支持),掛起機制是通過狀態機來實現,其中的狀態對應於掛起調用。
第10章 Kotlin與Java操作
Java調用Kotlin
Java可以調用Kotlin代碼,但是要多用一些註解語法。
@JvmName
註解修改生成的Java類的類名 (不建議修改,推薦Kotlin默認的命名生成規則)
@JvmField
註解標註Kotlin中的屬性字段,表示這個一個實例字段,不會生成getters/setter方法
@JvmStatic
註解靜態方法,在相應的類中生成靜態方法
這些註解語法是編譯器爲了更加方便Java調用Kotlin代碼提供的一些技巧,使在Java中調用Kotlin代碼更加自然優雅些。
JvmOverloads
註解,生成額外的重載函數給Java調用
Throws(Exception::class)
,讓Kotlin的異常變成受檢的,讓Java編譯器可以檢查到。(在Kotlin中,所有異常都是非受檢的,在運行時,這個異常還是拋出來的)
Kotlin與Java對比
打印日誌
- Java
System.out.print("Java");
System.out.println("Java");
- Kotlin
print("Kotlin")
println("Kotlin")
其實,Kotlin中的println函數是一個內聯函數,它其實就是通過封裝java.lang.System
類的System.out.println來實現的。
@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中,我們只使用一個問號安全調用符號就省去了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中使用$
和${}
(花括號裏面是表達式的時候)佔位符來實現字符串的拼接,這個比在Java中每次使用加號來拼接要方便許多。
換行
- Java
String text = "First Line\n" +
"Second Line\n" +
"Third Line";
- Kotlin
val text = """
|First Line
|Second Line
|Third Line
""".trimMargin()
三元表達式
- 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
類型判斷和轉換(顯式)
- Java
if (object instanceof Car) {
}
Car car = (Car) object;
- Kotlin
if (object is Car) {
}
var car = object as Car
類型判斷和轉換 (隱式)
- Java
if (object instanceof Car) {
Car car = (Car) object;
}
- Kotlin
if (object is Car) {
var car = object // Kotlin智能轉換
}
Kotlin的類型系統具備一定的類型推斷能力,這樣也省去了不少在Java中類型轉換的樣板式代碼。
Range區間
- 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循環
- 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)}
方法(函數)定義
- Java
void doSomething() {
// 實現
}
void doSomething(int... numbers) {
// 實現
}
- Kotlin
fun doSomething() {
// 實現
}
fun doSomething(vararg numbers: Int) {
// 實現
}
帶返回值的方法(函數)
- Java
int getScore() {
// logic here
return score;
}
- Kotlin
fun getScore(): Int {
// logic here
return score
}
// 單表達式函數
fun getScore(): Int = score
另外,Kotlin中的函數是可以直接傳入函數參數,同時可以返回一個函數類型的。
constructor 構造器
- Java
public class Utils {
private Utils() {
// 外部無法來調用實例化
}
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數據類
這段Kotlin中的數據類的代碼:
data class Developer(val name: String, val age: Int)
對應下面這段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;
}
}
}