Kotlin學習與實踐 (一)基礎
阿新 • • 發佈:2018-01-27
eat 代碼塊 數據 eas 特性 neu 簡潔 跟著 pla
1、 函數和變量
直奔主題不啰嗦
* a.關鍵字 fun 用來聲明函數。
* b.參數的類型寫在參數名字的後面。
* c.函數可以定義再文件的最外層,不需要把它放入類中。
* d.數組就是類。 和Java不同Kotlin沒有聲明數組類型的特殊語法。
* f.使用println代替了System.out.println。 Kotlin標準庫給Java的標準庫函數提供了許多語法更簡潔的包裝,而println就是其中一個。
* g.和許多現代語言一樣,Kotlin可以省略每行代碼結尾的分號
fun main(args: Array<String>) { println("Hello Kotlin !") println(max(6, 8)) }
* 參數的類型和方法返回值的類型都聲明在後面,中間用:隔開
* 沒有返回值的方法聲明的時候就不需要再方法末尾加上返回值類型
* 函數(方法)聲明的總結:
* 使用 “fun” 來聲明函數,函數名稱緊隨其後,括號括起來的是參數列表,參數列表的後面跟著返回類型,返回類型和參數列表之間用":"隔開。
fun max(a: Int, b: Int): Int { return if (a > b) a else b }
* 語句和表達式:
* 在Kotlin中,if是表達式,而不是語句。語句和表達式的區別在於:
* 表達式有值,並且可以作為另一個表達式的一部分使用;
* 而語句總是包圍著它的代碼塊中的頂層元素,並沒有自己的值。
* 再Java中所有的控制語句都是語句,而再Kotlin中,除了循環(for\do\while)以外的大多數控制結構都是表達式。
* 另一方面,Java中的賦值語句是表達式,在Kotlin中反而變成了語句。
* 表達式函數體
* 函數體由單個表達式構成的,可以用這個表達式作為完成的函數體,並去掉花括號和return。
* 如果函數體寫在花括號內,我們說這個函數有代碼塊體。如果它直接返回了一個表達式,他就是表達式體。
fun max2(a: Int, b: Int): Int = if (a > b) a else b
* 對於表達式函數來說,編譯器會分析作為函數體的表達式,並把它的類型作為函數的返回類型,即使沒有顯式地寫出來。
* 這種分析通常被稱作“類型推導”
* 註意:只有表達式函數的返回類型可以省略。對於有返回值的代碼塊體函數,必須顯式地寫出返回類型和return語句。
fun max3(a: Int, b: Int) = if (a > b) a else b fun min(a: Int, b: Int) = if (a < b) a else b fun min2(a: Int, b: Int): Int = if (a < b) a else b fun min3(a: Int, b: Int): Int { return if (a < b) a else b }
2、變量
* 在Java中要先聲明變量的類型 然後加上關鍵字 最後是變量的名稱 例如 public String name = "Mauiie"
* 在Kotlin 中聲明變量的時候以關鍵字開始,然後是變量名稱,最後加上變量類型(類型可以省去) 例如 val/var name:String = "Mauiie"
* 和表達式一樣如果不聲明變量的類型,Kotlin 會自動分析變量的類型 類型推導 例如 var/val name = "Mauiie"
val answer2: Int = 50 //省略寫法 val question = "this is a String variate" val answer = 42 fun test() { //如果聲明的變量沒有初始化器,就必須顯示的聲明他的類型 val testAnswer: Int testAnswer = 42 }
* Kotlin 中聲明變量有兩個關鍵字
*
* val (來自value) -- 不可變引用。使用val 聲明的變量不可以再次被賦值. 相當於Java 中的 final 變量
* var (來自variable) -- 可變引用。 這種變量的值可以被改變. 相當於Java中的非 final 變量
* 盡管 var 允許變量改變自己的值,但是不能改變變量的類型。
/* *在定義了val 變量的代碼塊執行期間,val變量只能進行唯一一次初始化,但是如果編譯器能確保唯一一條初始化語句會被執行 * 可以根據條件使用不同的值來初始化它 * */ fun testValWithMoreThanOneValue(a: Int) { val message: String if (a < 5) { message = "a > 10" } else if (a in 6..14) { message = "6 <= a <= 14" } else { message = "a > 15"; } } /* * 另外盡管 val 引用自身是不可變的,但是他指向的對象是可變的,這點和Java 的final一樣,例如 * */ fun testValChange() { val languages = arrayListOf("Java") languages.add("PHP") }
3、package
* 在Kotlin中 每一個Kotlin文件都能以一條 package語句開頭,而文件中的所有的聲明(類、函數、屬性)都在這個包中
* 如果其他文件中定義的也是同一個包,這個文件則可以直接使用它們 如果package不一樣則需要導入(import)
* Kotlin 不區分導入的是類還是函數,而且它允許使用 import 關鍵字導入任何種類的聲明。可以直接導入頂層函數的名稱
* Kotlin 中包層級結構不需要遵循目錄層級結構 但是大多數情況下 我們還是要遵循目錄層級關系的
package com.mauiie.kotlin.chapter1basic import java.util.Random class Rectangle1(val height: Int, val width: Int) { val isSquauare: Boolean get() = height == width } fun createRectangle(): Rectangle1 { val random = Random() return Rectangle1(random.nextInt(), random.nextInt()) } fun main(args: Array<String>) { println(createRectangle().isSquauare) }
4、字符串模板
* 字符串模板Kotlin可以讓你再字符串字面值中引用局部變量,只需要再局部變量前加上字符 $ ,
* 而且不僅僅限於變量,還可以引用表達式,只要把表達式使用花括號括起來
fun main(args: Array<String>) { val name = if (args.size > 0) args[0] else "Kotlin" println("Hello $name") } fun testStringTemplate(): String { val strings = arrayListOf<String>() strings.add("jaja") strings.add("yihuihuijia") val templates = "haha , ${if (strings.isEmpty()) "dsa" else "13245"} ! jiu dian ban xia ban hui jia" return "Hello , ${strings.size}" }
5、類和屬性
* Java中bean或者pojo類(只有數據沒有其他代碼)通常被稱為值對象
* 再Kotlin中,public 是默認可見性,所以在聲明類的時候可以省略 public 下面就是Java和Kotlin聲明值對象的對比
//Java //public class Persin{ // private final String name; // // public Persion(String name){ // this.name = name; // } // public String getName(){ // return name; // } // //} class Person(val name: String)
* 在Kotlin中,字段和其訪問器的組合常常被叫做屬性。
* 再Kotlin中屬性是頭等語言特性,完全代替了字段和訪問器方法。在類中聲明一個屬性和聲明一個變量一樣:使用var 和 val。
* 聲明var對象是可變的(既有getter 又有 setter),聲明的val對象是只讀的(只有 getter)。
class Student( val name: String, var isMerried: Boolean )
類的使用
* 構造省去 Java中的new 關鍵字,直接跟構造函數
* 直接訪問或者操作屬性 不用調用 getter 或者 setter方法
fun useClass() { val persin = Person("Bob") val student = Student("John", false) student.isMerried = true // student.name = "dsa" val 不能修改 println(persin.name) println(student.name + student.isMerried) }
自定義訪問器
* 下面的類中的屬性 isSquare 不需要字段來保存它的值。它只有一個自定義的實現的 getter,它的值每次都是算出來的
class Rectangle(val height: Int, val width: Int) { val isSquare: Boolean get() { return height == width } }
6、enum和when
* Kotlin 使用 enum class 兩個關鍵字來聲明枚舉類 而 Java中只需要一個enum關鍵字
* Kotlin 中 enum 是一個軟關鍵字,它必須和 class一起才有特殊意義,在其他地方仍可以使用做變量名。
enum class Color1 { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
* 和Java一樣,Kotlin 枚舉並不只是值的列表,可以給枚舉聲明屬性和方法
enum class Color(val r: Int, val g: Int, val b: Int) { RED(255, 0, 0), ORANGE(155, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238); //這是Kotlin中唯一使用分號的地方:如果要在枚舉中定義任何方法,就要使用分號把枚舉常量列表和方法定義分開 fun rgb() = (r * 256 + g) + 256 + b }
* Kotlin中的when 語句相當於 Java中的 switch 語句
* 和 if 相似 Kotlin 語句也是表達式,因此可以寫一個直接返回when 表達式的表達式體函數
* 而且還不需要再分支語句後面寫上break 語句
fun getMnemonic(color: Color) = when (color) { Color.RED -> "Richard" Color.ORANGE -> "Of" Color.YELLOW -> "York" Color.GREEN -> "Gave" Color.BLUE -> "Battle" Color.INDIGO -> "In" Color.VIOLET -> "Vain" }
* 多個值合並到一個分支語句的時候可以直接使用逗號分隔開
fun getWarmth(color: Color) = when (color) { Color.RED, Color.ORANGE, Color.YELLOW -> "warm" Color.GREEN -> "neutral" Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold" }
* 導入Color 的枚舉常量 之後可以直接使用
import com.mauiie.kotlin.chapter1basic.Color.* fun getCold(color: Color) = when (color) { RED, ORANGE, YELLOW -> "warm" GREEN -> "neutral" BLUE, INDIGO, Color.VIOLET -> "cold" }
* 和Java的switch語句不一樣之處在於,switch語句必須使用常量(枚舉常量、字符串或者數字字面值)
* 而Kotlin允許使用任何對象
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") } }
不帶參數的“when”
*如果沒有給when表達式參數,分支條件就是任意的布爾表達式。
* mixOptimized 與 mix 做的是同樣的事 不帶參數的優點是不會創建多余的對象(Set),缺點是可讀性大大降低
fun mixOptimized(c1: Color, c2: Color) { when { (c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE (c1 == YELLOW && c2 == BLUE) || (c1 == BLUE && c2 == YELLOW) -> GREEN (c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO else -> throw Exception("Dirty color") } }
7、智能轉換:合並類型檢查和轉換
/** * Expr 是一個沒有聲明任何方法的接口 * 它只是一個標記接口,用來給不用的表達式提供一個公共的類型 */ interface Expr /** * Num 是接口Expr的一個實現類 實現接口使用一個冒號(:) + 接口 既可標記一個類實現了一個接口 */ class Num(val value: Int) : Expr class Sum(val left: Expr, val right: Expr) : Expr
* 在Kotlin中,使用 is 檢查來判斷一個變量是否是某個類型 它和Java的instanceOf 相似
* 在Java中如果你已經檢查過一個變量是某種類型並且要把它當做這種類型來訪問其成員時,在instanceOf檢查之後還需要顯式地加上類型轉換。
* 如果最初的變量會使用超過一次,常常選擇把類型轉換之後的結果存儲在另一個單獨的變量中
* 在Kotlin中,編譯器幫你完成了上述的類型轉化工作: 當你檢查過一個變量是某種類型,後面就不需要轉換它,可以就把它當做剛檢查過的類型使用
* (事實上編譯器為你執行了類型轉換) 這種行為就稱之為 智能轉換
fun eval(e: Expr): Int { if (e is Num) { val n = e as Num return n.value } if (e is Sum) { return eval(e.left) + eval(e.right) } throw IllegalArgumentException("Unknown expression ") }
智能轉換只在變量經過is檢查之後不再發生變化的情況下有效!當你對一個類的屬性進行只能轉換的時候,就想上面的例子一樣這個屬性必須是val屬性,而且不能有自定義的訪問器,否則每次對屬性的訪問都能返回同樣的值將無從驗證
* Kotlin的if(a>b) a else b 和Java的 a>b?a:b 一樣
* Kotlin 沒有三元運算符,因為if表達式有返回值,這一點和Java不同
* 所有我們可以把上面的 eval 方法改造成表達式函數 (使用表達式體語法去掉return和花括號使用if表達式作為函數體)
fun Eval(e: Expr): Int = if (e is Num) { e.value } else if (e is Sum) { Eval(e.left) + Eval(e.right) } else { throw IllegalArgumentException("Unknown expression ") }
* 如果if 分支中只有一個表達式,花括號也是可以省略的,如果if 分支是一個代碼塊,代碼塊中的最後一個表達式會被作為結果返回
fun EVAL(e: Expr): Int = if (e is Num) e.value else if (e is Sum) EVAL(e.left) + EVAL(e.right) else throw IllegalArgumentException("Unknown expression ")
* when 表達式不僅可以用於檢查值是否相等 還允許檢車實參值的類型(類型檢查也應用了一次智能轉換所以不需要額外的類型轉換就可以訪問Sum和Num的成員變量)
fun evalByWhen(e: Expr): Int = when (e) { is Num -> e.value is Sum -> eval(e.left) + eval(e.right) else -> throw IllegalArgumentException("Unknown expression") }
* if 和 when 都可以使用代碼塊作為分支體。 這種情況下代碼塊中的最後一個表達式就是結果。
* “代碼塊中最後的表達式就是結果” 再所有使用代碼塊並期望得到一個結果的地方都成立!比如 try catch、lambda
* 但是,但是,但是:這個規則對常規的函數不成立!!!! 一個函數要麽具有不是代碼塊的表達式函數體,要麽具有包含顯示return語句的代碼塊函數體
fun evalWithLogging(e: Expr): Int = when (e) { is Num -> { println("num :${e.value}") e.value } is Sum -> { val left = evalWithLogging(e.left) val right = evalWithLogging(e.right) println("num :$left + $right") println("num :${evalWithLogging(e.left) + evalWithLogging(e.right)}") left + right } else -> { println("IllegalArgumentException") throw IllegalArgumentException("Unknown expression") } }
8、循環叠代
* Kotlin中沒有常規的JAVA 的for循環,在這種循環中先初始化一個變量,再循環的每一步更新變量的值,並在值滿足一定條件的時候退出循環。
* 為了替代Java的這常規的for循環,Kotlin退出了“區間”的概念
* 區間本質上就是兩個值之間的間隔,這兩個值通常是數字,一個是起始值,一個是結束值,中間使用..運算符來標識區間
* 註意Kotlin的區間是包含(閉合)的,意味著第二個值始終是區間的一部分
//整數區間能做的最基本的事就是循環叠代其中所有的值。 如果你能叠代區間中所有的值,這樣的區間稱作數列 val oneToTen = 1..10
為了後面的例子先給出一個基礎函數
fun fizzBuzz(i: Int) = when { i % 15 == 0 -> "FizzBuzz" i % 3 == 0 -> "Fizz" i % 5 == 0 -> "Buzz" else -> "$i" }
* 最簡單的叠代
fun easyIteration() { for (i in 1..100) { println(fizzBuzz(i)) } }
* 叠代一個帶步長的區間 步長為2,每次正向(遞增)2, 步長還可以為負值(遞減)
fun iterationWithStep() { for (i in 1..100 step 2) { println("this is $i") println(fizzBuzz(i)) } }
* 叠代map for循環允許展開叠代集合的元素(在這個例子中:展開的是map鍵值對集合)。
* 把展開的結果存儲到了兩個變量中:letter 存儲鍵,binary 存儲值
fun mapIteration() { val binaryReps = TreeMap<Char, String>() for (c in ‘A‘..‘Z‘) { val binary = Integer.toBinaryString(c.toInt()) binaryReps[c] = binary //根據鍵來訪問個更新map的簡明語法 : 使用map[key] 來讀取值,使用map[key] = value 來設置它的值 } for ((letter, binary) in binaryReps) { println("$letter = $binary") } }
* 同樣展開叠代的方法一樣可以用來叠代集合的同事追蹤當前項的下標,不需要像Java 一樣建一個單獨的變量來存儲它
fun listIteration() { val sList = arrayListOf("10", "11", "1001") for ((index, element) in sList.withIndex()) { println("$index : $element") } }
關鍵字 in 除了能像上面的例子中用於叠代 in 還可以用來檢查區間或者幾個是否包含了某個值
* 使用in關鍵字來檢查一個值是否在區間(集合)中,或者它的逆運算,!n 來檢查這個值是否不在區間(集合)中
fun isLetter(c: Char) = c in ‘a‘..‘z‘ || c in ‘A‘..‘Z‘ //可以變換成 a <= c && c >= z fun isNotDigit(c: Char) = c !in ‘0‘..‘9‘
* in 和 !in 運算也適用於when
fun recognize(c: Char) = { when (c) { in ‘0‘..‘9‘ -> "It‘s a digit" // in ‘a‘..‘z‘ -> "It‘s a letter" // in ‘A‘..‘Z‘ -> "It‘s a letter" in ‘a‘..‘z‘, in ‘A‘..‘Z‘ -> "It‘s a letter" else -> "I don‘t konw" } }
註意 : 區間不僅限於字符。 假如有一個支持實例比較的任意類(實現了 java.lang.Comparable 接口),就能創建這樣的區間。
如果是這樣的區間,並不能列舉出這個區間中的所有對象
同樣 in 檢查也適用於集合
fun testIn() { println("Kotlin" in "Java" .. "Scala") println("Kotlin" in setOf("Java","Kotlin","Swift","C")) }
9、異常
* Kotlin 的異常處理和Java以及其他許多語言的處理方法相似。一個函數可以正常結束也可以在錯誤的情況下拋出異常。
* 方法的調用者可以捕獲到這個異常並且處理它,如果不處理異常會沿著調用棧再次拋出
* 和所有其他類一樣,不必使用new 關鍵字來創建異常實例。
* 和Java不同的是,Kotlin中throw 結構是一個表達式,能作為另一個表達式的一部分使用
fun testThrow(percentage: Int) = if (percentage in 0..100) "right" else if (percentage in 101..105) throw IllegalArgumentException("a percentage value must be between 0 and 100 $percentage") else throw IOException("xxx")
和Java最大的區別是 throws 子句沒有出現在代碼中: 如果用Java來寫上面的函數必須要顯示的在函數聲明後面寫上 throw xxException ,這樣做的原因是Java中IOException 是受檢異常,在Java中受檢異常必須顯示地處理。->必須聲明你的函數所能拋出的異常。
如果調用另一個函數需要處理這個函數的受檢異常,或者聲明你的函數也能拋出這個異常Kotlin並不區分受檢異常和未受檢異常。不用制定函數拋出的異常,而且可以選擇處理或者不處理異常。
這種設計是基於Java中使用異常的事件得出的,經驗顯示這些Java規則總是引起許多無意義的異常拋出,而這些規則不能總是保護你的代碼免受可能發生的錯誤。
* try 可以作為表達式.
* Kotlin中的try關鍵字就像 if 和 when 一樣,引入了一個表達式,可以把它返回或者賦值給別的變量,
* 不同於if try 的主體總需要用花括號括起來,和其他表達式一樣如果主體包含多個表達式最後一個表達式就是整個try表達式的值
fun tryTest(reader: BufferedReader) { val number = try { val readLine = reader.readLine() Integer.parseInt(readLine) } catch (e: NumberFormatException) { return //如果執行到此處就返回不會再往下執行了 } println("number is $number") } fun tryTest2(reader: BufferedReader) { val number = try { val readLine = reader.readLine() Integer.parseInt(readLine) } catch (e: NumberFormatException) { null //如果執行到此處會給 number 復制 null 然後繼續往下執行 } println("number is $number") }
Kotlin學習與實踐 (一)基礎