類的構造器
-
構造器
-
主構造器
//age 全局可用,name 只是普通的構造器參數 class Person(var age: Int, name: String) { }
-
init 塊
//age 全局可用,name 只是普通的構造器參數 class Person(var age: Int, name: String) { val n: String init { n = name } }
init 塊會在構造方法的最前面執行,可用於初始化
-
屬性初始化
可在 init 塊中進行初始化
-
重寫 get / set
class Person() { var name: String = "" get() { return "hello $field" } set(value) { field = "hello $value" } }
-
-
副構造器
-
不定義主構造器
open class A { constructor(name: String) { println("A的 一個參數構造器") } constructor(name: String, age: Int) { println("A的 兩個參數構造器") } init { println("A的 init") } } class B : A { constructor(name: String) : this(name, 20) { println("B的 一個參數構造器") } constructor(name: String, age: Int) : super(name, age) { println("B的 兩個參數的構造器") } } fun main() { var lv = B("張三") }
A的 init A的 兩個參數構造器 B的 兩個參數的構造器 B的 一個參數構造器
-
主構造器默認參
open class A(age: Int, name: String = "張三") { } fun main() { var a = A(20) }
-
-
工廠函數
open class A(age: Int, name: String = "張三") { fun A(): A { return A(20) } fun A(age: Int, name: String): A { return A(age, name) } }
函數的名字可以和類名相同
可見性對比
可見性對比 | java | Kotlin |
---|---|---|
public | 公開 | 與 Java 相同,默認 |
internal | X | 模塊內可見 |
default | 包內可見,默認 | X |
protected | 包內及子類可見 | 類內及子類可見 |
private | 類內可見 | 類或文件可見 |
模塊的概念:直觀的講,大致可以認爲是一個 Jar包,一個 aar
-
IntelliJ IDEA 模塊
-
Maven 模塊
-
Gradle SourceSet
-
Ant 任務中一次調用 的文件
如果定義了 internal 的函數,但是不想讓 java 訪問,可以使用 @JvmName("") 註解來實現,在 java 中調用的時候註解測參數將會成爲方法的名字,且報錯,不可以調用。
構造器的可見性
- 給構造器加註解的時候 constructor 就必須寫出來
- 如果使用單例,或者工廠等,就需要將構造器弄成私有的
頂級聲明的可見性
- 指文件內直接定義的屬性,函數,類等
- 頂級聲明不支持 protected
- 頂級聲明被 private 修飾後表示文件內部可見
類屬性的延時初始化
-
類屬性必須在構造時初始化
-
某些成員只有在類構造後纔會被初始化,例如android 中在 onCrate 中進行 findViewById
解決辦法
-
直接判空,或者類型強轉
-
使用 lateinit 關鍵字
lateinit 會讓編譯器忽略變量的初始化,不支持 Int 等基本類型
開發者必須在完全確定變量值生命週期的情況下使用 lateinit
不要在複雜的邏輯中使用 lateinit
-
使用 lazy
只有在首次使用的時候被訪問執行
代理Delegate
接口代理:對象 X 代替當前類 A 實現接口 B 的方法
屬性代理:對象 x 代理屬性 a 實現 getter/setter 方法
//接口代理
interface Api {
fun a()
fun b()
fun c()
}
class ApiImpl : Api {
override fun a() {}
override fun b() {}
override fun c() {}
}
class ApiWrapper(val api: Api)
//對象 api 代替類 ApiWrapper 實現接口 API
: Api by api {
override fun c() {
println("-------- c")
api.c()
}
}
屬性代理:
屬性代理是藉助於代理設計模式,把這個模式應用於一個屬性時,他可以將訪問器的邏輯代理給一個輔助對象
可以簡單理解爲屬性的 setter,getter 訪問器內部實現是交給一個代理對象來實現,相當於一個代理對象替換了原來字段的讀寫過程,而暴露在外部屬性的操作還是不變的,照樣是屬性賦值和讀取,只是 setter,getter內部實現變了
1,Lazy
當變量第一次使用是進行初始化,可以實現懶加載
//屬性代理
class Person(val name: String) {
//lazy返回的對象代理了屬性 firstName 的 getter
val firstName by lazy {
println("初始化-----")
"345"
}
}
fun main() {
val p = Person("")
println(p.firstName)
println(p.firstName)
}
初始化-----
345
345
只有在第一次調用的時候纔會執行。感覺比較適合安卓
初始化默認是線程安全的,通過 synchronized 鎖來保證
不過還是會影響性能,如果 lazy 的初始化不會涉及到多線程,那麼可以傳入 LazyThreadSafetyMode.NONE 來取消同步鎖
LazyThreadSafetyMode有三種模式:SYNCHRONIZED(默認模式)、PUBLICATION、NONE
其中PUBLICATION模式使用了AtomicReferenceFieldUpdater(原子操作)允許多個線程同時調用初始化流程。
2,自定義屬性代理
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any, property: KProperty<*>): String {
return "代理 get"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("代理 set")
}
}
fun main() {
val e = Example()
println(e.p)
e.p = "345"
}
代理 get
代理 set
3,observable
變量被賦值時會發出通知
Delegates.observable 可以傳入兩個參數,一個是初始值,另一個是變量被賦值時的 handle 方法
class User {
var name: String by Delegates.observable("345") { property, oldValue, newValue ->
println("$oldValue -> $newValue")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
輸出
345 -> first
first -> second
只要 user.name 被賦值,監聽就會觸發
類似的還有 vetoable ,只不過 vetoable 是在賦值前觸發,observable 是在賦值後觸發
4,vetoable
使用 vetoable 進行攔截
class User {
var name: String by Delegates.vetoable("345") { property, oldValue, newValue ->
if (newValue == "first") {
return@vetoable true //返回 true 表示first 可以賦值給 name
}
return@vetoable false //返回false 表示攔截其他賦值操作
}
}
fun main() {
val user = User()
user.name = "first"
println(user.name)
user.name = "second"
println(user.name)
}
first
first
5,對 map 的代理
fun main() {
val map = mutableMapOf("name" to "張三", "age" to 1)
var name: String by map
println(name)
name = "李四"
println(map["name"])
}
張三
李四
6,局部變量代理
fun main() {
a {
345
}
}
fun a(sum: () -> Int) {
val s by lazy(sum)
println(s) //345
}
案例
:使用屬性代理讀寫 Properties
class PropertiesDelegate(private val path: String, private val defaultValue: String = "") {
private lateinit var url: URL
private val properties: Properties by lazy {
val prop = Properties()
url = try {
// 查找具有給定名稱的資源
javaClass.getResourceAsStream(path).use {
//從輸入流中讀取屬性列表(鍵值對)
prop.load(it)
}
javaClass.getResource(path)
} catch (e: Exception) {
try {
ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {
prop.load(it)
}
ClassLoader.getSystemClassLoader().getResource(path)!!
} catch (e: Exception) {
FileInputStream(path).use {
prop.load(it)
}
URL("file://${File(path).canonicalPath}")
}
}
prop
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
//獲取屬性信息
return properties.getProperty(property.name, defaultValue)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
//設置屬性信息
properties.setProperty(property.name, value)
//寫入到文件,第二個參數爲註釋信息
File(url.toURI()).outputStream().use {
properties.store(it, "Hello!!l")
}
}
}
abstract class AbsProperties(path: String) {
protected val prop = PropertiesDelegate(path)
}
class Config : AbsProperties("Config.properties") {
var author by prop
var version by prop
var desc by prop
}
fun main() {
val config = Config()
print(config.author) //getValue
config.author = "345" //setValue
println(config.author)
}
單例
//單例的定義,默認是惡漢模式
object Signleton {
var x = 2
//模擬 java 中靜態方法,只有在 object 中可以使用
@JvmStatic
fun y() {
println("---------")
}
}
// Signleton.INSTANCE.getX(); 從 java 中調用該類
// Signleton.y(); JvmStatic :靜態的
class Foo {
//伴生對象,與普通類同名的 object
//相當於java中的 public static void y(){}
companion object {
@JvmStatic
fun y() {
println("y")
}
//生成靜態 Field
@JvmField
var y: Int = 2
}
//屬性可以使用 JvmField,生成非靜態 Field,
@JvmField
var x: Int = 2
}
fun main() {
Signleton.x
Signleton.y()
println(Foo.y)
println(Foo.y())
}
內部類
fun main() {
//創建非靜態內部類對象
val inner = Outer().Inner()
//創建靜態內部類對象
val staticInner = Outer.StaticInner()
//匿名內部類,可以實現多個接口,並且繼承一個類
object : Runnable, Cloneable {
override fun run() {
}
}
}
class Outer {
//非靜態內部類
inner class Inner {
}
//靜態內部類
class StaticInner {
}
//一旦定義出來,就會被初始化
object HH {
}
}
數據類
data class Book(val id: Long, val name: String, val author: String) {
}
JavaBean | data class | |
---|---|---|
構造方法 | 默認無參構造 | 屬性作爲參數 |
字段 | 字段私有,get/set 公開 | 屬性 |
繼承性 | 可繼承,可被繼承 | 不可被繼承 |
component | 無 | 相等性,解構等 |
data class 爲我們做了什麼
編譯器會根據我們在構造函數中聲明的屬性自動導出下列成員:
-
equals / hashCode
-
toString
-
componentN()
-
copy()
如果在類中明確定義或繼承了上面的基礎方法,則不會自動生成
限制:
- 構造函數至少要一個參數
- 所有構造函數的參數都必須使用 val 或者 var 標記
- data class 不可以是 abstract,open sealed or inner
- 不可以實現接口
數據類自帶的結構方法:componentN()
fun main() {
val book = Book(1L, "百科", "張三")
val (id, name, author) = book
println("$id $name $author")
}
實際上,結構是調用了 Book 類的 component1() 和 component2() ,component3()。
如何作爲 Java Bean 使用呢
需要解決兩個限制:
至少一個構造器,和 final。data class 是不可被繼承的。
如果解決呢:NoArg 插件,AllPoen 插件
在 build.gradle 中添加插件
plugins {
......
id("org.jetbrains.kotlin.plugin.noarg").version("1.3.50")
id("org.jetbrains.kotlin.plugin.allopen").version("1.3.50")
}
noArg {
//執行 init 塊中的代碼
invokeInitializers = true
annotation("com.kotlin.study.five.PoKo")
}
allOpen {
annotation("com.kotlin.study.five.PoKo")
}
注意build.gradle 使用的是 kt 的代碼
還需要定義一個註解:就是 annotation指定路徑下的 PoKo 註解
public @interface PoKo {
}
空的即可
使用
//使用該註解後會在字節碼中生成一個無參構造,
//可被繼承
@PoKo
data class Book(val id: Long, val name: String, val author: String) {
init {
println("------")
}
}
fun main() { val b = Book::class.java.newInstance()}
枚舉
fun main() {
S1.Busy.run()
S1.Idle.run()
val state = State.Busy
//條件分支
val value = when (state) {
State.Idle -> {
0
}
State.Busy -> {
1
}
}
//枚舉的比較,因爲是有順序的,所以可以。需要重載運算符
//枚舉的區間,因爲有順序
}
//枚舉,父類是 Enum,所以不能繼承其他類
enum class State {
Idle,
Busy
}
//帶參數的構造
enum class S(val id: Int) {
Idle(0),
Busy(1)
}
//實現接口的枚舉
enum class S1 : Runnable {
Idle {
//實現字節的 run 方法
override fun run() {
println("For Idle")
}
},
Busy;
override fun run() {
println("For every state.")
}
}
//爲枚舉定義擴展
fun State.next(): State {
return State.values().let {
val next = (ordinal + 1) % it.size
it[next]
}
}
- 枚舉
- 枚舉的基本知識:定義方法,屬性,構造器,接口實現
- 爲枚舉定義擴展
- 分支表達式
- 比較與區間
密封類
- 密封類是一種特殊的抽象類,不能被實例化,但是可以有抽象成員
- 密封類的子類定義在與自身相同的文件中
- 密封類的子類個數是有限的
fun main() {
//只能通過子類實例化對象
val play: PlayerState = Start("小鳥")
}
//首先是一個抽象類,其次是一個密封類,構造器是私有的
sealed class PlayerState {
}
class Start(val song: String) : PlayerState() {
}
class Stop : PlayerState() {
}
- 密封類
- 概念:抽象,子類可數,子類封閉
- 構造器私有:無法在外部文件中繼續密封類
- 子類定義:必須定義在當前文件中
- 與枚舉對比:enum:實例可數,sealed:子類可數
內聯類
- 內聯類是對某一個類型的包裝
- 內聯類是類似於 Java 裝箱類型的一種類型
- 編譯器會盡可能使用被包裝的類型進行優化
fun main() {
var x = BoxInt(3)
val newValue = x.value * 200
println(newValue) //600
x++
println(x) //BoxInt(value=4)
}
//內聯類
//對 Int 做了包裝,Int 必須定義在主構造器,且必須是 val 類型 和 public
//可實現接口。且不能繼續和被繼承
inline class BoxInt(val value: Int) {
//內聯類屬性:不應改包含狀態。也就是隻能定義方法或者函數
//重寫自增運算符
operator fun inc(): BoxInt {
return BoxInt(value + 1)
}
}
內聯類目前還在公測。
限制
-
主構造器必須有且只有一個只讀屬性。因爲要對其進行包裝
-
不能定義有 backing-field 的其他屬性。不能有狀態
-
被包裝的類型不是是泛型
-
不能繼續且被繼承
-
內聯類不能被定義爲其他類的內部類
-
定義的方法在編譯的時候會被替換爲靜態方法
數據類的 Json 化、
依賴
kotlin("kapt") version ("1.3.50")
implementation("com.squareup.moshi:moshi:1.8.0")
implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
fun main() {
//Gson
val gson = Gson()
println(gson.toJson(Person("Gson", 20)))
println(gson.fromJson("""{"name":"Gson"}""", Person::class.java))
println("----------------------")
//MoShi
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(Person::class.java)
println(jsonAdapter.toJson(Person("MoShi", 20)))
println(jsonAdapter.fromJson("""{"name":"Gson"}"""))
}
@JsonClass(generateAdapter = true)
data class Person(val name: String, val age: Int = 10)
{"name":"Gson","age":20}
Person(name=Gson, age=0)
----------------------
{"name":"MoShi","age":20}
Person(name=Gson, age=10)
Bean 類中 age 的默認值是 10
在 gson 解析的時候沒有指定 age 則爲 0
而 moshi 解析的時候會按照構造方法中的默認值來進行解析。注意 moshi 用到了註解處理器 @JsonClass
遞歸整形案例
fun main() {
val l1 = inListOf(0, 1, 3, 4)
println(l1)
println(l1.joinToString('-'))
println(l1.sun())
val (x, y, z) = l1
println(x)
println(y)
println(z)
}
fun inListOf(vararg ints: Int): IntList {
return when (ints.size) {
0 -> IntList.Nil
else -> {
//遞歸調用,
//slice:切割,從第一個到最後一個(注意不是第 0 個)
//遞歸時,ints[0]永遠是上一次的下一個
IntList.Cons(ints[0], inListOf(*(ints.slice(1 until ints.size).toIntArray())))
}
}
}
sealed class IntList {
object Nil : IntList() {
override fun toString(): String {
return "Nil"
}
}
class Cons(val head: Int, val tail: IntList) : IntList() {
override fun toString(): String {
return "$head,$tail"
}
}
fun joinToString(sep: Char = ','): String {
return when (this) {
Nil -> "Nil"
is Cons -> {
"${head}$sep${tail.joinToString(sep)}"
}
}
}
operator fun component1(): Int? {
return when (this) {
Nil -> null
is Cons -> head
}
}
operator fun component2(): Int? {
return when (this) {
Nil -> null
//下一個 Cons
is Cons -> tail
.component1()
}
}
operator fun component3(): Int? {
return when (this) {
Nil -> null
is Cons -> tail
.component2()
}
}
}
fun IntList.sun(): Int {
return when (this) {
IntList.Nil -> 0
is IntList.Cons -> {
head + tail.sun()
}
}
}
- 類型進階
- 類的構造器
- 主構造器:唯一的路徑
- 副構造器:提供其他的構造路徑
- init 快:可寫多個,最後會合成一個執行
- 可見性
- public
- internal:模塊內可見,類似一個 jar,aar
- protected:沒有包內可見,只有子類看見
- private
- 類屬性的延時初始化
- 可空類型
- lateinit
- lazy
- 代理 Delegate:
- 接口代理
- 屬性代理:lazy,observable,vetoable 等
- 使用屬性代理讀寫 Properties
- 單例 object
- 伴生對象
- 定義 java field 的 @JvmField
- 定義靜態成員的 @javaStatic
- 內部類
- 默認靜態
- 非靜態用 inner 修飾
- 匿名內部類可以實現多個接口
- 本地類和本地函數
- 數據類
- component:主構造器中定義的屬性
- 編譯器生成的方法:equals,hashCode,copy,componentN
- 解構:定義了 componentN方法,即可通過解構獲取到對應的值
- 插件:noArg(增加默認構造方法),allOpen(去掉 final)
- 其他類
- 枚舉類
- 密封類:子類只能定義在父類相同的文件中
- 內聯類:類似於裝箱拆箱
- 數據類的 json 序列化
- Gson,Moshi,Kotlinx.serialization
- 特性:默認參數,init 塊
- 遞歸的整形列表的簡單實現
- 遞歸用的非常好
- 可進行解構
- 類的構造器
參考自慕課網 Kotlin 從入門到精通