Kotlin學習筆記——歸納整理
目錄
Kotlin簡介來自於著名的IDE IntelliJ IDEA(Android Studio基於此開發) 軟體開發公司 JetBrains(位於東歐捷克)
是一種基於JVM的靜態型別程式語言,2017年穀歌I/O大會確定Kotlin為Android的官方語言
一:基礎語法
1.1 特性
在Kotlin中定義包與Java有點不同,在Kotlin中目錄與包結構無需匹配,
// 包格式 和 java 一致
package com.ymc.hellokotlin
fun main(args: Array<String>) {
println("Hello world")
println(max(2,3))
}
fun max(a:Int ,b:Int):Int{
return if(a>b) a else b;
}
- main 函式不需要在class 中就可以執行
- fun 代表一個函式,後邊緊跟函式名稱,引數列表和返回值型別
- 引數實現寫 引數名稱 然後冒號隔開,再寫引數型別(和java 相反)
- 函式的返回值是在後邊的(和java剛好相反的)當然 有返回值 也可以不寫返回型別(前提是:只有表示式體 函式返回型別可以省略,如果是程式碼塊體函式就必須要 寫明函式返回型別),因為kotlin 通過 型別推導 也是可以知道返回值型別的
- system.out.println 被包裝為 println
- 在行末可以省略 分號 (類似 js)
- 看到max函式中 if類似於三元表示式 kotlin中,if 是有結果值的表示式
- 如果返回值 類似於 java 中的 void 則可以寫成 :Unit ,當然也可以省略不寫
在kotlin 中,除了部分迴圈(for do 和 do/while)大多控制結構都是表示式,是有返回值的。另一方面 java 中的賦值語句為表示式,而kotlin 中則為語句。
與Java定義包名一樣,在原始檔的開頭定義包名:但是不同的是,包名和資料夾路徑可以不一致。
1.2 變數
kotlin 的變數宣告是可以省略型別的,所以kotlin 變數宣告有別於java ,kotlin變數宣告順序為關鍵字變數名稱型別(可不加),如果變數沒有初始化則需要明確表明變數型別。
常量與變數都可以沒有初始化值,但是在引用前必須初始化編譯器支援自動型別判斷,即宣告時可以不指定型別,由編譯器判斷。如果不在宣告的時候初始化則必須提供變數的型別
主要分為兩種定義:
(官方推薦 儘量使用val 宣告變數,使程式更接近函數語言程式設計風格)
- val :不可變引用 ,在val宣告變數後不能再初始化之外再次賦值與java final一致
- var :可變引用 , 該型別變數可以隨便賦值
1.2.1 可變變數的定義: var 關鍵字
var <變數名> : <變數型別> = <初始值>
var sum: Int = 3
sum = 8
//由於編譯支援型別推導可以省略Int型別
var sum = 3
sum = 8
1.2.2不可變變數的定義: val 關鍵字
( 不能進行二次賦值,類似Java中的final型別)
val <常量名> : <常量型別> = <初始值>
val sum: Int //沒有賦值初始化之前必須指定型別
sum = 5
1.2.3 常量
已知值的屬性可以使用 const 修飾符標記為 編譯期常量。需要滿足如下幾種條件 (類似 java 中的 constanUtil 中的 常量值)
- 位於頂層或者是 object 的一個成員
- 用 String 或原生型別 值初始化
- 沒有自定義 getter
const val SUBSYSTEM_KEY: String = "key"
@Deprecated(SUBSYSTEM_KEY) fun foo() { …… }
1.2.4 字串模板
字串可以包含模板表示式,以$開始
val book = Book("Thinking In Java", 59.0f, "Unknown")
val extraValue = "extra"
Log.d("MainActivity", "book.name = ${book.name}; book.price=${book.price};extraValue=$extraValue")
1.3 類與屬性
1.3.1 基礎
在Kotlin中所有類都有一個共同的超類Any
//類定義,繼承類和實現介面
class FeedBackActivity : NativeBaseActivity(), View.OnLongClickListener, BitmapUtil.SaveImageCall {
}
建立類的例項
val invoice = Invoice()
val customer = Customer("Joe Smith")
//注意 Kotlin 並沒有 new 關鍵字。
我們比較一下 java 和 kotlin 中類的寫法的不同
java:
public class DemoBean {
private final String name;
public DemoBean(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Kotlin:
class DemoBean(val name: String)
類是將資料和處理資料的程式碼封裝成一個單一的實體。
class Person{
var name :String = "ymc" // 可讀可寫
val isMarried : Boolean = false // 只讀
}
上段程式碼中 isMarried 會生成一個欄位,一個getter ,而 name 則會生成 一個 getter和一個 setter 。
kotlin 在你宣告屬性的時候,你就聲明瞭對應的訪問器,預設的訪問器 就是返回值的 getter 和 更新數值的 setter ,kotlin 會暴漏一個 getName 方法,當然我們也可以自定義訪問器。
fun main(args: Array<String>) {
var person = Person()
println(person.name +";"+ person.isMarried )
}
可以看到我們直接呼叫而不需要get,感覺很像js…
1.3.2 自定義訪問器
class Person{
var name :String = "ymc"
var sex : Int = 0; // 0 為女性 1為男性
val isMarried : Boolean = false
val isBoy :Boolean
get() {
return if(sex==1) true else false
}
}
將Person 新增 sex 和 isboy 屬性,重寫getter ,這樣就可以做到自定義訪問器。
如果 我們設定屬性為val ,但是通過自定義 getter修改屬性?
fun main(args: Array<String>) {
val name = "Hello Kotlin"
name = "Hello Java"
}
Error:(8, 5) Kotlin: Val cannot be reassigned
如果單純的修改,則會報錯
接下來我們通過 自定義訪問器 看看
class RandomNum {
val num: Int
get() = Random().nextInt()
}
fun main(args: Array<String>) {
println("the num is ${RandomNum().num}")
}
the num is -1251923160
the num is -1527833641
總結: 由以上的例子可以說明假設一是成立的,在Kotlin中的val修飾的變數不能說是不可變的,而只能說僅僅具有可讀許可權。
1.3.3 備用欄位
kotlin 中並不允許使用欄位,這個時候我們就可以使用備用欄位,比如下段程式碼,起到區域性變數的作用。
//初始化值會直接寫入備用欄位
var counter = 0
get() = field // field可以理解為自己本身
set(value) {
if (value >= 0)
field = value
}
// 這種情況並不需要備用欄位,所有不會生成備用欄位
val isEmpty: Boolean
get() = this.size == 0
注意:field識別符號只允許在屬性的訪問器函式內使用.
1.3.4 延遲初始化屬性和變數
屬性宣告為非空型別必須在建構函式中初始化,為處理這種情況,你可以用 lateinit 修飾符標記該屬性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
這樣的話,我們就可以不用再建構函式的時候對其進行初始化,後續在哪裡需要呼叫 setup就可以。
1.4 函式
1.4.1 函式基本方法
Kotlin 中的函式使用 fun 關鍵字宣告,引數即 name: type。引數用逗號隔開
fun double(x: Int): Int {
return 2 * x
}
//Int 返回型別可以省略
fun sum(a: Int, b: Int): Int {
return a + b
}
//也可以
fun sum(a: Int, b: Int) = a + b
減少方法過載
//支援預設引數值,減少方法過載
fun showToast(message: String, duration:Int = Toast.LENGTH_LONG) {
Toast.makeText(this, message, duration).show()
}
//呼叫方式:沒有預設值的引數,呼叫時,必須填寫
showToast("toast");
showToast("toast", Toast.LENGTH_LONG);
Main方法比較
//Java
public static void main(String[] args){
}
//Kotlin
fun main(args:Array<String>){
}
1.4.2 主次建構函式
在Kotlin中的一個類可以有一個主建構函式和一個或多個次建構函式
- 主建構函式:帶有類名的為主建構函式(只有一個)
- 主建構函式不能包含任何的程式碼。初始化的程式碼可以放到以 init 關鍵字作為字首的初始化塊
- 主建構函式中宣告的屬性可以是可變的(var)或只讀的(val)
//如果建構函式有註解或可見性修飾符,這個 constructor 關鍵字是必需的,並且這些修飾符在constructor前面:
class Customer public @Inject constructor(name: String) { …… }
//無修飾可不寫constructor關鍵字
class Customer (name: String) {
var a :Int = 1
init{……}
}
- 次建構函式:不帶類名並且有constructor關鍵字修飾的函式為次建構函式(可以一個或多個),並且只能存在主建構函式程式碼塊之內
//如果類有一個主建構函式,每個次建構函式需要委託給主建構函式, 可以直接委託或者通過別的次建構函式間接委託。委託到同一個類的另一個建構函式用 this 關鍵字即可:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
- 無參建構函式:
- 如果沒有建構函式,將會預設一個無引數建構函式
- 如果主建構函式的所有的引數都有預設值,編譯器會生成一個額外的無參建構函式,它將使用預設值。
//如建構函式為私有,需用private修飾
class DontCreateMe public constructor () {
}
class Customer(val customerName: String = ""){}
1.4.3 函式呼叫
val result = double(2)
Sample().foo() // 建立類 Sample 例項並呼叫 foo
1.5 控制流:if、when、for、while
1.5.1 區間
區間表示式由具有操作符形式 … 的 rangeTo 函式輔以 in 和 !in 形成。
if (i in 1..10) { // 等同於 1 <= i && i <= 10
println(i)
}
//順序
for (i in 1..4) print(i) // 輸出“1234”
for (i in 4..1) print(i) // 什麼都不輸出
//倒序
for (i in 4 downTo 1) print(i) // 輸出“4321”
//要建立一個不包括其結束元素的區間,可以使用 until 函式:
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
//能否以不等於 1 的任意步長迭代數字? 當然沒問題, step() 函式有助於此:每迴圈到第二個元素就剔除
for (i in 1..4 step 2) print(i) // 輸出“13”
for (i in 4 downTo 1 step 2) print(i) // 輸出“42”
1.5.2 if表示式
if和Java用法一致
val max = if (a > b) a else b
1.5.3 when表示式
when它能完全取代switch/case,也可以用來取代 if-else if鏈,並且還有很多新的特性。
如果其他分支都不滿足條件將會求值else分支。 如果 when 作為一個表示式使用,則必須有 else 分支, 除非編譯器能夠檢測出所有的可能情況都已經覆蓋了。
//分支條件可以是:Int,區間,方法,字串,物件等
when (x) {
0, 1 -> print("x == 0 or x == 1")
in 2 -> print("x == 2")
in 3..8 -> print("x == 3..8")
parseInt(s)-> print("parseInt")
is String -> print("x is String")
else -> print("otherwise")
}
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")//智慧轉換
else -> false
}
//when 也可以用來取代 if-else if鏈。 如果不提供引數,所有的分支條件都是簡單的布林表示式,而當一個分支的條件為真時則執行該分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
1.5.4 for 迴圈
for (item in collection) print(item)
//迴圈體可以是一個程式碼塊。
for (item: Int in ints) {
// ……
}
for (i in 1..3) {
println(i)//輸出“123”
}
for (i in 6 downTo 0 step 2) {
println(i)//輸出“6420”
}
//如果想索引遍歷,可以通過indices函式
for (i in array.indices) {
println(array[i])
}
//或者你可以用庫函式 withIndex:
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
//輸出:
the element at 0 is a
the element at 1 is b
the element at 2 is c
1.5.5 While迴圈
while 和 do…while 照常使用
fun main(args: Array<String>) {
println("----while 使用-----")
var x = 5
while (x > 0) {
println( x--)
}
println("----do...while 使用-----")
var y = 5
do {
println(y--)
} while(y>0)
}
1.5.6 跳出迴圈
- return,預設從最直接包圍它的函式或者匿名函式返回。
- break,終止最直接包圍它的迴圈。
- continue,繼續下一次最直接包圍它的迴圈。
1.6 關鍵字,修飾符,特殊符號
關鍵字:
- Any:在Kotlin中所有類都有一個共同的超類Any,Any並不是Object
- open:修飾類:表示能被繼承;修飾方法:表示需要重寫
修飾符:
final、open、abstract、override對比
可見性修飾符:
類、物件、介面、建構函式、方法、屬性和它們的 setter 都可以有 可見性修飾符。
在 Kotlin 中有這四個可見性修飾符:private、 protected、 internal 和 public。
如果沒有顯式指定修飾符的話,預設可見性是 public
internal —— 能見到類宣告的 本模組內 的任何客戶端都可見其 internal 成員
其它和Java一樣
特殊符號:
-
?:表示當前是否物件可以為空
-
!!: 表示當前物件不為空的情況下執行
private var cloth_Rv: RecyclerView ?= null
cloth_Rv!!.setHasFixedSize(true)
1.7 object關鍵字
object關鍵字主要有三種使用場景
- 物件宣告(object declaration)
- 伴生物件(companion object)
- 物件表示式(object expression)
1.7.1 物件宣告(object declaration)
將類的宣告和定義該類的單例物件結合在一起(即通過object就實現了單例模式)
物件宣告中不能包含構造器(包括主構造器和次級構造器)
物件宣告例項解析以及在kotlin和java程式碼中的呼叫
object UserManager {
fun saveUser()
}
// 反編譯出的Java程式碼
public final class UserManager {
public static final UserManager INSTANCE;
public final void saveUser() {
}
private UserManager() {
}
static {
UserManager var0 = new UserManager();
INSTANCE = var0;
}
}
在kotlin和java程式碼中,它們的呼叫方式有點差別:
- kotlin程式碼呼叫:UserManager.saveUser()
- java程式碼呼叫:UserManager.INSTANCE.saveUser();
1.7.2 伴生物件(companion object)
Kotlin給Java開發者帶來最大改變之一就是廢棄了static修飾符。與Java不同的是在Kotlin的類中不允許你宣告靜態成員或方法。相反,你必須向類中新增Companion物件來包裝這些靜態引用: 差異看起來似乎很小,但是它有一些明顯的不同。
在kotlin中是沒有static關鍵字的,也就意味著沒有靜態方法和靜態成員。那麼在kotlin中如果想要表達這種概念,可以使用包級別函式(package-level funcation)和伴生物件(companion object)。
伴生物件語法形式:
class ClassName {
// 伴生物件名可以省略,預設為Companion
companion object 伴生物件名 {
// define field and method
}
}
伴生物件例項解析以及在kotlin和java程式碼中的呼叫:
class App {
companion object {
fun getAppContext() {}
}
}
// 反編譯出的Java程式碼
public final class App {
public static final App.Companion Companion = new App.Companion((DefaultConstructorMarker)null);
public static final class Companion {
public final void getAppContext() {
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
與物件宣告類似,它們在kotlin和java程式碼中的呼叫方式也有點差別:
- kotlin程式碼呼叫:App.getAppContext()
- java程式碼呼叫:App.Companion.getAppContext();
companion:這個關鍵字實際上只是一個快捷方式,允許你通過類名訪問該物件的內容(如果伴生物件存在一個特定的類中,並且只是用到其中的方法或屬性名稱,那麼伴生物件的類名可以省略不寫)。就編譯而言,下面的testCompanion()方法中的三行都是有效的語句。
class TopLevelClass {
companion object {
fun doSomeStuff() {
...
}
}
object FakeCompanion {
fun doOtherStuff() {
...
}
}
}
fun testCompanion() {
TopLevelClass.doSomeStuff()
TopLevelClass.Companion.doSomeStuff()
TopLevelClass.FakeCompanion.doOtherStuff()
}
為了相容的公平性,companion關鍵字還提供了更多選項,尤其是與Java互操作性相關選項。果您嘗試在Java類中編寫相同的測試程式碼,呼叫方式可能會略有不同:
public void testCompanion() {
TopLevelClass.Companion.doSomeStuff();
TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}
區別在於:
Companion作為Java程式碼中靜態成員開放(實際上它是一個物件例項,但是由於它的名稱是以大寫的C開頭,所以有點存在誤導性)
FakeCompanion引用了我們的第二個單例物件的類名。在第二個方法呼叫中,我們需要使用它的INSTANCE屬性來實際訪問Java中的例項(你可以開啟IntelliJ IDEA或AndroidStudio中的"Show Kotlin Bytecode"選單欄,並點選裡面"Decompile"按鈕來檢視反編譯後對應的Java程式碼)
在這兩種情況下(不管是Kotlin還是Java),使用伴生物件Companion類比FakeCompanion類那種呼叫語法更加簡短。此外,由於Kotlin提供一些註解,可以讓編譯器生成一些簡短的呼叫方式,以便於在Java程式碼中依然可以像在Kotlin中那樣簡短形式呼叫。
1.7.3 物件表示式(object expression)內部類/匿名內部類
- 內部類
//如果需要呼叫成員變數,需要用inner修飾內部類
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
- 匿名內部類
//匿名內部類
textView.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
//...
}
})
物件表示式常用來作為匿名內部類的實現:
private val callBack = object : CallBack {
override fun onSuccess() {}
override fun onFailure() {}
}
// 通過物件表示式實現點選事件回撥
btn.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
TODO("Not yet implemented")
}
})
1.8 擴充套件
函式擴充套件可以讓你對任意類進行擴充套件,而不用繼承等等複雜的操作。
對引數的解釋:
- Context:表示函式的接收者,也就是函式擴充套件的物件
- . :擴充套件函式修飾符
- toast:擴充套件函式的名稱或者屬性名稱
1.8.1 擴充套件函式
fun Context.showToast(content: String, duration: Int = Toast.LENGTH_SHORT): Toast {
val toast = Toast.makeText(MyApplication.context, content, duration)
toast.show()
return toast
}
這樣只要是Context的子類,任何地方都可以呼叫Toast了
1.8.2 擴充套件屬性
var View.padLeft: Int
set(value) {
setPadding(value, paddingTop, paddingRight, paddingBottom)
}
get() {
return paddingLeft
}
//呼叫 textView.padLeft = 16
1.8.3 Any擴充套件函式
apply
- apply 是 Any 的擴充套件函式,因而所有型別都能呼叫。
- apply 接受一個lambda表示式作為引數,並在apply呼叫時立即執行,apply返回原來的物件。
- apply 主要作用是將多個初始化程式碼鏈式操作,提高程式碼可讀性。
val task = Runnable { println("Running") }
val thread = Thread(task)
thread.setDaemon(true)
thread.start()
以上程式碼可以簡化為:
val task = Runnable { println("Running") }
Thread(task).apply { setDaemon(true) }.start()