《Kotlin極簡教程》筆記

第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中,因爲IntegerNumber的子類型,數組類型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 Tin 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;
      }
   }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章