文章目錄
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。使用不可變引用、不可變對象及無副作用的函數讓你的代碼更接近函數式編程風格。
val message: String
if (canPerformOperation) {
message = "Success"
} else {
message = "Failed"
}
// 儘管val引用自身是不可變的,但是它指向的對象可能是可變的
val languages = arrayListOf("java")
languages.add("kotlin") // 改變引用指向的對象
// 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 字符串模板
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "kotlin"
// $表示字符串模板,如果要在字符串輸出$符號,要使用轉義字符"\"
// 在字符串模板中也可以使用表達式,使用"{}"括起來
println("Hello, $name");
println("Hello, ${args[0]}")
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)
}
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"結構中使用任意對象
// 對比when的setOf(c1, c2)表達式的輸出結果
fun mix(c1: Color, c2: Color) =
when(setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> 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
} else 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")
}
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"
}
// 循環從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 size) {
print(fizzBuzz(i))
}
// 遍歷對象列表
val users = arrayListOf(...)
for (user in users) {
print(user)
}
4.2 迭代map
val binaryReps = TreeMap<Char, String>() // 創建一個TreeMap
// 循環字符區間A到F
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt)
binaryReps[c] = binary // map存儲,key爲c,value爲binary
}
// 將鍵給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: BufferReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
return null
} finally {
reader.close()
}
}
val reader = BufferReader(StringReader("23"))
println(readNum(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)
}