最近在學kotlin,這是本人看菜鳥教程後所做的筆記,很多會內容和菜鳥教程重複,僅供參考
基礎語法
包聲明
//類在包的位置 package com.demo.main import java.util.*
函數定義
函數定義使用關鍵字 fun,參數格式爲:參數 : 類型
fun printSum(a: Int,b: Int){ println(a+b) }
無返回值的函數
public fun printSum(a: Int, b: Int) { print(a + b) }
可變長參數函數
fun vars(vararg a:String){ for(str in a){ println(str) } }
定義常量與變量
可變變量定義:var 關鍵字
var <標識符> : <類型> = <初始化值>
不可變變量定義:val關鍵字,只能賦值一次的變量Java中final修飾的變量)
val <標識符> : <類型> = <初始化值>
註釋
// 這是一個單行註釋
/* 這是一個多行的 塊註釋。 */
字符串模板
$ 表示一個變量名或者變量值
$varName 表示變量值
${varName.fun()} 表示變量的方法返回值:
數據類型
類型 | 位寬度 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
比較兩個數字
在 Kotlin 中,三個等號 === 表示比較對象地址,兩個 == 表示比較兩個值大小。
類型轉換
由於不同的表示方式,較小類型並不是較大類型的子類型,較小的類型不能隱式轉換爲較大的類型。 這意味着在不進行顯式轉換的情況下我們不能把 Byte 型值賦給一個 Int 變量。
val b: Byte = 10 // OK, 字面值是靜態檢測的 val i: Int = b // 錯誤
我們可以代用其toInt()方法。
val b: Byte = 1 // OK, 字面值是靜態檢測的 val i: Int = b.toInt() // OK
每種數據類型都有下面的這些方法,可以轉化爲其它的類型:
toByte(): Byte toShort(): Short toInt(): Int toLong(): Long toFloat(): Float toDouble(): Double toChar(): Char
有些情況下也是可以使用自動類型轉化的,前提是可以根據上下文環境推斷出正確的數據類型而且數學操作符會做相應的重載。例如下面是正確的:
val l = 1L + 3 // Long + Int => Long
條件控制
IF 表達式
一個 if 語句包含一個布爾表達式和一條或多條語句。
var max = a if (a < b) max = b // 使用 else var max: Int if (a > b) { max = a } else { max = b } // 作爲表達式 val max = if (a > b) a else b
When 表達式
when最簡單的形勢如下:
fun whenTest(a : Int){ when(a){ 1->{ println(1) } 2->{ println(2) } 3->{ println(3) } else->{ println("其他") } } }
when 也可以用來取代 if-else if鏈。 如果不提供參數,所有的分支條件都是簡單的布爾表達式,而當一個分支的條件爲真時則執行該分支:
when { x.isOdd() -> print("x is odd") x.isEven() -> print("x is even") else -> print("x is funny") }
循環控制
For 循環
循環體可以是一個代碼塊:
for (item: Int in ints) { // …… }
while 與 do...while 循環
while是最基本的循環,它的結構爲:
while( 布爾表達式 ) { //循環內容 }
do…while 循環 對於 while 語句而言,如果不滿足條件,則不能進入循環。但有時候我們需要即使不滿足條件,也至少執行一次。
do…while 循環和 while 循環相似,不同的是,do…while 循環至少會執行一次。
do { //代碼語句 }while(布爾表達式);
返回和跳轉
Kotlin 有三種結構化跳轉表達式:
- return。默認從最直接包圍它的函數或者匿名函數 返回。
- break。終止最直接包圍它的循環。
- continue。繼續下一次最直接包圍它的循環。
類和對象
類定義
Kotlin 中使用關鍵字 class 聲明類,後面緊跟類名:
class Runoob { // 類名爲 Runoob // 大括號內是類體構成 }
也可以定義一個空類:
class Empty
屬性定義
類的屬性可以用關鍵字 var 聲明爲可變的,否則使用只讀關鍵字 val 聲明爲不可變。
我們可以像使用普通函數那樣使用構造函數創建類實例:
val site = Runoob() // Kotlin 中沒有 new 關鍵字
要使用一個屬性,只要用名稱引用它即可
site.name // 使用 . 號來引用 site.url
主構造器
主構造器中不能包含任何代碼,初始化代碼可以放在初始化代碼段中,初始化代碼段使用 init 關鍵字作爲前綴。
class Person constructor(firstName: String) { init { println("FirstName is $firstName") } }
次構造函數
類也可以有二級構造函數,需要加前綴 constructor:
class Person { constructor(parent: Person) { parent.children.add(this) } }
如果類有主構造函數,每個次構造函數都要,或直接或間接通過另一個次構造函數代理主構造函數。在同一個類中代理另一個構造函數使用 this 關鍵字:
class Person(val name: String) { constructor (name: String, age:Int) : this(name) { // 初始化... } }
抽象類
抽象是面向對象編程的特徵之一,類本身,或類中的部分成員,都可以聲明爲abstract的。抽象成員在類中不存在具體的實現。
abstract class father{ abstract fun f() } class childs : father() { override fun f() { } }
嵌套類
class Outer { // 外部類 private val bar: Int = 1 class Nested { // 嵌套類 fun foo() = 2 } } fun main(args: Array<String>) { val demo = Outer.Nested().foo() // 調用格式:外部類.嵌套類.嵌套類方法/屬性 println(demo) // == 2 }
內部類
內部類使用 inner 關鍵字來表示。
內部類會帶有一個對外部類的對象的引用,所以內部類可以訪問外部類成員屬性和成員函數。
class Outer { private val bar: Int = 1 var v = "成員屬性" /**嵌套內部類**/ inner class Inner { fun foo() = bar // 訪問外部類成員 fun innerTest() { var o = this@Outer //獲取外部類的成員變量 println("內部類可以引用外部類的成員,例如:" + o.v) } } } fun main(args: Array<String>) { val demo = Outer().Inner().foo() println(demo) // 1 val demo2 = Outer().Inner().innerTest() println(demo2) // 內部類可以引用外部類的成員,例如:成員屬性 }
匿名內部類
使用對象表達式來創建匿名內部類:
class Test { var v = "成員屬性" fun setInterFace(test: TestInterFace) { test.test() } } /** * 定義接口 */ interface TestInterFace { fun test() } fun main(args: Array<String>) { var test = Test() /** * 採用對象表達式來創建接口對象,即匿名內部類的實例。 */ test.setInterFace(object : TestInterFace { override fun test() { println("對象表達式創建匿名內部類的實例") } }) }
類的修飾符
類的修飾符包括 classModifier 和accessModifier:
- classModifier: 類屬性修飾符,標示類本身特性。
abstract // 抽象類 final // 類不可繼承,默認屬性 enum // 枚舉類 open // 類可繼承,類默認是final的 annotation // 註解類
- accessModifier: 訪問權限修飾符
private // 僅在同一個文件中可見 protected // 同一個文件中或子類可見 public // 所有調用的地方都可見 internal // 同一個模塊中可見
繼承
Kotlin 中所有類都繼承該 Any 類,它是所有類的超類,對於沒有超類型聲明的類是默認超類:
class Example // 從 Any 隱式繼承
Any 默認提供了三個函數:
equals() hashCode() toString()
注意:Any 不是 java.lang.Object。
如果一個類要被繼承,可以使用 open 關鍵字進行修飾。
open class father(p: Int) // 定義基類 class Derived(p: Int) : father(p)
構造函數
如果子類有主構造函數, 則基類必須在主構造函數中立即初始化。
open class Person(var name : String, var age : Int){// 基類 } class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) { } // 測試 fun main(args: Array<String>) { val s = Student("Runoob", 18, "S12346", 89) println("學生名: ${s.name}") println("年齡: ${s.age}") println("學生號: ${s.no}") println("成績: ${s.score}") }
子類沒有主構造函數
如果子類沒有主構造函數,則必須在每一個二級構造函數中用 super 關鍵字初始化基類,或者在代理另一個構造函數。初始化基類時,可以調用基類的不同構造方法。
/**用戶基類**/ open class Person(name:String){ /**次級構造函數**/ constructor(name:String,age:Int):this(name){ //初始化 println("-------基類次級構造函數---------") } } /**子類繼承 Person 類**/ class Student:Person{ /**次級構造函數**/ constructor(name:String,age:Int,no:String,score:Int):super(name,age){ println("-------繼承類次級構造函數---------") println("學生名: ${name}") println("年齡: ${age}") println("學生號: ${no}") println("成績: ${score}") } }
接口
Kotlin 接口與 Java 8 類似,使用 interface 關鍵字定義接口,允許方法有默認實現:
interface MyInterface{ fun bar() fun foo(){ println("foo") } } class MyClass : MyInterface{ override fun bar() { println("bar") } } fun main(args:Array<String>){ var myClass=MyClass() myClass.bar() myClass.foo() }
接口中的屬性
接口中的屬性只能是抽象的,不允許初始化值,接口不會保存屬性值,實現接口時,必須重寫屬性:
interface MyInterface{ var name:String } class MyClass : MyInterface{ override var name="Jack" } fun main(args:Array<String>){ var myClass=MyClass() println(myClass.name) }
函數重寫
實現多個接口時,可能會遇到同一方法繼承多個實現的問題。例如:
interface MyInterface2{ fun foo() fun bar(){ println("bar2") } } class MyClass : MyInterface,MyInterface2{ override fun bar() { super<MyInterface2>.bar() } override fun foo() { println("foo2") } } fun main(args:Array<String>){ var myClass=MyClass() myClass.foo() myClass.bar() }
當一個有實現方法,一個沒有實現方法時,默認是沒有實現方法的, super<MyInterface>.foo()切換實現方法,兩個都有時也可以通過這個方式切換實現方法
泛型
泛型,即 "參數化類型",將類型參數化,可以用在類,接口,方法上。
與 Java 一樣,Kotlin 也提供泛型,爲類型安全提供保證,消除類型強轉的煩惱。
聲明一個泛型類:
class Class<T>(t: T) { var value = t }
型變
Kotlin 中沒有通配符類型,它有兩個其他的東西:聲明處型變(declaration-site variance)與類型投影(type projections)。
聲明處的類型變異使用協變註解修飾符:in、out,消費者 in, 生產者 out。
使用 out 使得一個類型參數協變,協變類型參數只能用作輸出,可以作爲返回值類型但是無法作爲入參的類型:
in 使得一個類型參數逆變,逆變類型參數只能用作輸入,可以作爲入參的類型但是無法作爲返回值的類型:
class demo<out A>(val a: A) { fun foo(): A { return a } } class demo2<in A>(a: A) { fun foo(a: A) { } }
泛型約束
我們可以使用泛型約束來設定一個給定參數允許使用的類型。
Kotlin 中使用 : 對泛型的的類型上限進行約束。
fun <T : Comparable<T>> sort(list: List<T>) { // …… }
枚舉類
枚舉類最基本的用法是實現一個類型安全的枚舉。
枚舉常量用逗號分隔,每個枚舉常量都是一個對象。
enum class Color{ RED,BLACK,BLUE,GREEN,WHITE } fun main(args: Array<String>) { var color:Color=Color.BLUE println(Color.values()) println(Color.valueOf("RED")) println(color.name) println(color.ordinal) }
對象表達式和對象聲明
對象可以繼承於某個基類,或者實現其他接口:
open class A(x: Int) { public open val y: Int = x } interface B { } fun main(args:Array<String>){ val ab: A = object : A(1), B { override val y = 15 } println(ab.y) }
通過對象表達式可以越過類的定義直接得到一個對象:
val site = object { var name: String = "Jack" } println(site.name)
匿名對象可以用作只在本地和私有作用域中聲明的類型。如果你使用匿名對象作爲公有函數的 返回類型或者用作公有屬性的類型,那麼該函數或屬性的實際類型 會是匿名對象聲明的超類型,如果你沒有聲明任何超類型,就會是 Any。在匿名對象 中添加的成員將無法訪問。
class A { // 私有函數,所以其返回類型是匿名對象類型 private fun foo() = object { val x: String = "x" } // 公有函數,所以其返回類型是 Any fun Foo2() = object { val x: String = "x" } fun bar() { val x1 = foo().x // 沒問題 val x2 = publicFoo().x // 錯誤:未能解析的引用“x” } }
對象聲明
Kotlin 使用 object 關鍵字來聲明一個對象。
object Test{ fun foo(){ println("Jack") } } fun main(args:Array<String>){ Test.foo() }
當然你也可以定義一個變量來獲取獲取這個對象,當時當你定義兩個不同的變量來獲取這個對象時,你會發現你並不能得到兩個不同的變量。也就是說通過這種方式,我們獲得一個單例。
object My { var name: String = "JACK" } fun main(args:Array<String>){ var s1 = My var s2 = My s1.name = "HelloJack" println(s1.name) println(s2.name) }
伴生對象
類內部的對象聲明可以用 companion 關鍵字標記,這樣它就與外部類關聯在一起,我們就可以直接通過外部類訪問到對象的內部元素。
class MyClass { companion object Factory { fun create(): MyClass = MyClass() } fun test(){ println("Jack") } } fun main(args:Array<String>){ val instance = MyClass.create() instance.test() }
我們可以省略掉該對象的對象名,然後使用 Companion 替代需要聲明的對象名:
class MyClass { companion object { } } val x = MyClass.Companion
注意:一個類裏面只能聲明一個內部關聯對象,即關鍵字 companion 只能使用一次。
類委託
類的委託即一個類中定義的方法實際是調用另一個類的對象的方法來實現的。
// 創建接口 interface Base { fun print() } // 實現此接口的被委託的類 class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // 通過關鍵字 by 建立委託類 class Derived(b: Base) : Base by b fun main(args:Array<String>){ val b = BaseImpl(10) Derived(b).print() // 輸出 10 }
lazy() 是一個函數, 接受一個 Lambda 表達式作爲參數, 返回一個 Lazy <T> 實例的函數,返回的實例可以作爲實現延遲屬性的委託: 第一次調用 get() 會執行已傳遞給 lazy() 的 lamda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。
val MyLazyValue: String by lazy { println("computed!") // 第一次調用輸出,第二次調用不執行 "Hello" } fun main(args:Array<String>){ println(MyLazyValue) println(MyLazyValue) }
一個常見的用例是在一個映射(map)裏存儲屬性的值。 這經常出現在像解析 JSON 或者做其他"動態"事情的應用中。 在這種情況下,你可以使用映射實例自身作爲委託來實現委託屬性。
class Data(val map: Map<String, Any?>) { val name: String by map val url: String by map } fun main(args:Array<String>){ val site = Data(mapOf( "name" to "Jack1", "url" to "Jack2" )) // 讀取映射值 println(site.name) println(site.url) }