《Kotlin極簡教程》筆記

第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中,因為IntegerNumber的子類型,數(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 Tin 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;
      }
   }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容