Skip to content

Commit 096b5da

Browse files
committed
update kotlin notes
1 parent 63cf2e4 commit 096b5da

File tree

5 files changed

+385
-24
lines changed

5 files changed

+385
-24
lines changed

KotlinCourse/1.Kotlin_简介&变量&类&接口.md

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,20 @@ var weight = 70.5 // double
204204
}
205205
```
206206

207+
208+
209+
### 变量保存了指向对象的引用
210+
211+
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/variable.jpg?raw=true)
212+
213+
当该对象被赋值给变量时,这个对象本身并不会被直接赋值给当前的变量。相反,该对象的引用会被赋值给该变量。因为当前的变量存储的是对象的引用,因此它可以访问该对象。
214+
215+
如果你使用val来声明一个变量,那么该变量所存储的对象的引用将不可修改。然而如果你使用var声明了一个变量,你可以对该变量重新赋值。例如,如果我们使用代码: `x = 6`,将x的值赋为6,此时会创建一个值为6的新Int对象,并且x会存放该对象的引用。下面新的引用会替代原有的引用值被存放在x中:
216+
217+
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/var_chage.jpg?raw=true)
218+
219+
**注意: 在Java中,数字类型是原生类型,所以变量存储的是实际数值。但是在Kotlin中的数字也是对象,而变量仅仅存储该数字对象的引用,并非对象本身。**
220+
207221
### 优先使用val来避免副作用
208222

209223
在很多Kotlin的学习资料中,都会传递一个原则:优先使用val来声明变量。这相当正确,但更好的理解可以是:尽可能采用val、不可变对象及纯函数来设计程序。关于纯函数的概念,其实就是没有副作用的函数,具备引用透明性。
@@ -267,7 +281,7 @@ var size: Int = 2
267281
这个例子中就会内存溢出。
268282

269283
`kotlin`为此提供了一种我们要说的后端变量,也就是`field`。编译器会检查函数体,如果使用到了它,就会生成一个后端变量,否则就不会生成。
270-
我们在使用的时候,用`field`代替属性本身进行操作。按照惯例`set`参数的名称是`value`,但是如果你喜欢你可以选择一个不同的名称。
284+
我们在使用的时候,用`field`代替属性本身进行操作。按照惯例`set`参数的名称是`value`,但是如果你喜欢你可以选择一个不同的名称。setter通过field标识更新变量属性值。field指的是属性的支持字段,你可以将其视为对属性的底层值的引用。在getter和setter中使用field代替属性名称很重要,因为这样可以阻止你陷入无限循环中。
271285

272286
```kotlin
273287
class A {
@@ -289,7 +303,17 @@ fun main() {
289303
1
290304
```
291305

306+
如果我们不手动写getter和setter方法,编译器会在编译代码时添加以下代码段:
307+
308+
```kotlin
309+
var myProperty: String
310+
get() = field
311+
set(value) {
312+
field = value
313+
}
314+
```
292315

316+
这意味着无论何时当你使用点操作符来获取或设置属性值时,实际上你总是调用了属性的getter或是setter。那么,为什么编译器要这么做呢?为属性添加getter和setter意味着有访问该属性的标准方法。getter处理获取值的所有请求,而setter处理所有属性值设置的请求。因此,如果你想要改变处理这些请求的方式,你可以在不破坏任何人代码的前提下进行。通过将其包装在getter和setter中来输出对属性的直接访问称为数据隐藏。
293317

294318
## 延迟初始化
295319

@@ -375,37 +399,88 @@ val sex: String by lazy(LazyThreadSafetyMode.NONE) {
375399

376400
## 类的定义:使用`class`关键字
377401

402+
当你在定义类的时候,你需要想想该类所创建的对象需要什么。你需要考虑:
403+
404+
- 每个对象自身的特点
405+
406+
对象自身的特点称为属性(properties)。它们代表了对象自身的状态(数据),并且该类中的每一个对象都有自己独特的数值。例如,一个狗(Dog)类可能有名字(name)、体重(weight)和品种(breed)属性。一个歌曲(Song)类可能有标题(title)和演唱者(artist)属性。
407+
408+
- 每个对象的行为
409+
410+
对象的行为是它们的函数(functions)。它们决定了对象的行为,并且可能回使用对象的属性。例如,之前提到的Dog类,可能具有吠叫(bark)函数;Song这个类可能会有播放(play)函数。
411+
412+
413+
378414
类可以包含:
415+
379416
- 构造函数和初始化块
380417
- 函数
381418
- 属性
382419
- 嵌套类和内部类
383420
- 对象声明
384421

385422

386-
```kotlin
387-
class MainActivity{
388423

424+
你可以将类想象成一个对象的模板,因为它告诉编译器如何创建该特定类的对象。它还将告诉编译器每个对象应该具有哪些属性,并且从该类生成的每个对象都可以拥有自己独有的属性值。例如,每个Dog对象都有自己的名称、重量和品种属性,每个Dog的属性值都可以是不同的。
425+
426+
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/kotlin_class_1.jpg?raw=true)
427+
428+
429+
430+
431+
```kotlin
432+
class Dog(val name: String, var weight: Int, val breed: String){
433+
fun bark() {
434+
435+
}
389436
}
390437
```
391438

392439
如果有参数的话你只需要在类名后面写上它的参数,如果这个类没有任何内容可以省略大括号:
393440
```kotlin
394-
class Person(name: String, age: Int)
441+
class Dog(val name: String, var weight: Int, val breed: String)
395442
```
396443

397444
### 创建类的实例
398445

399446
```kotlin
400-
val person = Person("charon", 18)
447+
val myDog = Dog("Fido", 70, "Mixed" )
401448
```
402449

403450
上面的类有一个默认的构造函数。
404451

405452
注意:创建类的实例不用`new`了啊。
406453

454+
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/kotlin_class_dog_sample.jpg?raw=true)
455+
456+
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/kotlin_class_dog.jpg?raw=true)
457+
458+
类中所定义的函数又称为成员函数。有时也被称为方法。
459+
460+
#### 创建对象的执行过程
461+
462+
```kotlin
463+
var myDog = Dog("Fido", 70, "Mixed")
464+
```
465+
466+
1. 系统会为每个传入Dog构造函数的参数创建一个对象。它会创建一个值为“Fido”的String,一个值为70的Int,以及一个值为“Mixed”的String。
467+
2. 系统会为一个新的Dog对象分配空间,并且Dog构造函数会被调用。
468+
3. Dog构造函数定义了三个属性:名称、重量以及品种。在这个现象背后,每一个属性实际上是一个变量。对于构造函数中定义的每个属性,都会有一个相应类型的变量被创建。
469+
4. 相应的变量的引用将会被赋值给Dog的属性。例如,值为“Fido”的String将会被赋值给name属性。
470+
5. 最后,这个新的Dog对象的引用将会被赋值给名为myDog的Dog变量。
471+
472+
![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/kotlin_dog_new.jpg?raw=true)
473+
474+
475+
476+
477+
478+
479+
407480
### 构造函数
408481

