Kotlin入門系列:第一章 Kotlin基礎

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"

一般開發中需要使用到 valvar 的情況:

  • 在函數中聲明一個臨時變量或調用其他函數返回給臨時變量時,如
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")
	}
}

注意:智能轉換隻在 isis 關鍵字相當於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"循環

whiledo 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)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章