《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(C)

当F<|C时,如果有f(F)<|f(C),那么f叫做协变(Convariant);
当F<|C时,如果有f(C)<|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(C)<|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;
      }
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容