1,類的構(gòu)造器
當(dāng)我們定義一個(gè)Person類時(shí)
class Person(var name:String,age:Int)
- 帶 var/val 字段的屬性 是類內(nèi)全局可見的
- 而后面age 不帶var/val 該屬性僅在構(gòu)造器內(nèi)可見(init塊,屬性初始化)
上面的定義相當(dāng)于
class Person(var name: String, age: Int) {
var age = 0
init {
this.age = age
println(this.age)
}
}
<1>init塊
- init塊跟Java中的構(gòu)造塊基本類似,唯一的區(qū)別是Kotlin中init塊中能訪問到構(gòu)造函數(shù)中的不帶var/val 的變量
例如
class Person(var name: String, age: Int) {
var age = 0
init {
this.age = age
println(this.age)
}
var testa=3
init {
this.age=testa
println(this.age)
}
}
他們會(huì)一起編譯到函數(shù)的構(gòu)造函數(shù)中一起執(zhí)行
<2>類的屬性必須初始化
Kotlin類的屬性必須初始化,類的生命周期和屬性的相同,這樣使用者在調(diào)用類的屬性時(shí)不會(huì)有負(fù)擔(dān)
<3>類的繼承
子類繼承父類必須調(diào)用父類的構(gòu)造方法
abstract class Anim
class Pig(var name: String):Anim(){
//必須調(diào)用父類構(gòu)造函數(shù)
}
<4>副構(gòu)造器
- 副構(gòu)造器:定義在主構(gòu)造器后在類內(nèi)部定義的構(gòu)造器都被成為福構(gòu)造器
- 副構(gòu)造器必須調(diào)用到主構(gòu)造器,確保構(gòu)造路徑唯一性
abstract class Anim
class Pig(var name: String) : Anim() {
//必須調(diào)用父類構(gòu)造函數(shù)
var age = 0;
constructor(age: Int) : this("fff") {
this.age = age
}
}
不定義主構(gòu)造器(不推薦)
abstract class Anim
class Dog : Anim{
var name:String
var age:Int
constructor(name: String,age:Int):super(){
this.name=name
this.age= age
}
}
一般情況下有這種情況的,比較推薦使用:主構(gòu)造器+默認(rèn)參數(shù)的形式
<5>工廠函數(shù)
fun main() {
//Person的工廠方法
Person("1111111")
//String中系統(tǒng)的工廠方法
String(charArrayOf('1'))
//自定義String
String(IntArray(2){ it })
}
val persons=HashMap<String,Person>()
fun Person(name: String):Person{
//緩存person對(duì)象
return persons[name]?:Person(name,25).also { persons[name] = it }
}
fun String(ints:IntArray):String{
return ints.contentToString()
}
工廠函數(shù)和擴(kuò)展方法:個(gè)人理解擴(kuò)展方法的場(chǎng)景更大一些,而工廠函數(shù)的意義就是為了創(chuàng)建該類的對(duì)象,擴(kuò)展方法可以返回該類對(duì)象也可以返回為Unit
2,類與成員的可見性
<1>模塊的概念
- Intelij IDEA 模塊
- Maven工程
- Gradle SourceSet
- Ant 任務(wù)中一次調(diào)用<kotlinc>的文件
- 直觀的講模塊的可以認(rèn)為是一個(gè)jar包,一個(gè)aar包,通常一個(gè)jar包或者aar包里面所有的源文件會(huì)一次通過<kotlinc>通過編譯
<2> internal VS default
- 一般由SDK或者公共組件開發(fā)者用于隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)
- default 可通過外部創(chuàng)建相同的包名進(jìn)行訪問,訪問控制比較弱
- default 會(huì)導(dǎo)致不同抽象層次的類聚集到相同的包之下
- internal 可方便處理內(nèi)外隔離,提升模塊代碼內(nèi)聚減少接口暴露
- internal 修飾的Kotlin類或者成員在Java中可直接訪問
針對(duì)Kotlin中被internal修飾的類能被Java訪問,可以通過一些特殊手段讓其不能訪問,例如下例:
internal class CoreApiKotlinA {
@JvmName("%abcd")
internal fun a(){
println("Hello A")
}
}
<3>構(gòu)造器可見性
class Cat :Anim {
var name: String
var age: Int
private constructor(name: String, age: Int):super() {
this.name = name
this.age = age
}
}
可用于單例
<4>類的屬性可見性
class Half (private var name: String,var age: Int){
private var sex:Boolean=true
init {
println("$name---$age")
}
}
//只能訪問到age和sex
var half = Half("111", 2)
half.age=11
屬性的getter和setter
- 屬性的getter必須和屬性的可見 一致
下例中 sex是public 所以不能把getter設(shè)置為private
class Half (private var name: String,var age: Int){
var sex:Boolean=true
private set
//private get
}
- 屬性的setter可見性不得大于屬性的可見性
class Half(private var name: String, var age: Int) {
private var firstName: String = ""
// public set
}
<5>頂級(jí)聲明的可見性(Kotlin獨(dú)有)
- 頂級(jí)聲明指文件內(nèi)直接定義的屬性,函數(shù),類等
- 頂級(jí)聲明不支持protect
- 頂級(jí)聲明被private修飾表示文件內(nèi)可見
3,類屬性的延遲初始化
為什么要延遲初始化?
- 類屬性必須在構(gòu)造時(shí)進(jìn)行初始化
- 某些成員只有在類初始化后才會(huì)別初始化
class Beer(var name: String,var age: Int){
lateinit var firstName:String
}
常見的例子有:在Android中控件的初始化是在Activity的onCreate()方法中才能初始化,可以使用lateinit進(jìn)行延遲初始化
lateinit的注意事項(xiàng)
- lateinit 會(huì)讓編譯器忽略變量的初始化,不支持Int等基本類型
- 開發(fā)者必須能夠在完全確定變量的生命周期下使用lateinit
- 不要在復(fù)雜的邏輯中使用lateinit,它只會(huì)讓代碼變得更加脆弱
- Kotlin在1.2引入判斷l(xiāng)ateinit屬性是否初始化的api 最好不要用
lazy
4蚓聘,代理 Delegate
接口代理
對(duì)象X 代替當(dāng)前類A 實(shí)現(xiàn)接口B的方法
我 代替 你 處理了 它
屬性代理
對(duì)象X代替屬性a實(shí)現(xiàn)getter/setter方法
<1>接口代理
定義接口
interface InterFace {
fun funA()
fun funB()
fun funC()
}
定義增強(qiáng)接口類
class InterfaceWrapper(var inter:InterFace):InterFace{
override fun funA() {
inter.funA()
}
override fun funB() {
inter.funB()
}
override fun funC() {
println("---------------")
inter.funC()
}
代理形式
class InterWrapper (private val inter: InterFace):InterFace by inter{
override fun funC() {
println("---------------")
}
}
接口 inter 代理 InterWrapper 實(shí)現(xiàn) 接口 InterFace 的方法
對(duì)于對(duì)象inter的唯一要求就是實(shí)現(xiàn)被代理的接口,因?yàn)槿绻鹖nter沒有實(shí)現(xiàn)接口方法的話,就不具有代理的能力
小案例 創(chuàng)建一個(gè)SupportArray
class SupperArray<E>(
private val list: MutableList<E?> = ArrayList(),
private val map: MutableMap<String, E> = HashMap()
) : MutableList<E?> by list, MutableMap<String, E> by map {
override fun isEmpty(): Boolean {
return list.isEmpty() && map.isEmpty()
}
override fun clear() {
list.clear()
map.clear()
}
override val size: Int
get() = list.size + map.size
override fun set(index: Int, element: E?): E? {
if (list.size <= index) {
repeat(index - list.size + 1) {
list.add(null)
}
}
return list.set(index, element)
}
override fun toString(): String {
return "list:${list};map:${map}"
}
}
SupportArray
- 實(shí)現(xiàn) MutableList 被list代理了接口的實(shí)現(xiàn)
- 實(shí)現(xiàn)MutableMap 被map代理了接口的實(shí)現(xiàn)
使用SupperArray
fun main() {
val supperArray = SupperArray<String>()
supperArray.add("Hello")
supperArray["Hello"] = "wwwwwwwwww"
supperArray["Word"] = "fffffffffffff"
supperArray[4] = "$$$$$$$$$"
println(supperArray)
}
輸出
list:[Hello, null, null, null, $$$$$$$$$];
map:{Word=fffffffffffff, Hello=wwwwwwwwww}
<2>屬性代理
getter方法代理
class PersonNow(var sex: Boolean) {
val firstName: String by lazy {
if (sex)
"nan"
else
"nv"
}
}
源碼分析
--lazy方法
public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
SynchronizedLazyImpl(initializer)
--Lazy.kt
@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?,
property: KProperty<*>): T = value
可以看到Lazy定義的方法 getValue
getValue(thisRef: Any?, property: KProperty<*>)
要想代理屬性的getter需要重寫該方法
setter方法代理
對(duì)于setter方法,其代理可分為兩步:開始設(shè)置和設(shè)置之后
Delegates.observable代理設(shè)置之后
class Manger {
var state:Int by Delegates.observable(0){
property, oldValue, newValue ->
println("$oldValue,$newValue")
}
}
Delegates.vetoable代理設(shè)置之前
class Mantou {
var ss: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
println("$oldValue,$newValue")
newValue % 2 == 0
}
}
調(diào)用
fun main() {
val mantou = Mantou()
mantou.ss = 1
mantou.ss = 2
mantou.ss = 3
mantou.ss = 4
}
結(jié)果
0,1
0,2
2,3
2,4
Delegates.vetoable 給定初始化值后其結(jié)果返回值為Boolean,如果返回true表示允許本次對(duì)屬性的設(shè)置,屬性的值為newValue,返回false表示攔截此次設(shè)置值,屬性的值仍然為oldValue
源碼得知
Delegate.observable 返回 ReadWriteProperty
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
對(duì)于var 來說需要實(shí)現(xiàn)下列方法,就可以代理屬性
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
對(duì)val來說 實(shí)現(xiàn)下方法即可代理屬性
public operator fun getValue(thisRef: R, property: KProperty<*>): T
具體例子
class Food {
var x: Int by X()
}
class X {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
println("getValue")
return 2
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, i: Int) {
println("setValue")
}
}
fun main() {
val food = Food()
food.x=1;
println(food.x)
}
運(yùn)行結(jié)果
setValue
getValue
2
對(duì)food 的屬性 x 調(diào)用其getter或者setter時(shí)會(huì)調(diào)用的屬性代理
5,單例object
Kotlin中單例
- 使用object進(jìn)行定義
- 默認(rèn)生成無參構(gòu)造函數(shù)且不能自定義構(gòu)造器,但是可以通過init{}代碼塊進(jìn)行一些初始化操作
- 跟普通的Kotlin類一樣可以實(shí)現(xiàn)接口,可接繼承其他類
- 其本質(zhì)就是Kotlin使用餓漢式內(nèi)部生成了INSTANCE的實(shí)例
public object Sigten {
var x: Int = 0
var y: String = ""
fun mm() {}
}
Kotlin中訪問
fun main() {
Sigten.x = 3
Sigten.y = "WWWWWWWW"
println("${Sigten.x}||${Sigten.y}")
}
Java中訪問
Sigten.INSTANCE.mm();
Sigten.INSTANCE.setX(1);
Sigten.INSTANCE.getX();
由于Kotlin是一門跨平臺(tái)語言,不能因?yàn)镴vm上有靜態(tài)變量就定義出相應(yīng)的變量類型,這對(duì)于C語言和JavaScript行不通
@JvmStatic 模擬JVM平臺(tái)的靜態(tài)變量
public object Sigten {
@JvmStatic var x: Int = 0
@JvmStatic fun mm() {
}
}
Java調(diào)用
Sigten.mm();
Sigten.setX(1);
Sigten.getX();
@JvmField 不讓Kotlin屬性生成getter和setter,直接訪問屬性
public object Sigten {
@JvmStatic var x: Int = 0
@JvmField var y:Int=0
@JvmStatic fun mm() {
}
}
Java調(diào)用
Sigten.mm();
Sigten.setX(1);
Sigten.getX();
Sigten.y=1;
伴生對(duì)象
- 是某個(gè)類的另一半
- 其名稱和所在類相同
- 伴生對(duì)象也是單例的
class FoodX {
companion object {
@JvmStatic var x: Int = 0
@JvmStatic fun mm() {
}
}
}
想當(dāng)于Java中
public class FoodX {
public static int x = 0;
public static void mm(){}
}
6,內(nèi)部類
- 內(nèi)部類分為靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類
- 靜態(tài)內(nèi)部類不需要引用外部對(duì)象,可直接創(chuàng)建對(duì)象
- 非靜態(tài)內(nèi)部類需要引用外部對(duì)象,容易引起內(nèi)存泄露
class Outer{
//內(nèi)部類
inner class Inner(){}
//靜態(tài)內(nèi)部類
class StaticInner(){}
}
使用
fun main() {
//創(chuàng)建非靜態(tài)內(nèi)部類
var inner = Outer().Inner()
//創(chuàng)建靜態(tài)內(nèi)部類
var staticInner = Outer.StaticInner()
}
<1>內(nèi)部object
- 內(nèi)部object 不存在非靜態(tài),都是靜態(tài)
object OuterObject {
object InnerStaticObject {
var x: Int = 0
}
}
使用
OuterObject.InnerStaticObject.x=3
<2>匿名內(nèi)部類
object :Runnable{
override fun run() {
}
}
和Java中不同的是,Kotlin中匿名內(nèi)部類支持多實(shí)現(xiàn)或繼承父類
例如
object :Runnable,Cloneable{
override fun run() {
}
}
Java中也可以實(shí)現(xiàn)方法中多繼承 使用LocalClass
public class TestObjeck {
public static void main(String[] args) {
class LocalClass implements Cloneable,Runnable{
@Override
public void run() {
}
}
}
}
Kotlin中不僅支持LocalClass 還支持localMethod(方法中定義方法)
object :Runnable,Cloneable{
override fun run() {
}
}
class LocalClass:Runnable,Cloneable{
override fun run() {
}
}
fun localMethod(){}
7,數(shù)據(jù)類 data class
component: 定義在主構(gòu)造器中的屬性又被稱為component
- 數(shù)據(jù)類和一般類最大的區(qū)別就在于component
- Kotlin中的data class 不等價(jià)與Java bean
- 編譯器會(huì)基于component 自動(dòng)生成 equals/hashCode/toString/copy
data class TT(val name: String, val age: Int) {}
fun main() {
var tt = TT("", 4)
}
如何合理的使用data class
- 單純的把data class 當(dāng)成數(shù)據(jù)結(jié)構(gòu)使用既是純數(shù)據(jù),大多數(shù)情況下不需要額外的實(shí)現(xiàn)
- 主構(gòu)造函數(shù)的變量類型 最好為基本類型\String 或者其他data class 保證數(shù)據(jù)類為純數(shù)據(jù)
- component 不能定義getter和setter 為了防止通過getter或者setter 進(jìn)行數(shù)據(jù)篡改
- 定義構(gòu)造函數(shù)的變量 最好定義為 val ,如果可變會(huì)導(dǎo)致其hashCode等值的改變 如果該對(duì)象在hashset中 則無法移除該元素
為了實(shí)現(xiàn)一些特殊的需求,需要data class 有無參構(gòu)造和可以被繼承,官方提供了 noArg 和allOpen 插件進(jìn)行支持
id 'org.jetbrains.kotlin.plugin.noarg' version '1.3.60'
id 'org.jetbrains.kotlin.plugin.allopen' version '1.3.60'
8, 枚舉類 enum class
- 和 Java中枚舉相似 區(qū)別是通過 enum class 定義
無參構(gòu)造枚舉
enum class CC {
Idle, Busy
}
有參構(gòu)造枚舉
enum class DD(arg: Int) {
Idle(0), Busy(1)
}
枚舉實(shí)現(xiàn)接口--統(tǒng)一實(shí)現(xiàn)方式
enum class FF : Runnable {
Idle, Busy;
override fun run() {
}
}
枚舉實(shí)現(xiàn)接口--單獨(dú)實(shí)現(xiàn)方式
enum class EE : Runnable {
Idle {
override fun run() {
}
},
Busy {
override fun run() {
}
};
}
<1>因?yàn)閑num 枚舉本身是個(gè)類 所以可以為其定義擴(kuò)展方法
<2>枚舉本事是可數(shù)的所在when 條件語句中可以不加else
<3> 因?yàn)槊杜e本身有順序的概念所以可以對(duì)其進(jìn)行比較大小
<4> 枚舉有順序所以也有區(qū)間概念
enum class Color {
White, Red, Black, Pink, Ori, Yellow
}
fun main() {
val ran = Color.Black..Color.Yellow
val color = Color.White
color in ran
}
9,密封類 sealed class
- 密封類是一種特殊的抽象類
- 密封類的子類必須定義在與自身相同的文件中
- 因?yàn)槊芊忸惖淖宇惗x范圍有限,所以密封類的子類有限,一旦定義好密封類的子類后,外部不可能出現(xiàn)其他子類
- 密封類首先是個(gè)抽象類,其次是個(gè)密封類
例子
sealed class PlayState
object Idle : PlayState()
object Playing : PlayState() {
fun start() {}
fun stop() {}
}
object Error : PlayState() {
fun recover() {}
}
使用上面定義的幾個(gè)類型做個(gè)小案例
class Player {
var state: PlayState = Idle
fun play() {
this.state = when (val state = this.state) {
is Idle -> {
Playing.also {
it.start()
}
}
is Playing -> {
state.stop()
state.also {
it.start()
}
}
is Error -> {
state.recover()
Playing.also {
it.start()
}
}
}
}
}
如何區(qū)分使用密封類還是枚舉呢
- 如果需要進(jìn)行類型區(qū)分則是使用密封類,如果要進(jìn)行值的區(qū)分 則用枚舉
- 枚舉類不能創(chuàng)建對(duì)象,而密封類可以創(chuàng)建對(duì)象
枚舉不能創(chuàng)建對(duì)象這點(diǎn)個(gè)人理解起來比較難一點(diǎn),為什么不能枚舉就不能創(chuàng)建對(duì)象呢?
因?yàn)槊杜e就是實(shí)例對(duì)象的存在<O(∩_∩)O哈哈~>
而密封類則不同,他的分支是類型,只要是子類的對(duì)象就行,因此可以創(chuàng)建多個(gè)對(duì)象
10,內(nèi)聯(lián)類 inline class
- 內(nèi)聯(lián)類是對(duì)某一個(gè)類型的包裝
- 類似于Java中裝箱類型的一種類型
- 編譯器會(huì)盡可能使用被包裝的類型進(jìn)行優(yōu)化
- 內(nèi)聯(lián)類目前在1.3版本中處于公測(cè)階段,謹(jǐn)慎使用
使用注意
- 內(nèi)聯(lián)類不能有backingfiled 只能有方法
- 內(nèi)聯(lián)類可以實(shí)現(xiàn)接口,但不能繼承父類也不能被繼承
例子
inline class BoxInt(private val value: Int) {
operator fun inc(): BoxInt {
return BoxInt(value + 1)
}
}
內(nèi)聯(lián)類的使用場(chǎng)景
- 官方 使用內(nèi)聯(lián)類 實(shí)現(xiàn)無符號(hào)類型
- 內(nèi)聯(lián)類模擬枚舉,因?yàn)槊杜e內(nèi)存開銷比較大,和dex文件大小