一、類
1.1 類聲明
Kotin中使用關(guān)鍵字class
聲明類,且默認(rèn)是public
靴迫。如果一個(gè)類沒有類體濒蒋,可以省略花括號(hào)
class Invoice { /*……*/ }
class Test
關(guān)于類盐碱,有幾個(gè)細(xì)節(jié)需要注意:
- Kotlin中的成員變量必須初始化
- 類默認(rèn)不可以被繼承,如需被繼承沪伙,需要加上
open
關(guān)鍵字 - Kotlin中用
:
表示繼承與實(shí)現(xiàn)瓮顽,即同時(shí)表示Java
中的extends
與implement
- Kotlin類中
override
變成關(guān)鍵字,且省略了protected
關(guān)鍵字围橡,即override
函數(shù)可見性繼承自父類
1.2 構(gòu)造函數(shù)
//Java 格式
public class User {
int id;
public User(int id, String name) {
this.id = id;
}
}
//kotlin格式
class User {
val id: Int
constructor(id: Int) {
this.id = id
}
}
Koltin中定義一個(gè)構(gòu)造函數(shù)暖混,使用constructor
關(guān)鍵字,默認(rèn)為public
翁授。一般不會(huì)像前述方式實(shí)現(xiàn)拣播,而是通過主構(gòu)造器實(shí)現(xiàn)。
主構(gòu)造函數(shù)
一個(gè)類可以有一個(gè)主構(gòu)造函數(shù)以及一個(gè)或多個(gè)次構(gòu)造函數(shù)收擦。
主構(gòu)造函數(shù)是類頭的一部分:它跟在類名(與可選的類型參數(shù))后贮配。
class Person constructor(firstName: String) { /*……*/ }
//如果主構(gòu)造函數(shù)沒有任何注解或者可見性修飾符,可以省略這個(gè) constructor 關(guān)鍵字炬守,等價(jià)于
class Person(firstName: String) { /*……*/ }
//如果構(gòu)造函數(shù)有注解或可見性修飾符牧嫉,這個(gè) constructor 關(guān)鍵字是必需的,并且這些修飾符在它前面
class Customer public @Inject constructor(name: String) { /*……*/ }
關(guān)于主構(gòu)造函數(shù),主要有兩部分內(nèi)容需要關(guān)注酣藻,即初始化代碼塊與屬性聲明
-
初始化代碼塊
主構(gòu)造函數(shù)不能包含任何代碼曹洽,所以初始化的代碼放到
init
關(guān)鍵字作為前綴的初始化塊(initializer blocks)中class Test constructor(num : Int){ init { println("num = $num") } } var test = Test(1) //類實(shí)例化
init
代碼塊執(zhí)行順序:在次構(gòu)造函數(shù)之前,主構(gòu)造函數(shù)之后執(zhí)行init
代碼塊可以寫多個(gè) -
屬性聲明
可以直接在主構(gòu)造函數(shù)中簡(jiǎn)便聲明屬性
class Test(val num1 : Int, var num2 : Long, val str : String){ ... } //等價(jià)于 class Test(num1 : Int, num2 : Long, str : String){ val num1: Int = num1 var num2: Long = num2 val str: String = str ... }
次構(gòu)造函數(shù)
Kotlin
中支持二級(jí)構(gòu)造函數(shù)辽剧。以constructor
關(guān)鍵字作為前綴
class Person {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(parent: Person) {
parent.children.add(this)
}
}
如果類有一個(gè)主構(gòu)造函數(shù)送淆,每個(gè)次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù),可以直接委托或者通過別的次構(gòu)造函數(shù)間接委托怕轿。
委托到同一個(gè)類的另一個(gè)構(gòu)造函數(shù)用 this
關(guān)鍵字
class User constructor(var name: String) {
constructor(name: String, id: Int) : this(name) {
}
constructor(name: String, id: Int, age: Int) : this(name, id) {
}
}
如果類主構(gòu)造函數(shù)的所有參數(shù)都具有默認(rèn)值偷崩,編譯器將生成一個(gè)額外的無參數(shù)構(gòu)造函數(shù),它將使用默認(rèn)值撞羽。
fun main(args: Array<String>) {
var test = Test() //num1 = 10 num2 = 20
var test1 = Test(1,2) //num1 = 1 num2 = 2
var test2 = Test(4,5,6) //num1 = 4 num2 = 5 num1 = 4 num2 = 5 num3 = 6
}
class Test constructor(num1: Int = 10 , num2: Int = 20){
init {
println("num1 = $num1\t num2 = $num2")
}
constructor(num1 : Int = 1, num2 : Int = 2, num3 : Int = 3) : this(num1 , num2){
println("num1 = $num1\t num2 = $num2 \t num3 = $num3")
}
}
類實(shí)例化
創(chuàng)建一個(gè)類的實(shí)例阐斜,調(diào)用類的構(gòu)造函數(shù)即可
val invoice = Invoice()
val customer = Customer("Joe Smith")
注意 Kotlin 并沒有
new
關(guān)鍵字
二、屬性與字段
2.1 屬性聲明
類中屬性用關(guān)鍵字var
或val
進(jìn)行聲明
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
}
屬性使用:直接用名稱引用即可
fun copyAddress(address: Address): Address {
val result = Address() // Kotlin 中沒有“new”關(guān)鍵字
result.name = address.name // 將調(diào)用訪問器
result.street = address.street
// ……
return result
}
2.2 屬性訪問器
在Java中诀紊,需要加上getter
方法和setter方法才可以調(diào)用屬性谒出,而在Kotlin中可直接用名稱引用,這時(shí)因?yàn)槠潆[含默認(rèn)實(shí)現(xiàn)了getter
和setter
(讀訪問器與寫訪問器)
class User {
var name = "Czh"
var userName: String
get() = name
set(value) {
name = value
}
//用val只讀標(biāo)識(shí)只讀
val sex: String
get() = "男"
}
這里有幾個(gè)細(xì)節(jié)需要重點(diǎn)說明一下:
-
val
修飾的只讀屬性邻奠,不能有寫訪問器setter()
- 關(guān)鍵字
get
和set
分別對(duì)應(yīng)getter
和setter
-
getter
與setter
自定義實(shí)現(xiàn)位于聲明的變量下方 -
setter
函數(shù)的參數(shù)為value
笤喳,且使用時(shí)需要設(shè)置幕后字段
簡(jiǎn)單來說,Kotlin中每個(gè)成員變量就是一個(gè)屬性碌宴,每個(gè)屬性中包含了(Field + Getter + Setter)
屬性訪問器可見性
可以根據(jù)實(shí)際情況修改屬性訪問器的可見性
var str1 = "kotlin_1"
private set //setter()訪問器的私有化,且擁有默認(rèn)實(shí)現(xiàn)
var str3 = "kotlin_3"
private get //編譯出錯(cuò)杀狡,因?yàn)椴荒苡術(shù)etter()的訪問器可見性
屬性引用
屬性與方法一樣,可以獲取其引用贰镣。分為類獲取屬性引用和實(shí)例獲取屬性引用
-
類獲取屬性引用
// 通過類對(duì)象獲取該類的屬性引用 val ageRef = Person::age // 通過類名屬性引用操作該屬性時(shí)需要一個(gè)Receiver val person = Person(18, "Kotlin") // 通過屬性引用調(diào)用get方法 val age = ageRef.get(person)
-
實(shí)例獲取屬性引用
// 通過實(shí)例獲取該類的屬性引用 val ageRef = person::age // 使用時(shí)無需Receiver ageRef.set(30)
其中獲取類中屬性引用時(shí)呜象,其屬性必須對(duì)獲取方式可見。
2.3 幕后字段
Kotlin 的類不能直接聲明域變量八孝,如果屬性需要董朝,kotlin會(huì)自動(dòng)提供鸠项。
在屬性的取值方法或設(shè)值方法中, 使用 field
標(biāo)識(shí)符可以引用這個(gè)backing field
var counter = 0 // 注意:這個(gè)初始器直接為后端域變量
set(value) {
if (value >= 0) field = value
}
field
標(biāo)識(shí)符只能在屬性的訪問器內(nèi)使用
更好的理解backing field
干跛,需要知道Kotlin中,訪問一個(gè)屬性的實(shí)質(zhì)是什么祟绊?
屬性的讀寫楼入,實(shí)質(zhì)是執(zhí)行了屬性的getter
與setter
訪問器
class Person {
var name:String = "Paul"
get() = "i am getter,name is Jake"
}
var person = Person()
val name = person.name
println("打印結(jié)果:$name") //打印結(jié)果:i am getter,name is Jake
class Person {
var name:String = "Paul"
set(value) {
println("執(zhí)行了寫訪問器,參數(shù)為:$value")
}
}
var person = Person()
person.name = "hi,this is new value" //執(zhí)行了寫訪問器牧抽,參數(shù)為:hi,this is new value
println("打印結(jié)果:${person.name}") //打印結(jié)果:Paul
注意:前述給屬性賦值嘉熊,執(zhí)行了寫訪問器
setter
,但是沒有屬性賦值扬舒,所以還是返回默認(rèn)值
如果按照J(rèn)ava的方法實(shí)現(xiàn)setter
阐肤,則會(huì)直接報(bào)錯(cuò)
class Person {
var name = ""
set(value) {
this.name = value //報(bào)錯(cuò)
}
}
上述代碼會(huì)直接內(nèi)存溢出錯(cuò)誤,反編譯為Java代碼
/**java*/
public final class Person {
@NotNull
private String name = "Paul";
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String value) {
this.setName(value); //setName遞歸調(diào)用
}
}
若無field
字段,在set方法中進(jìn)行屬性賦值會(huì)出現(xiàn)遞歸調(diào)用的情況孕惜。
Kotlin中愧薛,如果屬性至少一個(gè)訪問器使用默認(rèn)實(shí)現(xiàn),則會(huì)自動(dòng)提供幕后字段衫画,用關(guān)鍵字field
表示毫炉,其只能在getter
與setter
內(nèi)訪問。
class Person {
var name:String = ""
get() = field
set(value) {
field = value
}
}
class Person {
var name:String = ""
}
上述兩個(gè)屬性聲明是等價(jià)的削罩。
通過backing field
可以幫助我們?cè)趯傩栽L問器做一系列操作
class Person(var gender:Gender){
var name:String = ""
set(value) {
field = when(gender){
Gender.MALE -> "Jake.$value"
Gender.FEMALE -> "Rose.$value"
}
}
}
enum class Gender{
MALE,
FEMALE
}
2.4 幕后屬性
有時(shí)我們希望一個(gè)屬性:對(duì)外表現(xiàn)為只讀瞄勾,對(duì)內(nèi)表現(xiàn)為可讀可寫,我們將這種屬性稱為幕后屬性
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 初始化
}
return _table ?: throw AssertionError("Set to null by another thread")
}
將
_table
屬性聲明為private
,因此外部是不能訪問的弥激,內(nèi)部可以訪問进陡,外部訪問通過table
屬性,而table
屬性的值取決于_table
微服,這里_table
就是幕后屬性四濒。
三、可見性修飾符
public
:表示公有职辨,系統(tǒng)默認(rèn)使用此修飾符internal
:表示模塊 盗蟆,例如創(chuàng)建一個(gè)函數(shù)僅開放給module
內(nèi)部使用,不想對(duì)library的使用者可見舒裤,這是就應(yīng)該用internal
可見性修飾符protected
:表示私有+
子類喳资。此修飾符不能用于頂層
聲明。-
private
:表示私有 腾供。Java中
private
表示類中可見仆邓,作為內(nèi)部類時(shí)對(duì)外部類可見。Kotlin中
private
表示類中或所在文件內(nèi)可見伴鳖,作為內(nèi)部類時(shí)對(duì)外部類不可見class Sample { private val propertyInClass = 1 // 僅 Sample 類中可見 } private val propertyInFile = "A string." //整個(gè)文件可見
class Outter { fun method() { val inner = Inner() ?? val result = inner.number * 2 // 報(bào)錯(cuò)节值,對(duì)外部類不可見 } class Inner { private val number = 1 } }
四、繼承類
4.1 定義
定義繼承類的關(guān)鍵字為:open
榜聂。其中類搞疗、成員,方法都要使用open
關(guān)鍵字须肆,不過對(duì)于abstract
類默認(rèn)具有open
屬性匿乃。
open class Demo{
open var num = 3
open fun foo() = "foo"
open fun bar() = "bar"
}
class DemoTest : Demo(){
// Kotlin使用繼承是使用`:`符號(hào)
}
其中在 Kotlin 中,所有類都有一個(gè)共同的超類 Any
豌汇,這對(duì)于沒有超類型聲明的類是默認(rèn)超類
class Example // 從 Any 隱式繼承
Any
有三個(gè)方法:equals()
幢炸、 hashCode()
與 toString()
。
修飾符 | 相應(yīng)類的成員 | 注解 |
---|---|---|
final |
不能被覆寫 | 在kotlin中默認(rèn)所有的方法和類都是final屬性 |
open |
可以被覆寫 | 需要被明確指出 |
abstract |
必須要覆寫 | 不能被實(shí)例化拒贱,默認(rèn)具有open屬性宛徊。 |
override |
覆寫超類的方法 | 如果沒有被指定為final佛嬉,則默認(rèn)具有open屬性 |
4.2 構(gòu)造函數(shù)
主要分為實(shí)現(xiàn)類無主構(gòu)造函數(shù)和有主構(gòu)造函數(shù)。
實(shí)現(xiàn)類無主構(gòu)造函數(shù)
這種情況闸天,次構(gòu)造函數(shù)必須使用super
關(guān)鍵字初始化基類型或委托給另一個(gè)構(gòu)造函數(shù)
class MyView : View{
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr)
}
存在主構(gòu)造函數(shù)
這種情況巷燥,其基類必須用實(shí)現(xiàn)類主構(gòu)造函數(shù)的參數(shù)就地初始化
class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
: View(context, attrs, defStyleAttr) {
constructor(context: Context?) : this(context,null,0)
constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}
4.3 重寫與重載
重寫
當(dāng)基類中的函數(shù),沒有用open
修飾符修飾的時(shí)候号枕,實(shí)現(xiàn)類中不得出現(xiàn)與基類相同函數(shù)名的函數(shù)
open class Demo{
fun test(){}
}
class DemoTest : Demo(){
fun test(){} //編輯器直接報(bào)紅報(bào)錯(cuò)
override fun test(){} //同樣報(bào)錯(cuò)
}
當(dāng)一個(gè)基類去繼承另外一個(gè)基類時(shí)缰揪,第二個(gè)基類不想去覆蓋掉第一個(gè)基類的方法時(shí),第二個(gè)基類的該方法使用final
修飾符修飾葱淳。
open class A{
open fun foo(){}
}
open class B : A(){
// 這里使用final修飾符修飾該方法钝腺,禁止覆蓋掉類A的foo()函數(shù)
final override fun foo(){}
}
重載
Kotlin中函數(shù)重載與Java一致
open class Demo{
open fun foo() = "foo"
}
class DemoTest : Demo(){
fun foo(str: String) : String{
return str
}
override fun foo(): String {
return super.foo()
}
}
屬性重寫
在基類中聲明的屬性,可在其實(shí)現(xiàn)類中重寫該屬性赞厕。
- 該屬性必須以
override
關(guān)鍵字修飾 - 重寫前后屬性類型保持一致
- 僅可重寫屬性的
Getter
- 可以用
var
重寫基類的val
修飾的屬性艳狐,反之不行
open class Demo{
open var num = 3
}
class DemoTest : Demo(){
override var num: Int = 10
}
open class Demo{
open val valStr = "我是用val修飾的屬性"
}
class DemoTest : Demo(){
override var valStr: String = "abc"
set(value){field = value}
}
//重寫屬性的時(shí)候不用get() = super.xxx,因?yàn)檫@樣的話,不管你是否重新為該屬性賦了新值皿桑,還是支持setter(),在使用的時(shí)候都調(diào)用的是基類中的屬性值毫目。
class DemoTest : Demo(){
override var valStr: String = "abc"、
get() = super.valStr
set(value){field = value}
}
fun main(arge: Array<String>>){
println(DemoTest().valStr) //我是用val修飾的屬性
val demo = DemoTest()
demo.valStr = "1212121212"
println(demo.valStr) //我是用val修飾的屬性
}
重寫屬性的時(shí)候不用get() = super.xxx,
因?yàn)檫@樣的話诲侮,不管你是否重新為該屬性賦了新值镀虐,還是支持setter()
,在使用的時(shí)候都調(diào)用的是基類中的屬性值。
open class Demo{
open val valStr = "我是用val修飾的屬性"
}
class DemoTest : Demo(){
override var valStr: String = "abc"沟绪、
get() = super.valStr
set(value){field = value}
}
println(DemoTest().valStr) //我是用val修飾的屬性
val demo = DemoTest()
demo.valStr = "1212121212"
println(demo.valStr) //我是用val修飾的屬性
4.4 初始化順序
open class Base(val name: String) {
//基類初始化塊
init { println("Initializing Base") }
//基類屬性初始化
open val size: Int =
name.length.also { println("Initializing size in Base: $it") }
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {
//子類初始化塊
init { println("Initializing Derived") }
//子類屬性初始化
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}
fun main() {
val d = Derived("hello", "world")
}
Argument for Base: Hello // 基類init代碼塊
Initializing Base
Initializing size in Base: 5 //基類屬性初始化
Initializing Derived //子類init代碼塊
Initializing size in Derived: 10 //子類屬性初始化
可知繼承類的執(zhí)行順序是:先基類的init
代碼塊刮便,然后是基類的屬性初始化,接下來是子類的init
代碼塊與屬性的初始化绽慈。
五恨旱、接口與枚舉
5.1 接口定義
使用關(guān)鍵字 interface
來定義接口,且一個(gè)類或?qū)ο罂梢詫?shí)現(xiàn)一個(gè)或多個(gè)接口
interface MyInterface {
fun bar()
fun foo() {
// 可選的方法體
}
}
class Child : MyInterface {
override fun bar() {
// 方法體
}
}
5.2 屬性與方法
接口屬性
接口屬性主要有如下特點(diǎn):
- 屬性要么是抽象坝疼,要么提供訪問器的實(shí)現(xiàn)
- 不可以有幕后字段
interface Demo3Interface{
val num1: Int //抽象的
val num2 : Int
}
class Demo3(override val num1: Int, override val num2: Int) : Demo3Interface{
fun sum() : Int{
return num1 + num2
}
}
var demo = Demo3(1,2)
println(demo.sum()) // 3
interface Demo3Interface{
// val num3: Int = 3 這種方式不提供搜贤,會(huì)直接報(bào)錯(cuò)的
val num3: Int
get() = 3
val num4: Int
}
class Demo3(override val num1: Int, override val num2: Int) : Demo3Interface{
// 提供訪問器實(shí)現(xiàn)
override val num3: Int
get() = super.num3
// 手動(dòng)賦值
override var num4: Int = 4
fun result() : Int{
return num3 + num4
}
}
fun main(args: Array<String>) {
println(demo.result()) // 7
// 在這里也可以改變接口屬性的值
demo.num4 = 10
println(demo.result()) // 13
}
接口方法
接口方法主要有如下特點(diǎn):
- 不帶結(jié)構(gòu)體的函數(shù)可以省略大括號(hào)
- 不用強(qiáng)制重寫帶結(jié)構(gòu)體的函數(shù),可直接調(diào)用
interface Demo2Interface{
fun fun1() //無參無返回
fun fun2(num: Int) //有參無返回
fun fun3(num: Int) : Int //有參有返回
// 有結(jié)構(gòu)體钝凶, 可以不重寫
fun fun4() : String{
return "fun4"
}
fun fun5(){} //等價(jià)fun1
}
class Demo2 : Demo2Interface{
override fun fun1() {
println("我是fun1()方法")
}
override fun fun2(num: Int) {
println("我是fun2()方法仪芒,我的參數(shù)是$num")
}
override fun fun3(num: Int): Int {
println("我是fun3()方法,我的參數(shù)是$num腿椎,并且返回一個(gè)Int類型的值")
return num + 3
}
override fun fun4(): String {
println("我是fun4()方法桌硫,并且返回一個(gè)String類型的值")
return super.fun4()
}
}
fun main(args: Array<String>) {
var demo = Demo2()
demo.fun1()
demo.fun2(5)
println(demo.fun3(10))
println(demo.fun4())
//可以不重寫該方法直接調(diào)用
demo.fun5()
}
/************輸出結(jié)果***************/
我是fun1()方法
我是fun2()方法夭咬,我的參數(shù)是5
我是fun3()方法啃炸,我的參數(shù)是10,并且返回一個(gè)Int類型的值
13
我是fun4()方法卓舵,并且返回一個(gè)String類型的值
fun4
5.3 沖突解決
某些場(chǎng)景下南用,可能出現(xiàn)不同父類存在相同方法名的方法,導(dǎo)致子類實(shí)現(xiàn)時(shí)沖突,用用super<接口名>.方法名
來區(qū)分
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
5.4 枚舉定義
聲明格式:
enum class 類名{
...
}
enum class State{
NORMAL,NO_DATA,NO_INTERNET,ERROR,OTHER
}
//訪問枚舉常量
State.NORMAL.name
State.NO_DATA.name
Kotlin中枚舉的特點(diǎn)是:
- 每一個(gè)枚舉常量都是一個(gè)對(duì)象裹虫,用逗號(hào)分隔
- 訪問方式為:枚舉類名.枚舉常量.屬性
枚舉匿名類
實(shí)現(xiàn)枚舉常量的匿名類肿嘲,須提供一個(gè)定義在枚舉類內(nèi)部的抽象方法,且必須在枚舉變量后面筑公,最后一個(gè)枚舉變量用分號(hào)結(jié)束雳窟。
enum class ConsoleColor {
BLACK {
override fun print() {
println("我是枚舉常量 BLACK ")
}
},
GREEN {
override fun print() {
println("我是枚舉常量 GREEN ")
}
};
abstract fun print()
}
fun main(args: Array<String>) {
ConsoleColor.BLACK.print() //我是枚舉常量 BLACK
}
5.5 枚舉類使用
枚舉類對(duì)象內(nèi)置兩個(gè)屬性:
-
name
:枚舉對(duì)象名稱 -
ordinal
:枚舉對(duì)象索引
枚舉類同時(shí)提供了values()
和valueOf()
方法來檢測(cè)指定的名稱與枚舉類中定義的任何枚舉常量是否匹配,且提供enumValues()
和 enumValueOf()
函數(shù)以泛型的方式訪問枚舉類中的常量
enum class Color(var argb : Int){
RED(0xFF0000),
WHITE(0xFFFFFF),
BLACK(0x000000),
GREEN(0x00FF00)
}
println("name = " + Color.RED.name + "\tordinal = " + Color.RED.ordinal)
println("name = " + Color.WHITE.name + "\tordinal = " + Color.WHITE.ordinal)
println("name = " + Color.BLACK.name + "\tordinal = " + Color.BLACK.ordinal)
println("name = " + Color.GREEN.name + "\tordinal = " + Color.GREEN.ordinal)
/************輸出結(jié)果*********/
name = RED ordinal = 0
name = WHITE ordinal = 1
name = BLACK ordinal = 2
name = GREEN ordinal = 3
println(enumValues<Color>().joinToString { it.name })
println(enumValueOf<Color>("RED"))
/************輸出結(jié)果*********/
RED, WHITE, BLACK, GREEN
RED
println(Color.valueOf("RED"))
println(Color.values()[0])
println(Color.values()[1])
println(Color.values()[2])
println(Color.values()[3])
/************輸出結(jié)果*********/
RED
RED
WHITE
BLACK
GREEN
枚舉的一些其他特性:
- 可以定義擴(kuò)展方法和擴(kuò)展屬性
- 可以用在條件分支
when
- 有順序匣屡,可以用來比較大小
- 可以應(yīng)用在區(qū)間
- 也是類封救,可以實(shí)現(xiàn)接口
六、數(shù)據(jù)類
Kotlin中提供了關(guān)鍵字data
捣作,聲明一個(gè)類為數(shù)據(jù)類
data class 類名(var param1 :數(shù)據(jù)類型,...){}
//或者
data class 類名 可見性修飾符 constructor(var param1 : 數(shù)據(jù)類型 = 默認(rèn)值,...)
數(shù)據(jù)類的主要特點(diǎn)是:
- 沒有結(jié)構(gòu)體的時(shí)候誉结,大括號(hào)
{}
可省略 - 主構(gòu)造函數(shù)至少存在一個(gè)參數(shù),用
var
或val
修飾 - 不能是抽象的券躁,開放的或者內(nèi)部的
- 可以實(shí)現(xiàn)接口或繼承其他類
//kotlin
data class User(val name : String, val pwd : String)
上述代碼等價(jià)于
//Java
public class User {
private String name;
private String pwd;
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
// ......
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
copy
如需復(fù)制一個(gè)數(shù)據(jù)對(duì)象并改變它的一些屬性惩坑,其余保持不變,可用其copy
函數(shù)也拜。
val mUser = User("kotlin","123456")
println(mUser) // User(name=kotlin, pwd=123456)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser) // User(name=new Kotlin, pwd=123456)
解構(gòu)聲明
解構(gòu)聲明即把一個(gè)對(duì)象 解構(gòu)
成很多變量以舒。
Kotlin
中定義一個(gè)數(shù)據(jù)類,則系統(tǒng)會(huì)默認(rèn)自動(dòng)根據(jù)參數(shù)的個(gè)數(shù)生成component1() ... componentN()
函數(shù)慢哈。其...,componentN()
函數(shù)就是用于解構(gòu)聲明
val mUser = User("kotlin","123456")
val (name,pwd) = mUser
println("name = $name\tpwd = $pwd")
備注:
關(guān)于data數(shù)據(jù)類序列化問題稀轨,參考資料:https://cloud.tencent.com/developer/article/1587304
七、密封類
密封類用來表示受限的類繼承結(jié)構(gòu):當(dāng)一個(gè)值為有限幾種的類型岸军、而不能有任何其他類型時(shí)奋刽。某種意義上講,它相當(dāng)于是枚舉類的擴(kuò)展
密封類的子類必須是在密封類的內(nèi)部或必須存在于密封類的同一文件
kotlin1.5
放寬了限制艰赞,只需要保證 Sealed Classes
和它的子類佣谐,在同一個(gè)包名和 module
下面即可。
聲明
定義密封類的關(guān)鍵字:sealed
方妖,聲明格式為:
sealed class SealedExpr()
sealed class Expr {
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
}
object NotANumber : Expr() // 子類可以定在密封類外部
密封類不能被實(shí)例化狭魂,類似抽象類。通過反編譯可知党觅,內(nèi)部的子類編譯為靜態(tài)內(nèi)部類
使用
密封類主要的作用在于限制繼承雌澄,起到劃分子類的作用,也可保證安全杯瞻。一般與when
合用镐牺。
sealed class SealedClass{
class SonClass1: SealedClass(){}
class SonClass2: SealedClass(){}
}
fun check(fatherClass: FatherClass): String =
when(fatherClass){
is SonClass1 -> "1"
is SonClass2 -> "2"
else ->
throw IllegalArgumentException("Unknown Class")
}
fun main(args: Array<String>){
val son1 = SonClass1();
val result = check(son1)
println(result)
}
前述代碼,每在SealedClass
密封類添加一個(gè)子類魁莉,when
結(jié)構(gòu)就會(huì)檢查到增加了子類睬涧,必須給when
結(jié)構(gòu)添加分支募胃,否則編譯報(bào)錯(cuò)
密封類一般也稱為枚舉類的擴(kuò)展,sealed
不能修飾interface
畦浓、abstract class
和抽象類類似痹束,Sealed Class
可用于表示層級(jí)關(guān)系。它的子類可以是任意的類:data class
讶请、普通Kotlin
對(duì)象祷嘶、普通的類,甚至也可以是另一個(gè)密封類
sealed class ResponseResult<out T> {
data class Success<out T>(val data:T):ResponseResult<T>()
data class Failure(val throwable: Throwable?):ResponseResult<Nothing>()
override fun toString(): String {
return when(this) {
is Success<*> -> "Success=[data=$data]"
is Failure -> "Failure[throwable=$throwable]"
}
}
}
inline fun <reified T> ResponseResult<T>.doSuccess(success:(T) -> Unit) {
if (this is ResponseResult.Success) {
success(data)
}
}
inline fun <reified T> ResponseResult<T>.doFailure(failure:(Throwable?) -> Unit) {
if (this is ResponseResult.Failure) {
failure(throwable)
}
}
fun main() {
val result = if(true) {
ResponseResult.Success(xxx)
} else {
ResponseResult.Failure(xxx)
}
result.doSuccess{
}
result.doError{
}
}
八夺溢、其它類
8.1 抽象類
定義
用關(guān)鍵字abstract
聲明抽象類
抽象類和普通類的區(qū)別在于:抽象類除了可以有其自己的屬性抹蚀、構(gòu)造函數(shù)、方法等組成部分企垦,還包含了抽象函數(shù)以及抽象屬性环壤。
abstract class Lanauage{
val TAG = this.javaClass.simpleName // 自身的屬性
fun test() : Unit{
}
abstract var name : String // 抽象屬性
abstract fun init() // 抽象方法
}
class TestAbstarctA : Lanauage(){
override var name: String
get() = "Kotlin"
set(value) {}
override fun init() {
println("我是$name")
}
}
val mTestAbstarctA = TestAbstarctA()
println(mTestAbstarctA.name) //Kotlin
mTestAbstarctA.init() //我是Kotlin
抽象類的主要特點(diǎn)為:
- 不能被直接實(shí)例化
- 抽象成員只有定義,沒有實(shí)現(xiàn)
- 只能繼承一個(gè)抽象類
- 可以覆寫抽象類父類的函數(shù)
8.2 嵌套類
定義
類可以嵌套在其他類中钞诡,嵌套類默認(rèn)是靜態(tài)的郑现。
class Other{ // 外部類
val numOuther = 1
class Nested { // 嵌套類
fun init(){
println("執(zhí)行了init方法")
}
}
}
Other.Nested().init() // 調(diào)用格式為:外部類.嵌套類().嵌套類方法/屬性
嵌套類的主要特點(diǎn)為:
- 調(diào)用嵌套類屬性或方法格式:
外部類.嵌套類().嵌套類方法/屬性
- 無法訪問外部類的屬性和成員
8.3 內(nèi)部類
聲明一個(gè)內(nèi)部類使用inner
關(guān)鍵字。
內(nèi)部類會(huì)帶有一個(gè)對(duì)外部類的對(duì)象的引用荧降,能夠訪問外部類的成員接箫,但內(nèi)部類不能在接口或非內(nèi)部嵌套類中聲明。
class Other{ // 外部類
val numOther = 1
inner class InnerClass{ // 嵌套內(nèi)部類
val name = "InnerClass"
fun init(){
println("我是內(nèi)部類")
}
}
}
Other().InnerClass().init() // 調(diào)用格式為:外部類().內(nèi)部類().內(nèi)部類方法/屬性
8.4 匿名內(nèi)部類
通過object
來創(chuàng)建匿名內(nèi)部類
interface OnClickListener{
fun onItemClick(str : String)
}
class Other{
private lateinit var listener : OnClickListener
fun setOnClickListener(listener: OnClickListener){
this.listener = listener
}
fun testListener(){
listener.onItemClick("我是匿名內(nèi)部類的測(cè)試方法")
}
}
// 測(cè)試匿名內(nèi)部類
val other = Other()
other.setOnClickListener(object : OnClickListener{
override fun onItemClick(str: String) {
// todo
println(str)
}
})
other.testListener()
8.5 局部類
局部類即定義在方法中的類
val numOther = 1
fun partMethod(){
var name : String = "partMethod"
class Part{
var numPart : Int = 2
fun test(){
name = "test"
numPart = 5
println("我是局部類中的方法")
}
}
val part = Part()
println("name = $name \t numPart = " + part.numPart + "\t numOther = numOther")
part.test()
println("name = $name \t numPart = " + part.numPart + "\t numOther = numOther")
}
}
Other().partMethod()
/***********輸出結(jié)果***********/
name = partMethod numPart = 2 numOther = 1
我是局部類中的方法
name = test numPart = 5 numOther = 1
局部類的主要特點(diǎn)是:
- 局部類只能在定義該局部類的方法中使用朵诫。
- 定義在實(shí)例方法中的局部類可以訪問外部類的所有變量和方法辛友。但不能修改
- 局部類中的可以定義屬性、方法剪返,并且可以修改局部方法中的變量
九废累、類型別名
類型別名是為現(xiàn)有類型提供替代名稱,例如用較短名稱替代原有類型脱盲,解決代碼過于冗余與臃腫的問題邑滨,關(guān)鍵字為typelias
類名
// 類型別名,切記聲明在頂部
typealias First = TypeAliasDemoTestFirst
class TypeAliasDemoTestFirst{
fun show(){
println("name : ${this.javaClass.simpleName}")
}
}
val first = First()
first.show()
typealias NestA = DemoClassTestNest.A
class DemoClassTestNest{
class A{
fun show(){
println("name : ${this.javaClass.simpleName}")
}
}
}
val nestA = NestA()
nestA.show()
函數(shù)參數(shù)
typealias Predicate<T> = (T) -> Boolean
fun foo(p: Predicate<Int>) = p(42)
fun main() {
val f: (Int) -> Boolean = { it > 0 }
println(foo(f)) // 輸出 "true"
val p: Predicate<Int> = { it > 0 }
println(listOf(1, -2).filter(p)) // 輸出 "[1]"
}
屬性名沖突解決
在開發(fā)中钱反,處理json
字符串轉(zhuǎn)換成實(shí)體類的時(shí)候掖看,會(huì)出現(xiàn)屬性名和關(guān)鍵字沖突的問題
對(duì)于Java,使用@SerializedName
注解解決
public class Test{
private int id;
private String name;
@SerializedName("swicth")
private int s;
}
對(duì)于Kotlin面哥,只需將屬性名包括在符號(hào)``中即可
data class TestBean(val id : Int,
val name : String,
val `package` : String)
十哎壳、解構(gòu)聲明
解構(gòu)聲明:將一個(gè)對(duì)象解構(gòu)為多個(gè)變量的操作。data
數(shù)據(jù)類默認(rèn)支持解構(gòu)聲明尚卫,會(huì)生成component()
方法归榕。
data class Book(var name: String, var price: Float)
val (name, price) = Book("Kotlin入門", 66.6f) //解構(gòu)聲明
println(name) //Kotlin入門
println(price) //66.6
實(shí)現(xiàn)原理
數(shù)據(jù)類之所以可以使用解構(gòu)聲明,因?yàn)榫幾g器默認(rèn)為它聲明了類似函數(shù)
operator fun component1(): String {
return name
}
operator fun component2(): Float {
return price
}
其中的 component1()
和 component2()
函數(shù)是在 Kotlin 中廣泛使用的 約定原則 的另一個(gè)例子焕毫,為 name蹲坷、price變量賦值相當(dāng)于分別調(diào)用了 Book 對(duì)象的component1()
循签、component2()
函數(shù)
自定義解構(gòu)聲明
對(duì)于普通類疙咸,可以手動(dòng)聲明類似operator fun component1()
的方法县匠,來實(shí)現(xiàn)解構(gòu)聲明功能
class Book(var name: String, var price: Float) {
operator fun component1(): String {
return name
}
operator fun component2(): Float {
return price
}
}
fun main(args: Array<String>) {
val (name, price) = Book("Kotlin入門", 66.6f)
println(name) //Kotlin入門
println(price) //66.6
}
數(shù)組撒轮、集合解構(gòu)聲明
Kotlin 中數(shù)組,list
题山、map
系列集合默認(rèn)也支持解構(gòu)聲明的功能
fun main(args: Array<String>) {
val array = arrayOf(1, 2, 3)
val (a1, a2, a3) = array
val list = listOf(1, 2, 3)
val (b1, b2, b3) = list
val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
for ((key, value) in map) {
println("$key-$value")
}
}
解構(gòu)聲明忽略
如果在解構(gòu)聲明中不需要某個(gè)變量兰粉,那么可以用下劃線_
取代其名稱玖姑,這樣也就不會(huì)調(diào)用相應(yīng)的componentN()
操作符函數(shù):
val (_, price) = Book("Kotlin入門", 66.6f)
十一慨菱、對(duì)象表達(dá)式與對(duì)象聲明
11.1 對(duì)象表達(dá)式
對(duì)象表達(dá)式是在使用他們的地方立即執(zhí)行
創(chuàng)建一個(gè)繼承某個(gè)(或某些)類型的匿名類的對(duì)象幻林,我們一般會(huì)這么寫:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*……*/ }
override fun mouseEntered(e: MouseEvent) { /*……*/ }
})
kotlin和Java在創(chuàng)建匿名類的方式相似老速,只不過將
new
換成了object
定義一個(gè)接口與抽象類畏腕,kotlin可以一個(gè)匿名對(duì)象搞定郊尝,而java需要定義兩個(gè)匿名對(duì)象類
abstract class Person {
abstract fun play()
}
interface Move {
fun walk()
}
//對(duì)象表達(dá)式
val a: Any = object : Person(), Move {
override fun walk() {
println("walk")
}
override fun play() {
println("play")
}
}
fun main(args: Array<String>) {
//調(diào)用
if (a is Person) {
a.play()
}
if (a is Move) {
a.walk()
}
}
越過類定義
通過對(duì)象表達(dá)式流昏,可以越過類的定義况凉,直接得到一個(gè)對(duì)象
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
返回值類型
匿名對(duì)象可以用作只在本地和私有作用域中聲明的類型各拷。
- 作為公有函數(shù)的返回類型或公有屬性類型烤黍,實(shí)際類型為匿名對(duì)象的超類型
- 如果是私有傻盟,則為匿名對(duì)象類型
class C {
// 私有函數(shù)娘赴,所以其返回類型是匿名對(duì)象類型
private fun foo() = object {
val x: String = "x"
}
// 公有函數(shù)诽表,所以其返回類型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // foo私有函數(shù)隅肥,沒問題腥放,
val x2 = publicFoo().x // 錯(cuò)誤:公用函數(shù)返回超類型秃症,未能解析的引用“x”
}
}
對(duì)象表達(dá)式中的代碼可以訪問來自包含它的作用域的變量。
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ……
}
11.2 對(duì)象聲明
Kotlin 使用 object
關(guān)鍵字來聲明一個(gè)對(duì)象宗雇,其中變量與函數(shù)都是靜態(tài)的赔蒲。
可以通過對(duì)象聲明來獲得一個(gè)單例舞虱,且是線程安全的餓漢式單例母市。
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
對(duì)象聲明在另一個(gè)類的內(nèi)部時(shí)患久,只能通過類名來訪問蒋失,且該對(duì)象不能直接訪問到外部類的方法和變量
class Site {
var name = "Hello World"
object DeskTop{
var url = "www.runoob.com"
fun showName(){
print{"desk legs $name"} // 錯(cuò)誤,不能訪問到外部類的方法和變量
}
}
}
fun main(args: Array<String>) {
var site = Site()
site.DeskTop.url // 錯(cuò)誤荆萤,不能通過外部類的實(shí)例訪問到該對(duì)象
Site.DeskTop.url // 正確
}
伴生對(duì)象
類內(nèi)部的對(duì)象聲明可以用 companion
關(guān)鍵字標(biāo)記链韭,可以直接通過外部類訪問到對(duì)象的內(nèi)部元素敞峭,類似于Java中的static
靜態(tài)功能
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create() // 訪問到對(duì)象的內(nèi)部元素
可以省略伴生對(duì)象的名稱儡陨,在這種情況下將使用名稱 Companion
class MyClass {
companion object { }
}
val x = MyClass.Companion
一個(gè)類里只能聲明一個(gè)內(nèi)部關(guān)聯(lián)對(duì)象骗村,即
companion
只能使用一次
Java中的靜態(tài)變量和方法胚股,在kotlin中都放在companion object
中裙秋。因此Java中的靜態(tài)初始化在kotlin中也放在companion object
中摘刑,由init
和一對(duì)大括號(hào)表示枷恕。
class Sample {
companion object {
init {
...
}
}
}
伴生對(duì)象是真實(shí)對(duì)象的實(shí)例成員,而且還可以實(shí)現(xiàn)接口
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
fun test() {
println("test")
}
}
fun main(args: Array<String>) {
val f: Factory<MyClass> = MyClass
f.create().test()
}
語(yǔ)義差異
- 對(duì)象表達(dá)式是在使用他們的地方立即執(zhí)行(及初始化)的未玻;
- 對(duì)象聲明是在第一次被訪問到時(shí)延遲初始化的扳剿;
- 伴生對(duì)象的初始化是在相應(yīng)的類被加載(解析)時(shí)昼激,與 Java 靜態(tài)初始化器的語(yǔ)義相匹配