1 基本要素:函数和变量
1.1 Hello world
fun main(args: Array<String>) {
println("Hello world")
}
以上代码语法:
-
关键字fun用来声明一个函数
-
参数的类型写在它的名称后面
-
数组就是类,Kotlin没有声明数组类型的特殊语法
-
使用println代替System.out.println打印
-
代码结尾没有分号
1.2 函数
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
等价于
fun man(a: Int, b: Int) = if (a > b) a else b
fun main(args: Array<String>) {
println(max(10, 20));
}
在Kotlin中,if是有返回结果的表达式而不是判断语句(返回的值就是判断后的语句结果);第一种写法为代码块函数体,第二种写法为表达式函数体,表达式函数体不需要返回类型,代码在编译时会判断
1.3 变量
val question = "The Ultimate Question of Life, the Universe, and Everything"
val answer = 42
等价于
val answer: Int = 42
val yearsToCompute = 7.5e6
可变变量和不可变变量
-
val(来自value):不可变引用。使用val声明的变量不能在初始化之后再次赋值。它对应java的final变量
-
var(来自variable):可变引用。这种变量的值可以被改变。它对应java的非final变量
默认情况下,应该尽可能使用val关键字来声明所有的kotlin变量,仅在必要的时候换成var。使用不可变引用、不可变对象及无副作用的函数让你的代码更接近函数式编程风格。
fun main() {
val canPerformOperation = Random.nextBoolean()
val message = if (canPerformOperation) "Success" else "Failed"
print(message)
}
fun main() {
// 尽管val引用自身是不可变的,但是它指向的对象可能是可变的
val languages = arrayListOf("java")
languages.add("kotlin") // 改变引用指向的对象
println(language)
}
fun main() {
// var允许变量改变自己的值,但它的类型确实改变不了的
// 错误
var answer = 42
answer = "no answer"
}
一般开发中需要使用到 val
或 var
的情况:
- 在函数中声明一个临时变量或调用其他函数返回给临时变量时,如
val answer = 42;
var answer = 42;
val answer = callMethod()
- 创建model对象构造方法有参数时,如
class Person(val nickname: String, val age: Int)
其他情况和java一样,在函数中的参数直接声明即可,如
fun main(args: Arrays<String>) {}
1.4 编译时常量
在 kotlin 中常量是使用关键字 const
声明的,但常量的声明是有要求的:
-
const 只能修饰 object 的属性(object 声明的类相当于 java 的单例类),或 top-level 顶层变量
-
const 变量的值必须在编译期间确定下来,所以它的类型只能是 String 或基本类型
// 正确,kotlin 没有包的概念,顶层变量就是直接写在文件上的变量
// 顶层变量或函数相当于 java 的静态变量或静态函数
const val a = 0
class A {
// 错误,const 不能声明在非 object 的类中
const val s = 0
}
object A {
// 正确,const 声明在 object 类(即 java 的单例类)中
const val s = 0
}
class A {
companion object {
// 正确,companion object 相当于在 java 会创建一个名为 Companion 的静态类
const val s = 0
}
}
1.5 字符串模板
fun main() {
// $表示字符串模板,如果要在字符串输出$符号,要使用转义字符"\"
// 在字符串模板中也可以使用表达式,使用"{}"括起来
val args = arrayListOf("kotlin", "java")
val name = if (args.size > 0) args[0] else "kotlin"
println("Hello, $name")
println("Hello, ${args[1]}")
println("Hello, ${if (args.size > 0) args[0] else "kotlin"}")
}
2 类和属性
2.1 类和属性
java的对象
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
kotlin的对象
// 在kotlin中,类默认是public,所以可以省略
// 这种类(只有数据没有其他代码)通常被叫做值对象
class Person(val name: String)
2.2 属性
// 属性name和isMarried
// name使用val声明,表示只可读,只会为属性生成一个getter
// isMarried使用var声明,表示可读可写,会为属性生成getter和setter
class Person(val name:String, var isMarried: Boolean)
fun main(args: Array<String>) {
// 创建对象不需要new关键字
val person = Person("Bob", true)
// 可以直接访问类的属性,相当于调用了getter
// setter可以使用person.isMarried = false代表
println("person's name is ${person.name}, is married ${person.isMarried}")
}
2.3 自定义访问器
class Rectangle(val height: Int, val width: Int) {
// 自定义一个内部成员属性,修改它的getter访问
val isSquare get(): Boolean {
return height == width
}
}
等价于
class Rectangle(val height: Int, val width: Int) {
val isSquare get(): Boolean = height == width
或
val isSquare get() = height == width
}
fun main(args: Array<String>) {
val rectangle = Rectangle(41, 41)
println(rectangle.isSquare)
}
类的属性我们也可以自定义修改 getter 和 sette:
class Hello {
var string: String? = null
// 修改属性的 getter
get() {
return field + " modify getter"
}
// 修改属性的 setter
set(value) {
field = value + " modify setter"
}
// val 声明的属性只能重写 getter
val string2: String? = null
get() {
return field + " modify getter"
}
}
需要注意的是,声明为 val 并不代表变量是一个常量,而是该变量是 final 声明的不可重新赋值的变量,但是我们还是可以用修改 getter 的方式修改变量的值:
class Person(var birthYear: Int) {
val age: Int
get() {
return Calendar.getInstance().get(Calendar.YEAR) - birthYear
}
// 模拟一年后
fun oneYearsLater() {
birthYear--
}
}
fun main(args: Array<String>) {
val person = Person(1990)
println(person.age)
person.oneYearsLater() // Person 对象的 birthYear 被修改
println(person.age) // 因 birthYear 可变变量被修改,age 重新计算获取的 getter 值也被修改
}
输出结果:
28
29
3 表示和处理选择:枚举和"when"
3.1 声明枚举
// 枚举
// java只有一个enum关键字声明枚举,kotlin需要enum class,因为enum在kotlin中是软关键字,只有出现在class前面才有特殊意义
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
enum class Color(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORANGE(255, 165, 0); // 下面有方法时,要用分号隔断
fun rgb() = (r * 256 + g) * 256 + b // 枚举定义方法
}
3.2 使用"when"处理枚举
when
可以实现java的 switch
判断 ,switch只能判断枚举常量、字符串或者数字字面值,但when可以传入任何对象,只要匹配到when下面的分支即返回结果,且when下面的分支也可以是表达式
fun getMnemonic(color: Color) =
when (color) {
Color.RED -> "Richard" // 匹配到就直接返回"->"后面的值或表达式
COLOR.ORANGE -> "Of"
.....
}
fun getWarmth(color: Color) =
when(color) {
Color.RED, Color.ORANGE -> "warn" 多个匹配到,用","分隔
.....
}
fun main(args: Array<String>) {
println(getMnemonic(Color.BLUE))
}
3.3 在"when"结构中使用任意对象
// setOf(c1, c2)表示根据传入的参数创建一个set集合
// 在when语句块对比两个元素相同的集合
fun mix(c1: Color, c2: Color) =
when(setOf(c1, c2)) {
setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
setOf(Color.BLUE, Color.VIOLET) -> Color.INDIGO
else -> throw Exception("Dirty color") // 匹配不到
}
3.4 使用不带参数的"when"
// when没有传入参数,在代码块中表达式判断
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE
....
}
3.5 智能转换:合并类型检查和转换
interface Expr // 接口Expr
// 对象Num实现Expr接口
// 相当于java的public class Num implements Expr
class Num(val value: Int): Expr
// 对象Sum实现Expr接口
// 相当于java的public Sum implements Expr
class Sum(val left: Expr, val right: Expr): Expr
// is相当于java的instanceof
fun eval(e: Expr) : Int {
if (e is Num) {
val n = e as Num // 强制转换
return n.value
}
// kotlin对类型是会自动转换且不需要再使用其他变量强制转换
// 将e Expr自动转化为Sum,直接使用Sum的属性
if (e is Sum) {
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
}
等价于
fun eval(e: Expr): Int {
if (e is Num) {
e.value
}
if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
}
注意:智能转换只在 is
(is
关键字相当于java的 instanceof
)检查且之后不再发生变化的情况下,且属性必须为val属性,并且不能有自定义的访问器【即上例中的函数参数e在is判断后转换为Num或Sum类型后,在后续的代码中不会再进行其他类型的转换】。
或许你会觉得奇怪,为什么我们使用 is
判断后就能智能转换直接调用了?其实将kotlin反编译为java可以发现,在使用上和java是一致的,它还是会将类型强制转换后再调用:
public final int eval(@NotNull ExampleUnitTest.Expr e) {
Intrinsics.checkParameterIsNotNull(e, "e");
int var10000;
if (e instanceof Num) {
// 实际还是强制转换了类型
var10000 = ((Num)e).getValue();
} else {
if (!(e instanceof Sum)) {
throw (Throwable)(new IllegalArgumentException());
}
// 实际还是强制转换了类型
var10000 = this.eval(((Sum)e).getLeft()) + this.eval(((Sum)e).getRight());
}
return var10000;
}
3.6 重构:用"when"代替"if"
fun eval(e: Expr): Int =
when(e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else -> throw IllegalArgumentException("Unknown expression")
}
3.7 代码块作为"if"和"when"的分支
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num:${e.value}")
e.value
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLoggin(e.right)
println("sum: $left + $right")
left + right
}
else -> throw IllegalArgumentException("Unknown expression")
}
fun main() {
evalWithLogging(Num(10))
evalWithLogging(Sum(Num(10), Num(10)))
}
4 迭代:"while"循环和"for"循环
while
和 do while
和java一样
4.1 迭代数字:区间和数列
kotlin为了替代最常见的循环,使用了区间的概念,用 ..
运算符代表
val oneToTen = 1..10 // 代表区间范围1到10
fun fizzBuzz(i: Int) =
when {
i % 15 == 0 -> "FizzBuzz"
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "$i"
}
// 无论是循环list、map,都需要使用关键字in
// 循环从1开始到100,[1,100]都是闭区间包含数值
for (i in 1..100) {
print(fizzBuzz(i))
}
// 半开区间[1, 100)不包含100,相当于i in 1..100-1
for (i in 1 until 10) {
println(fizzBuzz(i))
}
// 倒序输出结果,从10到1
for (i in 10 downTo 1) {
println(i)
}
// 闭区间[1, 10],每个循环迭代步径为2,即+=2
for (i in 1..10 step 2) {
println(i)
}
// 遍历对象列表
val users = arrayListOf(...)
for (user in users) {
println(user)
}
// 闭包 println(it) 重复执行次数,这里指定循环10次
repeat(10) {
println(it)
}
4.2 迭代map
val binaryReps = TreeMap<Char, String>() // 创建一个TreeMap
// 循环字符区间A到F
for (c in 'A'..'F') {
// map存储,key为c(即key为字符A到F),value为binary(即value为字符A到F到二进制数)
binaryReps[c] = Integer.toBinaryString(c.toInt())
}
// 将键给letter,值给binary
for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}
// 跟踪下表index
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
println("$index:$element")
}
4.3 使用"in"检查集合和区间的成员
// c in 'a'..'z'相当于java的c >= a && c <= z
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
fun recognize(c: Char) = when(c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know..."
}
// 相当于"kotlin" >= "Java" && "kotlin" <= "Scala"
// 这里字符串是按照字母表顺序进行比较的,因为String就是这样实现Comparable接口的
println("kotlin" in "Java".."Scala")
5 kotlin中的异常
与java一样的抛异常,只是没有 new
关键字,kotlin创建对象不需要new
throw IllegalArgumentException()
5.1 “try”、“catch”、“finally”
// Int?不必显式地指定抛出的异常类型
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
return null
} finally {
reader.close()
}
}
try-catch在kotlin也是有返回值的,可以简化为
fun readNumber(reader: BufferedReader): Int? {
return try {
val line = reader.readLine()
Integer.parseInt(line)
} catch (e: NumberFormatException) {
null
} finally {
reader.close()
}
}
val reader = BufferedReader(StringReader("23"))
println(readNumber(reader))
5.2 "try"作为表达式
try可以作为表达式赋值返回
fun readNumber(reader: BufferReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch(e: NumberFormatException) {
// return // 如果抛出异常,执行return后不会再往下执行
null // 如果抛出异常,返回null
}
println(number)
}