482+
构造函数包含了初始化对象所需的代码。它在对象被分配给引用之前运行,这意味着你有机会对对象进行一些内部操作以便其被使用。大多人使用构造函数来定义对象的属性,并且给这些属性赋值。每当你创建一个新的对象,该对象所属的类的构造函数将会被调用。构造函数在你初始化对象时被调用。它通常被用于定义对象的属性,并且对属性赋值。
483+
409484
`Kotlin`中的一个类可以有一个主构造函数和一个或多个次构造函数。
410485

411486
#### 主构造函数
@@ -616,6 +691,8 @@ data class Artist(
616691
var mbid: String)
617692
```
618693

694+
数据类自动覆盖它们的equals方法以改变==操作符的行为,由此通过检查对象的每个属性值来判断是否相等。例如,假设你创建了两个属性值完全相同的Recipe对象,使用==操作符对它们进行比较将返回true,因为它们存放了相同的数据:除了提供从Any父类继承的equals方法的新实现,数据类还覆盖了hashCode和toString方法。
695+
619696
通过数据类,会自动提供以下函数:
620697

621698
- 所有属性的`get() set()`方法
@@ -649,7 +726,13 @@ val charon2 = charon.copy(age = 19)
649726
- data class之前不能用abstract、open、sealed或者inner进行修饰
650727
- 在Kotlin 1.1版本前数据类只允许实现接口,之后的版本既可以实现接口也可以继承类
651728

652-
## 多声明
729+
730+
731+
与任何其他类一样,你可以向数据类添加属性和方法,只需要将它们包含在类主体中。但是有一个大问题。在编译器生成数据类的方法实现时,比如覆盖equals方法和创建copy方法,它仅包含在主构造函数中定义的属性。因此如果你在数据类主体中定义添加的属性,则它们不会被包含到任何编译器生成的方法中。
732+
733+
### 数据类定义了componentN方法
734+
735+
定义数据类时,编译器会自动向该类添加一组方法,你可以将其作为访问对象属性值的替代方法。它们被称为componentN方法,其中N表示被访问属性的编号(按声明排序)。
653736

654737
多声明,也可以理解为变量映射,这就是编译器自动生成的`componentN()`方法。
655738

@@ -689,6 +772,10 @@ open class Person(num: Int)
689772
class SuperPerson(num: Int) : Person(num)
690773
```
691774

