專案未來可能需要使用
kotlin
開發,所以特此記錄一下學習筆記,僅供參考,方便後期查詢。已同步到GitHub
上:KotlinTest
Kotlin 簡介
kotlin
的目標是成為一門全棧語言,主要有以下的特點:
- 已經成為
Android
的官方推薦語言 - 百分百的和
java
相容,兩者可以相互轉換 JS
、JVM
、Native
多平臺開發
資料型別
1. 基本型別
Boolean true/false
Double 64
Float 32
Long 64
Int 32
Short 32
Byte 8
val aChar = '0'
val bChar = '我'
val cChar = '\u000f'
複製程式碼
Char
型別的轉義字元
\t 製表符
\b 游標後退一個字元
\n 回車
\r 游標回到行首
\' 單引號
\" 雙引號
\\ 反斜槓
\$ 美元符號,Kotlin 支援美元符號開頭的字串模板
複製程式碼
2. 基本型別的轉換
不可隱式轉換
val anInt: Int = 5
val aLong: Long = anInt.toLong()
複製程式碼
必須得通過.to型別
的方式進行資料的轉換
字串
-
一串
Char
-
用雙引號""引起來
val aString: String = "Hello World!"
-
字串比較
a == b 表示比較內容 類似 Java 中的 equals a === b 表示比較物件是否相同
-
字串模板
println("hello, $name") -> "hello, 小明"
3. Koltin 中的類和物件初始化
類的定義
- 類,一個抽象的概念
- 具有某些特徵的事物的概括
- 不特定指代任何一個具體的事物
一般寫法:
/**
* 其中類引數如果加上 var 修飾,那麼他便是成員變數,反之則是普通的引數
*/
class Student(var name: String, var age: Int){
init {
// ... 相當於建構函式中的程式碼
}
}
複製程式碼
物件
-
是一個具體的概念,與類相對
-
描述某一個類的具體個體
-
舉例:
某些人、領導的車等等
類和物件的關係
- 一個類通常可以有很多歌具體的物件
- 一個物件本質上只能從屬一個類
- 某一個人,他是工程師,但本質上還是屬於人這一類
一般寫法:
val student: Student = Student("xiaweizi", 23)
複製程式碼
類的繼承
- 提取多個類的共性得到一個更為抽象的類,即父類
- 子類擁有父類的一切特徵
- 子類也可以定義自己的特徵
- 所有的類最終繼承自
Any
,類似於java
中的Object
4. 空型別和智慧轉換
空型別
// 定義
val notNull: String = null // 錯誤,不可能為空
val nullanle: String? = null // 正確,可以為空
// 使用
notNull.length // 正確,不可能為空所以可以直接使用
nullable.length // 有可能為空,不能直接獲取長度
// 要想獲取長度,可以通過以下兩者方式
nullable!!.length // 正確,強制認定 nullable 不可能為空,如果為空則會丟擲空指標異常
nullable?.length // 正確,若 nullable 為空,則返回 null
複製程式碼
智慧型別轉換
val child: Child = parent as Child // 類似於 Java 的型別轉換,失敗則丟擲異常
val child: Child = parent as? Child // 如果轉換失敗,返回 null
複製程式碼
編譯器智慧識別轉換:
val parent: Parent = Child()
if (parent is Child) {
// parent 直接呼叫子類方法,不需要再進行強制轉換
}
val string: String = null
if (string != null) {
// string.length 可以直接呼叫length 方法
}
複製程式碼
5. 區間
一個數學上的概念,表示範圍, ClosedRange
的子類,IntRange
最常用
基本用法:
0..100 --> [0, 100]
0 until 100 --> [0, 100)
i in 0..100 表示 i 是否在區間[0, 100]中
複製程式碼
6. 陣列
基本寫法:
val ints: IntArray = IntArrayOf(1,2,3,5)
var charArray: CharArray = charArrayOf('a', 'b', 'c', 'd', 'e')
var stringArray: Array<String> = arrayOf("aa", "bb", "cc", "dd", "e")
複製程式碼
基本操作:
print(charArray[index])
ints[0] = 2
ints.length
cahrArray.joinToString("") // 講 char 陣列轉換成字串
stringArray.slice(1..4) // 取出區間裡的值
複製程式碼
程式結構
1. 常亮和變數
常量
val a = 2
類似 Java 中的 final
不可被重複賦值
執行時常量:val x = getX()
編譯期常量:const val x = 2
複製程式碼
變數
var a = 2
a = 3 // 可以被再次賦值
複製程式碼
型別推導
val string = "Hello" // 推匯出 String 型別
val int = 5 // 推匯出 Int 型別
var x = getString() + 5 // String 型別
複製程式碼
2. 函式 Function
以特定功能組織起來的程式碼塊
// 最簡單的列印資訊,無返回的方法
fun printMessage(message: String):Unit{
println("$message")
}
// 擁有返回值得方法
fun sum(first: Int, second: Int):Int {
return first + second
}
// 可以簡化成:
fun sum(first: Int, second: Int) = first + second
// 或者更簡單的匿名函式
val result = fun(first: Int, second: Int) = first + second
複製程式碼
3. Lambda 表示式
其實又是匿名函式
一般形式:
{傳入引數 -> 函式體,最後一行是返回值}
// 例如
val sum = {first: Int, second: Int -> first + second}
val printMessage = {message: String -> println(message)}
複製程式碼
型別標識
() -> Unit // 無參,返回值為 null
(Int) -> Int // 傳入整型,返回一個整型
(String, (String) -> String) -> Boolean // 傳入字串、Lambda 表示式,返回Boolean
複製程式碼
Lambda 表示式的簡化
- 函式引數呼叫時最後一個
Lambda
可以移出去 - 函式引數只有一個
Lambda
,呼叫時小括號可以省略 Lambda
只有一個引數可預設為it
- 入參、返回值與形參一致的函式可以用函式引用方式作為實參傳入
4. 成員變數和成員方法
成員變數的宣告
// 第一種是在建構函式中宣告
class Student(var age: Int, name: String){
// age 是成員變數 name 是區域性變數
}
// 第二種是在函式體內宣告
var a = 0
get() {
field += 1
return field
}
set(value) {
println("set)
field = value + 1
}
// 可以進行對 get 和 set 方法的重新定義
// 屬性的初始化儘量在構造方法中完成
// var 用 lateinit 延遲初始化, val 用 lazy
lateinit var sex: String
val person: Person by lazy {
Person()
}
複製程式碼
成員方法
在類中直接宣告方法可以直接呼叫,包括lambda
表示式
// 方法的宣告
fun sum(a: Int, b: Int) = a + b
val sum1 = {a: Int, b: Int -> a + b}
// 方法的呼叫
println(person.sum(1,2))
println(person.sum1(3,5))
複製程式碼
5. 運算子
在java
中運算子是不能重新定義過載的,只能按照原先的邏輯進行計算
而Kotlin
則可以重新定義運算子,使用operator
關鍵字,舉了例子:
// 定義一個用於計算複數的類
class Complex(var real: Double, var imaginary: Double) {
operator fun plus(other: Complex): Complex{
return Complex(real+other.real, imaginary+other.imaginary)
}
// 重新 toString 方法
overrride fun toString(): String {
return "$real + ${imaginary}i"
}
}
// 使用
val complex1 = Complex(1, 2)
val complex2 = Complex(2, 3)
println(complex1 + complex2)
// 輸出結果為
"3 + 5i"
複製程式碼
關鍵就是這個方法,方法名必須是plus
或者其他官方定義的運算子,引數有且僅有一個,型別自定義,返回值意識可以自定義的.
operator fun plus(other: Complex): Complex{
return Complex(real+other.real, imaginary+other.imaginary)
}
複製程式碼
6. 表示式
中綴表示式
通過infix
關鍵字修復方法,那麼就可以不用通過 物件.方法() 的方式呼叫,而是直接 物件 方法名 引數的方式呼叫。舉了例子
class Student(var age: Int){
infix fun big(student: Student): Boolean {
return age > student.age
}
}
// 如果沒有 infix 的呼叫方式:
println(Student(23).big(Student)(12))
// 如果使用 infix 修飾的呼叫方式:
println(Student(23) big Student(12))
複製程式碼
if
表示式
直接來個例子
val a = 20
val b = 30
val flag: Int = if(a > b) a else b
複製程式碼
When
表示式
加強版的 switch
,支援任意型別, 支援純粹表示式條件分支(類似if
),舉個栗子:
val a = 5
when(a) {
is Int -> println("$a is Int")
in 1..6 -> println("$a is in 1..6")
!in 1..4 -> println("$a is not in 1..4")
else -> {
println("null")
}
}
複製程式碼
for
迴圈
基本寫法
for (element in elements)
複製程式碼
while
迴圈
基本寫法
while() {
}
do {
} while()
複製程式碼
跳過和終止迴圈
跳過當前迴圈用 continue
終止迴圈用 break
複製程式碼
6. 異常捕獲
同樣也是表示式,可以用來賦值,舉個例子
return try{
x/y
}
catch(e: Exception) {
0
} finally {
//...
}
複製程式碼
如果沒有異常則返回x/y
,否則返回0
,finally
中的程式碼無論如何還是要執行的。
7. 具名引數、變長引數和預設引數
**具名引數:**給函式的實參附上形參
fun sum(first: Int, second: Int) = first + second
sum(second = 2, first = 1)
複製程式碼
**變長引數:**用varary
修飾,使用起來是和陣列一樣,某個引數可以接收多個值,可以不作為最後一個引數,如果傳參時有歧義,需要使用具名引數。
fun hello(vararg ints: Int, string: String) = ints.forEach(println(it))
hello(1,3,4,5,string = "hello")
// 如果最後一個引數也是 Int
fun hello(varary ints: Int, anInt: Int)
// 建立陣列
val arrayInt: IntArray = intArrayOf(1, 2, 3, 4)
hello(ints = *arrayInt, anInt = 2)
複製程式碼
**預設引數:**就是給引數傳入一個預設的值
fun hello(anInt: Int = 1, string: String)
hello(string = "aaa")
複製程式碼
物件導向
1. 繼承
繼承語法要點:
- 父類需要
open
才可以被繼承 - 父類方法、屬性需要
open
才可以被覆寫 - 介面、介面方法、抽象類預設為
open
- 覆寫父類(介面)成員需要
override
關鍵字
語法要點:
class A: B(), C, D
- 繼承類時實際上呼叫了父類的構造方法
- 類只能單繼承,介面可以多實現
介面代理:
一個類可以直接將自己的任務委託給介面的方法實現,舉個例子:
interface Drive{
fun drive()
}
interface Sing{
fun sing()
}
class CarDrive: Drive{
override fun drive() {
println("我會開車呦")
}
}
class LoveSing: Sing{
override fun sing() {
println("我會唱歌呦")
}
}
class Manager(drive: Drive, sing: Sing): Drive by drive, Sing by sing
fun main(args: Array<String>) {
val carDrive = CarDrive()
val loveSing = LoveSing()
val manager = Manager(carDrive, loveSing)
manager.drive()
manager.sing()
}
複製程式碼
這樣,manager
不用做任何事情,完全交付給介面實現.
介面方法衝突:
介面方法可以有預設實現,通過super<父類名>
.方法名
interface A{
fun a() = 0
}
interface B{
fun a() = 1
}
interface C{
fun a() = 2
}
class D(var aInt: Int): A,B,C{
override fun a(): Int {
return when(aInt){
in 1..10 ->{
super<A>.a()
}
in 11..100 ->{
super<B>.a()
}
else -> {
println("dd")
super<C>.a()
}
}
}
}
複製程式碼
2. 類及成員的可見性
跟java
類似,private、protected、public
,其中internal
代表的是模組內可見
3. Object
相當於Java
中的單例模式,有以下特點
-
只有一個例項的類
-
不能自定義構造方法
-
可以實現介面、繼承父類
-
本質上就是單例模式最基本的實現
interface getDataSuccess{ fun success() } abstract class getDataField{ abstract fun failed() } object NetUtil: getDataField(), getDataSuccess{ override fun success() { println("success") } override fun failed() { println("failed") } val state: Int = 0 fun getData(): String = "請求成功" } 複製程式碼
3. 伴生物件和靜態成員
相當於java
中的靜態方法
-
每個類可以對應一個伴生物件
-
伴生物件的成員全域性獨一份
-
如果
java
中想直接呼叫kotlin
中的靜態方法或者靜態變數,可以考慮使用JvmField JvmStatic
.open class Util private constructor(var anInt: Int) { companion object { @JvmStatic fun plus(first: Int, second: Int) = first + second fun copy(util: Util) = Util(util.anInt) @JvmField val tag = "tag" } } 複製程式碼
4. 方法的過載
通過給方法的引數配置預設值,即可實現方法的過載,按理說,一切可以擁有預設值的方法過載才是合理的方法過載。
名稱形同、引數不同,跟返回值沒有關係
class OverLoadTest {
@JvmOverLoads
fun a(anInt: Int = 0, string: String="") = 1
}
val test = OverLoadTest()
test.a(1, "")
test.a()
test.a(anInt = 2)
test.a(string = "")
複製程式碼
使用JvmOverLoads
是為了方便Java
中呼叫方法的過載.
5. 擴充套件方法
kotlin
中的擴充套件方法,我認為相當於java
中的代理模式,拿到被代理的物件,然後進行一系列的操作。
fun String.add(anInt: Int): String {
var sb = StringBuilder()
for (i in 0 until anInt) {
sb.append(this)
}
return sb.toString()
}
operator fun String.times(anInt: Int): String {
var sb = StringBuilder()
for (i in 0 until anInt) {
sb.append(this)
}
return sb.toString()
}
// 使用
var string = "xiaweizi"
println(string.add(5))
println(string * (3))
複製程式碼
6. 屬性代理
類似之前說的var anInt: Int by lazy{2}
,懶賦值就是使用的屬性代理,來看個例子:
fun main(args: Array<String>) {
val a: Int by DelegatesTest()
println(a)
var b: Int by DelegatesTest()
b = 3
println(b)
}
class DelegatesTest {
private var anInt: Int? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
println("getValue")
return anInt?:0
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int): Unit {
println("setValue")
this.anInt = value
}
}
複製程式碼
val
對應getValue
,var
對應getValue和setValue
方法,這個時候宣告的屬性就全權交付給DelegatesTest
類中的anInt
代理,當anInt
為空的時候返回0
,否則返回anInt
.
7. JavaBean
使用data
修飾類,類似java
中的javaBean
,預設實現了set get toString
等方法,並擁有componentN
方法.
不過有個缺點就是,無法被繼承,沒有無參建構函式,可以通過安裝allOpen
和noArg
外掛解決這個問題.
data class UserBean(var name: String, var age: Int)
val userBean: UserBean = UserBean("小芳", 23)
println(userBean.name)
println(userBean.toString())
println(userBean.component1())
println(userBean.component2())
val (name, age) = userBean
println("name: $name")
println("age: $age")
複製程式碼
至於這種寫法
val (name, age) = userBean
,是因為定義了component1
的運算子
class Complex{
operator fun component1() = "你好呀"
operator fun component2() = 2
operator fun component3() = 'a'
}
val complex = Complex()
val (a, b, c) = complex
println(a + b + c)
複製程式碼
使用起來也是很簡單的
8. 內部類
- 定義在類內部的類
- 與類成員有相似的訪問控制
- 預設是靜態內部類,非靜態用
inner
關鍵字 this@Outter
this@Inner
的用法- 匿名內部類
- 沒有定義名字的內部類
- 類名編譯時生成,類似
Outter$1.class
- 可繼承父類,實現多個介面,與
Java
注意區別
舉個例子:
class Outer{
var string: String = "outer"
class Inner1{
var string: String = "inner1"
fun sum(first: Int, second: Int) = first + second
}
inner class Inner2{
var string: String = "inner2"
fun cha(first: Int, second: Int) = first - second
fun getInnerField() = this.string
fun getOuterField() = this@Outer.string
}
}
fun main(args: Array<String>) {
val inner1 = Outer.Inner1()
val inner2 = Outer().Inner2()
println(inner1.sum(1, 2))
println(inner2.cha(2, 1))
println(inner2.getInnerField())
println(inner2.getOuterField())
}
複製程式碼
匿名內部類:
val listener: onClickListener = object : Father(), Mother, onClickListener{
override fun sing() {
println("mother sing")
}
override fun teach() {
println("father teach")
}
override fun onClick() {
println("匿名內部類")
}
}
複製程式碼
使用
Object
實現匿名內部類
9. 列舉和密封類
列舉是物件可數,每個狀態相當於每個物件,是可以傳構造引數的
密封類時子類可數,在kotlin
大於1.1子類只需要與密封類在同一個檔案加,保護子類的位置
sealed class SealedClassTest{
class sum(first: Int, seocnd: Int): SealedClassTest()
class cha(first: Int, seocnd: Int): SealedClassTest()
object Bean: SealedClassTest()
}
enum class HttpStatus(val anInt: Int){
SUCCESS(0), FAILED(1), LOADING(2)
}
fun main(args: Array<String>) {
val class1 = SealedClassTest.cha(1, 2)
println(HttpStatus.SUCCESS)
}
複製程式碼
高階函式
1. 基本概念
- 傳入或者返回函式的函式
- 函式引用
::println
- 帶有
Receiver
的引用pdfPrinter::println
有三種顯示
// 1. 包級函式
intArray.forEach(::print)
// 2. 類.方法
intArray.forEach(Int::addOne)
fun Int.addOne(): Unit {
println("addOne:$this")
}
// 3. 物件.方法
intArray.forEach(AddTwo()::addTwo)
class AddTwo {
fun addTwo(anInt: Int): Unit {
println("addTwo:$anInt")
}
}
複製程式碼
2. 常用的高階函式
常用的高階函式還是有很多的,會簡單的使用例子即可:
// 遍歷
fun forEachTest() {
val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")
strings.forEach { println(it) } // 遍歷每一個值
strings.forEachIndexed { index, s -> println("index:$index,String:$s") } // 遍歷 下標和值一一對應
}
// 重新拷貝一個值
fun mapTest() {
val strings: Array<String> = arrayOf("aa", "ee", "bb", "ll")
var map = strings.map { "$it-test" }
map.forEach { print("$it\t") }
}
// 將集合合體
fun flatMapTest() {
val lists = listOf(1..10,
2..11,
3..12)
var flatMap = lists.flatMap {
it.map {
"No.$it"
}
}
flatMap.forEach(::println)
}
fun reduceTest() {
val ints = listOf(2, 3, 4, 5)
println(ints.reduce { acc, i ->
acc + i
})
}
// 字串連線
fun foldTest(){
val ints = listOf(2, 3, 4, 5)
println(ints.fold(StringBuffer(), { acc, i -> acc.append("$i,") }))
println(ints.joinToString(","))
}
fun filterTest() {
val ints = listOf(1, 2, 3, 4, 5, 6)
println(ints.filter { element -> element % 2 == 0 })
}
// 當值不是奇數就去,遇到偶數就停止了
fun takeWhileTest() {
val ints = listOf(1, 3, 3, 4, 5, 6)
println(ints.takeWhile { it % 2 != 0 })
}
fun letTest() {
findPerson()?.let { (name, age) -> println("name:$name, age:$age") }
findPerson()?.apply { println("name:$name, age:$age") }
with(findPerson()!!) { println("name:$name, age:$age") }
}
data class Person(val name: String, val age: Int)
fun findPerson(): Person? {
return Person("aa", 23)
}
複製程式碼
3. 複合函式
有點類似資料中的f(g(x))
fun main(args: Array<String>) {
val add1 = {int: Int ->
println("add1")
int + 1}
val add2 = {int : Int ->
println("add2")
int + 2}
var add3 = add1 addThen (add2)
println(add3(4))
}
infix fun <P1, P2, R> Function1<P1, P2>.addThen(function: Function1<P2, R>): Function1<P1, R> {
return fun(p: P1): R{
return function.invoke(this.invoke(p))
}
}
複製程式碼
4. Currying
簡單來說就是多元函式變換成一元函式呼叫鏈式,舉個簡單的例子,這是優化之前:
fun log(tag: String, out: OutputStream, message: String){
out.write("[$tag], $message".toByteArray())
}
複製程式碼
優化之後
fun log(tag: String)
= fun(out: OutputStream)
= fun(message: String)
= out.write("[$tag], $message".toByteArray())
複製程式碼
5. 計算檔案字串個數的小例子
首先將字串轉換成字串陣列:
val map: HashMap<Char, Int> = HashMap()
var toCharArray = File("build.gradle").readText().toCharArray()
複製程式碼
通過分組的方式,統計每個字串的個數,並列印:
toCharArray.groupBy { it }.map { it.key to it.value.size }.forEach { println(it) }
複製程式碼
kotlin
和java
的混合開發
1. 基本的互動操作
屬性讀寫
Kotlin
自動識別Java Getter/Setter
Java
操作Kotlin
屬性通過Getter/Setter
空安全型別
Kotlin
空安全型別的原理- 平臺型別
Platform Type
Java
可以通過@Nullable、@NotNull
幾類函式的呼叫
- 包級函式:靜態方法
- 擴充套件方法:帶
Receiver
的靜態方法 - 運算子過載:帶
Receiver
的對應名稱的靜態方法
幾個常用的註解
@JvmField
:將屬性編譯為Java變數
@JvmStatic
:將物件的方法編譯成功Java
靜態方法@JvmOverloads
:預設引數生成過載方法@JvmName
:制定Kotlin
檔案編譯後的類名
NoArg 和 AllOpen
NoArg
為被標註的類生成無參構造AllOpen
為被標註的類去掉final
,允許被繼承
正規表示式
- 用
Raw
字串定義正規表示式 Java
的Pattern
Kotlin
的Regex
舉個例子:
val source = "Hello This my phone number: 010-12345678."
val pattern = """.*(\d{3}-\d{8}).*"""
Regex(pattern).findAll(source).toList().flatMap(MatchResult::groupValues).forEach(::print)複製程式碼