第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
,List
等的父类型,对于任何的List
这里的X
只要是Animal的子类型,那么List extends Animal>
就是List
的父类型。
使用通配符List extends Animal>
的引用,我们不可以往这个List
中添加Animal
类型以及其子类型的元素。如图,Java编译器是不允许的。
因为对于set
方法,编译器无法知道具体的类型,所以会拒绝这个调用。但是,如果是get
方法形式的调用,则是允许的:
List extends Animal> list1 = new ArrayList<>();
List list4 = new ArrayList<>();
list4.add(new Dog());
animal.act(list4);
list1 = list4;
animal.act(list1);
我们这里把引用变量List extends Animal> list1
直接赋值List
,因为编译器知道可以把返回对象转换为一个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(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
。即:
List extends Nubmer> list1 = new ArrayList()
List extends Nubmer> list2 = new ArrayList()
但这里不能向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 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) {
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 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 listOfNumber = Arrays.asList(1, 2, 3, 4);
final Map map = new HashMap();
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;
}
}
}