775+
冒号后面的Person(num)会调用Person类的构造函数,以确保所有的初始化代码(例如给属性赋值)能够被执行。调用父类构造函数是强制性的:如果父类有主构造函数,你必须在子类头中调用它,否则代码将无法通过编译。请记住,即使你没有在父类中显式地添加构造函数,编译器也会在编译代码的时候自动创建一个空构造函数。假如我们不想为Person类添加构造函数,因此编译器在编译代码的时候创建了一个空构造函数。该构造函数通过使用Person()被调用。
776+
777+
778+
692779
如果该类有一个主构造函数,其基类必须用基类型的主构造函数参数就地初始化。
693780
如果类没有主构造函数,那么每个次构造函数必须使用`super`关键字初始化其基类型,或委托给另一个构造函数做到这一点。
694781
注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
@@ -849,7 +936,7 @@ open class SuperPerson(num: Int) : Person(num) {
849936
}
850937
```
851938

852-
每个声明的属性可以由具有初始化器的属性或者具有`get`方法的属性覆盖,你也可以用一个`var`属性覆盖一个`val`属性,但反之则不行
939+
每个声明的属性可以由具有初始化器的属性或者具有`get`方法的属性覆盖,如果某个属性在父类中被定义为val,你可以在子类中使用var属性覆盖它。只需要覆盖该属性并将其声明为var即可。请注意,这只适用于这一种方式。如果尝试使用val覆盖var属性,编译器将会感到沮丧并拒绝编译你的代码
853940

854941

855942

@@ -886,9 +973,10 @@ abstract class Derived : Base() {
886973

887974

888975

889-
890976
## 接口:使用`interface`关键字
891977

978+
接口可以让你在父类层次结构之外定义共同的行为接口用于为共同行为定义协议,使你可以不依赖严格的继承结构却又可以利用多态。与抽象类类似,接口不能被实例化且可以定义抽象或具体的方法和属性,但两者有一个关键的不同点:类可以实现多个接口,但是只能继承于一个直接父类。所以接口不仅拥有抽象类的优点,而且使用起来更加灵活。
979+
892980
```kotlin
893981
interface FlyingAnimal {
894982
fun fly()
@@ -964,6 +1052,26 @@ toast("Hello")
9641052
toast("Hello", Toast.LENGTH_LONG)
9651053
```
9661054

1055+
### 无参主函数
1056+
1057+
如果你使用的是Kotlin1.2或更早的版本,若想正常运行程序,你的主函数必须写成如下形式:
1058+
1059+
```kotlin
1060+
fun main(args: Array<String>) {
1061+
// ...
1062+
}
1063+
```
1064+
1065+
从Kotlin1.3版本器,你可以忽略main函数的参数,写成如下形式:
1066+
1067+
```kotlin
1068+
fun main() {
1069+
// ...
1070+
}
1071+
```
1072+
1073+
1074+
9671075

9681076

9691077
### 可变长参数函数:使用`vararg`关键字
@@ -981,6 +1089,16 @@ fun main(args: Array<String>) {
9811089
}
9821090
```
9831091

1092+
如果你有一个现有的值数组,则可以通过在数组名前加上`*`来将这些值传递给该函数。星号`(*)`被称为扩展运算符,以下是它的一些使用示例:
1093+
1094+
```kotlin
1095+
vval myArray = arrayOf(1, 2, 3, 4, 5)
1096+
val mList = vars(*myArray)
1097+
val mList2 = vars(0, *myArray, 6, 7)
1098+
```
1099+
1100+
1101+
9841102

9851103

9861104
### `Unit`:让函数调用皆为表达式

KotlinCourse/2.Kotlin_高阶函数&Lambda&内联函数.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ val foo = { x: Int ->
252252
> “Lambda 表达式”(lambda expression)其实就是匿名函数,`Lambda`表达式基于数学中的`λ`演算得名,直接对应于其中的`lambda`抽象
253253
> `(lambda abstraction)`,是一个匿名函数,即没有函数名的函数。`Lambda`表达式可以表示闭包(注意和数学传统意义上的不同)。
254254
255+
256+
255257
`Java 8`的一个大亮点是引入`Lambda`表达式,使用它设计的代码会更加简洁。
256258

257259
```java
@@ -327,6 +329,50 @@ val sum: (Int, Int) -> Int = { x, y -> x + y }
327329
则此`Lambda`表达式变成`val sum = { x: Int, y: Int -> x + y }`,此时`Lambda`表达式会根据主体中的最后一个(或可能是单个)表达式会视为
328330
返回值。当然,在某些特定情况下,`x``y`的类型了是可以推断的,所以`val sum = { x, y -> x + y }`
329331

332+
333+
334+
通过调用lambda来执行它的代码你可以使用invoke函数调用lambda,并传入参数的值。例如,以下代码定义了变量addInts,并将用于将两个Int参数相加的lambda赋值给它。然后代码调用了该lambda,传入参数值6和7,并将结果赋值给变量result:
335+
336+
```kotlin
337+
val addInts = { x: Int, y: Int -> x + y }
338+
val result = addInts.invoke(6, 7)
339+
// 还可以使用如下快捷方式调用lambda:
340+
val result = addInts(6, 7)
341+
342+
```
343+
344+
### lambda表达式类型
345+
346+
就像任何其他类型的对象一样,lambda也具有类型。然而,lambda类型的不同点在于,它不会为lambda的实现指定类名,而是指定lambda的参数和返回值的类型。lambda类型的格式如下:
347+
348+
```kotlin
349+
(parameters) -> return_type
350+
```
351+
352+
因此,如果你的lambda具有单独的Int参数并返回一个字符串,如下代码所示:
353+
354+
```kotlin
355+
val msg = { x: Int -> "xxx" }
356+
```
357+
358+
其类型为:
359+
360+
```kotlin
361+
(Int) -> String
362+
```
363+
364+
如果将lambda赋值给一个变量,编译器会根据该lambda来推测变量的类型,如上例所示。然而,就像任何其他类型的对象一样,你可以显式地定义该变量的类型。例如,以下代码定义了一个变量add,该变量可以保存对具有两个Int参数并返回Int类型的lambda的引用:
365+
366+
```kotlin
367+
val add: (Int, Int) -> Int
368+
369+
add = { x: Int, y: Int -> x + y }
370+
```
371+
372+
Lambda类型也被认为是函数类型。
373+
374+
375+
330376
## Lambda开销
331377

332378
```kotlin
@@ -344,6 +390,32 @@ listOf(1, 2, 3).forEach { item -> foo(item) }
344390

345391
默认情况下,我们可以直接用it来代表item,而不需要用item -> 进行声明。
346392

393+
你可以将单独的参数替换为it。
394+
395+
如果lambda具有一个单独的参数,而且编译器能够推断其类型,你可以省略该参数,并在lambda的主体中使用关键字it指代它。
396+
397+
要了解它是如何工作的,如前所述,假设使用以下代码将lambda赋值给变量:
398+
399+
```kotlin
400+
val addFive: (Int) -> Int = { x -> x + 5 }
401+
```
402+
403+
由于lambda具有单独的参数x,而且编译器能够推断出x为Int类型,因此我们可以省略该x参数,并在lambda的主体中使用it替换它:
404+
405+
```kotlin
406+
val addFive: (Int) - Int = { it + 5 }
407+
```
408+
409+
在上述代码中,{it+5}等价于{x->x+5},但更加简洁。请注意,你只能在编译器能够推断该参数类型的情况下使用it语法。例如,以下代码将无法编译,因为编译器不知道it应该是什么类型:
410+
411+
```kotlin
412+
val addFive = { it + 5 } // 该代码无法编译,因为编译器不能推断其类型
413+
```
414+
415+
416+
417+
418+
347419
我们看一下foo函数用IDE转换后的Java代码:
348420

349421
```java

0 commit comments

Comments
 (0)