参考书籍【Kotlin实战】进行的知识点记录,仅作参考,不做教程。
fun main(args: Array<String>) {
println("Hello, World!")
}
介绍一下特征和语法:
声明一个有返回值(类型)的函数,返回类型放在参数列表之后。
// 返回类型是Int
fun max(x: Int, y: Int): Int {
return if (x > y) x else y
}
函数的声明以关键字fun开始,函数名称紧随其后,这个例子中函数名称是max,接下来是括号括起来的参数列表。参数列表的后面跟着返回类型,它们之间用一个冒号隔开。
一个函数的基本结构如下:
语句和表达式
在Kotlin中,if是表达式,而不是语句。语句和表达式的区别在于,表达式有值,并且能作为另一个表达式的一部分使用;而语句总是包围着它的代码块中的顶层元素,并且没有自己的值。在Java中,所有的控制结构都是语句。而在Kotlin中,除了循环(for、do和do/while)以外大多数控制结构都是表达式。这种结合控制结构和其他表达式的能力让你可以简明扼要地表示许多常见的模式,稍后会看到这些内容。
另一方面,Java中的赋值操作是表达式,在Kotlin中反而变成了语句。这有助于避免比较和赋值之间的混淆,而这种混淆是常见的错误来源。
可以让前面的函数变得更简单。因为它的函数体是由单个表达式构成的,可以用这个表达式作为完整的函数体,并去掉花括号和return语,如下:
fun max(x: Int, y: Int): Int = if (x > y) x else y
// 继续简化,可以省略函数的返回类型(kotlin的类型推导)
fun max(x: Int, y: Int) = if (x > y) x else y
如果函数体写在花括号中,我们说这个函数有代码块体(代码块函数)。如果它直接返回了一个表达式,它就有表达式体(表达式函数)。
为什么有些函数可以不声明返回类型?作为一门静态类型语言,Kotlin不是要求每个表达式都应该在编译期具有类型吗?事实上,每个变量和表达式都有类型,每个函数都有返回类型。但是对表达式体函数来说,编译器会分析作为函数体的表达式,并把它的类型作为函数的返回类型,即使没有显式地写出来。这种分析通常被称作类型推导。
注意,只有表达式体函数的返回类型可以省略。对于有返回值的代码块体函数必须显式地写出返回类型和return语句。
变量的声明以关键字开始,然后是变量名称,最后加上类型(不加也可以):
// 省略了变量的类型
val info = "hello, kotlin"
val age = 1
// 显式的指定变量类型
val address: String = "china"
val tel: Int = 123
// double
val weight = 50.6
如果变量没有初始化器,需要显示的指定变量的类型:
val info: String
info = "hello, kotlin"
声明变量的关键字有两个:
默认情况下,应该尽可能地使用 val 关键字来声明所有的Kotlin 变量,仅在必要的时候换成var。使用不可变引用、不可变对象及无副作用的函数让你的代码更接近函数式编程风格。
在定义了val变量的代码块执行期间,val变量只能进行唯一一次初始化。
尽管val 引用自身是不可变的,但是它指向的对象可能是可变的。例如:
// 声明不可变引用
val list = arrayListOf(1, 2, 3)
// 改变引用指向的对象
list.add(5)
var 关键字修饰的变量,允许变量改变自己的值,但它的类型却是改变不了的。如下
// var声明变量,类型推导为默认String类型
var info = "hello, kotlin"
// 编译报错:The integer literal does not conform to the expected type String
info = 1
编译器只会根据初始化器来推断变量的类型,在决定类型的时候不会考虑后续的赋值操作。如果需要在变量中存储不匹配类型的值,必须手动把值转换或强制转换到正确的类型,后续会说到。
fun main(args: Array<String>) {
val name = "kotlin"
println("hello, $name")
}
在代码中,声明了一个变量name,并在后面的字符串字面值中使用了它。Kotlin 可以在字符串字面值中引用局部变量,只需要在变量名称前面加上字符$。如果要在字符串中使用$字符,你要对它转义:println(“\$name”)会打印$name,并不会把x解释成变量的引用。
val name = "Kotlin"
// Kotlin
println("Hello $name")
println("Hello ${name}")
// Hello $name
println("Hello \$name")
// Hello ${name}
println("Hello \${name}")
还可以引用更复杂的表达式,而不是仅限于简单的变量名称,只需要把表达式用花括号括起来:
// 年龄这么大了, 你可真是老者
val age = 100
println("年龄这么大了, 你可真是${if (age > 90) "老者" else "年轻的很"}")
先看一个Java中一个JavaBean,Person类,只有一个属性,name:
public class Person {
private final string name;
public Person(String name){
this.name = name;
}
public string getName(){
return name;
}
}
再看Kotlin中定义一个同上的Person类:
// 这种只有数据没有其他代码的类,叫做值对象
class Person(val name: String)
类的概念就是把数据和处理数据的代码封装成一个单一的实体。在Java中,数据存储在字段中,通常还是私有的。如果想让类的使用者访问到数据得提供访问器方法:一个getter,可能还有一个setter。setter还可以包含额外的逻辑。
在Java中,字段和其访问器的组合常常被叫作属性。在Kotlin中,属性是头等的语言特性,完全代替了字段和访问器方法。在类中声明一个属性和声明一个变量一样:使用val和var 关键字。声明成 val的属性是只读的,而var属性是可变的。
基本上,声明属性的时候,就声明了对应的访问器(只读属性有一个getter,而可写属性既有getter也有setter)。访问器的默认实现非常简单:创建一个存储值的字段,以及返回值的getter和更新值的setter。但是如果有需要,可以声明自定义的访问器,使用不同的逻辑来计算和更新属性的值。
上面Kotlin方式声明的Person类,隐藏了和原始Java代码相同的底层实现:它是一个字段都是私有的类,这些字段在构造方法中初始化并能通过对应的getter访问。
在Kotlin中使用Person类:
val person = Person("kotlin")
// kotlin
println(person.name)
怎样写一个属性访问器的自定义实现。假设声明这样一个矩形,它能判断自己是否是正方形。不需要一个单独的字段来存储这个信息(是否是正方形),因为可以随时通过检查矩形的长宽是否相等来判断:
class Rectangle(val height: Int, val width: Int) {
// 定义类中的属性
val isSquare: Boolean
// 自定义属性的getter访问器
get() {
return height == width
}
}
属性isSquare不需要字段来保存它的值。它只有一个自定义实现的getter。它的值是每次访问属性的时候计算出来的。
注意,不需要使用带花括号的完整语法,也可以这样写get()=height==width。对这个属性的调用依然不变:
val rectangle = Rectangle(10, 20)
// false
println("rectangle isSquare: ${rectangle.isSquare}")
val rectangle2 = Rectangle(20, 20)
// true
println("rectangle2 isSquare: ${rectangle2.isSquare}")
kotlin中的when结构看作是java中switch的替代品,但是比switch更强大。
声明一个简单的枚举类
enum class Color {
// 最后没有分号
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
Kotlin用了enum和class 两个关键字。
和Java 一样,枚举并不只是值的列表,可以给枚举类声明属性和方法。
enum class Color(val sort: Int, val chineseName: String) {
// 这里注意最后一个枚举项以分号结尾, 把枚举常量和方法定义分开
RED(1, "红色"), ORANGE(2, "橙色"), YELLOW(3, "黄色"), GREEN(4, "绿色"), BLUE(5, "蓝色"), INDIGO(6, "靛蓝色"), VIOLET(7, "紫色");
fun getName(): String {
return chineseName
}
}
fun main(args: Array<String>) {
// 红色
println(Color.RED.getName())
}
枚举常量用的声明构造方法和属性的语法与常规类一样。当声明每个枚举常量的时候,必须提供该常量的属性值。注这里的必须使用分号的地方,如果要在枚举类中定义任何方法,就要使用分号把枚举常量列表和方法定义分开。
和if 相似,when 是一个有返回值的表达式,因此可以写一个直接返回 when表示式的表达式体函数。
// 使用when选择正确的枚举值
// 这个函数直接返回一个when表达式
fun getColor(color: Color) = when (color) {
// 如果颜色和枚举常量相等就返回对应的字符串
Color.RED -> "红"
Color.ORANGE -> "橙"
Color.YELLOW -> "黄"
Color.GREEN -> "绿"
Color.BLUE -> "蓝"
Color.INDIGO -> "靛"
Color.VIOLET -> "紫"
}
fun main(args: Array<String>) {
// 红
println(getColor(Color.RED))
}
上面的代码根据传进来的color值找到对应的分支。和Java不一样,你不需要在每个分支都写上break语句。如果匹配成功,只有对应的分支会执行。也可以把多个值合并到同一个分支,只需要用逗号隔开这些值:
fun getColor(color: Color) = when (color) {
// 在一个when分支上合并多个选项
Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN -> "红橙黄绿"
Color.BLUE -> "蓝"
Color.INDIGO -> "靛"
Color.VIOLET -> "紫"
}
// 不带参数的when
fun getColor(color: Color) = when {
// 如果颜色和枚举常量相等就返回对应的字符串
color == Color.RED || color == Color.ORANGE || color == Color.YELLOW || color == Color.GREEN -> "红橙黄绿"
color == Color.BLUE -> "蓝"
color == Color.INDIGO -> "靛"
color == Color.VIOLET -> "紫"
else -> throw Exception("不知道的颜色")
}
如果没有给when表达式提供参数,分支条件就是任意的布尔表达式。
// 区间迭代,闭区间
for (i in 1..10) {
println(i)
}
// 迭代带步长的区间
for (i in 1..10 step 2) {
println(i)
}
// 倒序区间迭代
for (i in 10 downTo 1) {
println(i)
}
// 左闭右开区间迭代
for (i in 1 until 10) {
println(i)
}
```kotlin
// 定义一个简单的map
val map = mapOf(1 to "one", 2 to "two", 3 to "three")
// 迭代map
for (entry in map) {
println("key: ${entry.key}, value: ${entry.value}")
}
// 迭代map
for ((key, value) in map) {
println("key: $key, value: $value")
}