內容:
- 類,介面和抽象類
- 繼承修飾符和可見性修飾符
- 巢狀類、內部類和密封類
- 主構造方法和從構造方法,複雜的單例實現
- 介面定義屬性
一類和介面
在第一篇基礎文章中已經介紹了類的有引數構造方法的宣告語法,但對於開發是遠遠不夠的,還有很多細節需要我們去注意和學習。
1.1 Kotlin中的類和java中的類區別
Kotlin中的類和java中的類大致一樣,但是還是有些許區別,如第一篇講解的。
- Kotlin預設是final和public的,而java卻不是。
- Kotlin中巢狀類並不是內部類,並沒有包含對其外部類的隱式引用。
3. 之前文章還沒有介紹構造方法的方法體。
4.data類,提供了資料類標準的可用的方法。
5.更簡便的單例模式,伴生物件,以及匿名類,繼承和實現介面等語法
1.2 介面的定義
interface Clickable {
fun click();
}
複製程式碼
interface Clickable2{
fun click2();
}複製程式碼
class Son1(name: String, address: String = "") :Father(name,address),Clickable,Clickable2 {
override fun click2() {}
override fun click() {}
}複製程式碼
override 修飾符必須要求強制寫出。與java不同的是,java中的介面中定義的必須全部是抽象方法,但是在kotlin中介面中可以實體方法,有點和java中的抽象類相似,像這樣:
interface Clickable {
fun click()
fun showoff() {//介面中定義的預設實現方法,預設是open,可以被子類重寫
Log.e("rrrrrrrrrr", "rrrrrrrr")
}
}複製程式碼
假設我們再去定義一個介面,也有一個同名方法的預設實現,而子類不去實現,看看會出現什麼情況
interface Clickable2{
fun click2()
fun showoff() {
Log.e("rrrrrrrrrr", "Clickable2")
}
}複製程式碼
發現IDE去要求我們必須實現預設方法,否則會報錯
class aaaa : Clickable ,Clickable2{
override fun click2() {
}
override fun showoff() {
}
override fun click() {
}
}複製程式碼
假如我們就是鑽牛角尖,就想在這種情況下呼叫其中一個父類方法,或者兩個父類方法都呼叫,怎麼辦?
Kotlin工程師給我們提供了顯示的呼叫方式!!!
語法:使用尖括號加上父型別名字的“Super”表明了你想要呼叫哪一個父類的方法,像這樣:
override fun showoff() {
super<Clickable>.showoff()
super<Clickable2>.showoff()
}複製程式碼
事實上,java中介面並不能宣告實體方法,那麼Kotlin可以這樣寫,那麼它的編譯原理是什麼呢?它會把每個帶預設方法的介面編譯成一個普通介面和一個將方法體作為靜態函式的類的結合體。
1.3類之間繼承和重寫
open class DD {
open var name: String = ""
open fun setValue(neme: String) {
}
}複製程式碼
class EE: DD() {
override var name = "eee"
override fun setValue(neme: String) {
this.name = neme
}
}複製程式碼
open class EE : DD() {
override var name = "eee"
final override fun setValue(neme: String) {
this.name = neme
}
}複製程式碼
延伸: 預設是final的好處,可以更好的支援型別的自動轉換。
1.4抽象類的定義
//抽象類預設是open的
abstract class Animated {
open fun fun1() {
//預設是final,可以用open去修飾,開啟重寫
}
//抽象方法,預設是open狀態
abstract fun fun2()
}複製程式碼
到了這裡,我們可以看出,介面和抽象類都可以有實體方法,唯一的區別就是抽象類的實體方法預設是final,而介面中實體方法預設是open。
二 繼承修飾符和可見性修飾符
2.1繼承修飾符
通過上邊類,介面和抽象類的使用可以總結如下:
open: 重寫類,方法,屬性開關,預設介面都是open修飾,抽象類和抽象方法是open修飾。
final:不能被重寫類,方法,屬性開關,預設類中全部是final,抽象類中的實體全是final。
abstract :只有抽象類和抽象方法可以使用,預設被它修飾的同時是被open修飾。
override :重寫父類的識別符號,如果是重寫,必須顯示的顯示出來。
2.2 可見性修飾符
我們都知道,kotlin中多了頂層宣告的函式或方法,這些修飾符大部分同樣適用頂層宣告中。
延伸:可見修飾符和java有不同的地方,那麼在編譯成位元組碼的時候,kotlin會怎麼轉化呢?
public還是public,private被轉換成包私有,protected和java的一樣,internal會變成public
三 巢狀類和內部類
3.1 巢狀類
和java一樣,Kotlin也支援在一個類中宣告一個類。區別是kotlin的內部類預設情況不能使用外部類物件。
class Student() {
var name :String?=null
class Score() {
}
}複製程式碼
預設情況下Score類是不能使用name屬性的,因為他們是巢狀類,內部預設情況下是沒有持有外部類引用的。
3.2 內部類
如我們因為某種需要,就是想寫出類似java中的內部類。這裡介紹一個關鍵字inner,而且引用需要使用this@外部類名字去呼叫。如:
class Outer {
var arr = ArrayList<String>()
inner class Innter {
val size: Int
get() = this@Outer.arr.size
}
}
複製程式碼
3.3密封類
為一個類新增一個修飾符sealed,那麼所有的直接子類必須巢狀在父類中。
sealed class A{
open class B : A(){
}
}複製程式碼
但是可以在外部繼承B類。
四 構造方法
總算是說道構造方法了,如果看文章的人,應該內心早就罵娘了,這麼久了,還不說構造,你小子到底是不是專業的?講!這就講!看官,讓你久等了。。。。。
在java中構造方法可以過載,而我們已經學過了Kotlin的過載,是不是傳遞一個預設值就行了?這樣只是簡單的過載。構造方法的特殊性,註定它的與眾不同,生下來就含著金鑰匙。Kotlin區分主構造方法和從構造方法。
從構造方法:在類體內部宣告。
4.1主構造方法和方法體
在第一篇已經簡單接觸了主構造方法:
class Person(val name: String)
1.宣告構成方法的引數;
2.可以把引數宣告成類中的屬性。
我們還可以這樣寫:
class Person constructor(name: String) {
val name: String
init {
this.name = name
}
}複製程式碼
init關鍵字用來引入一個初始化語句塊。
class Person(name: String) {
val name: String = name
}複製程式碼
注意:如果沒有給這個類宣告任何構造方法或者宣告瞭有引數的構造方法都有預設值,編譯器都會額外的生成一個空引數的構造方法。如:
class Person(name: String="") 複製程式碼
class Person複製程式碼
4.2複雜的單例實現
Kotlin中如果我們想要把一個類變成單例模式你可以這樣寫:
把一個類的構造方法私有化,
class Person private constructor()複製程式碼
而結果卻發現沒法寫靜態方法,因為我們還沒有學。如果用頂層方法嘗試去寫,還是建立不出來私有構造方法。這就是第二篇為什麼說頂層方法不適合單例模式。
錯誤程式碼:
class Person private constructor() {
}
public fun getPerson () = Person()//這裡會報錯,因為構造方法是私有的複製程式碼
這裡如果提供一個靜態方法的宣告方法多好,Kotlin其實是支援的,需要使用這樣的結構:
//這裡邊的都是靜態方法和靜態屬性
companion object {
}複製程式碼
所以我們可以把方法寫到這裡邊去建立一個單例模式,之後還會講解更簡單的單例模式寫法。
class Person private constructor() {
//這裡邊的都是靜態方法和靜態屬性
companion object {
private var mInstance: Person? = null//宣告一個靜態常量
//生命一個靜態屬性
val instance: Person
//從新寫他的get方法
get() {
if (mInstance == null) {
synchronized(Person::class.java) {
mInstance = Person()
}
}
return mInstance!!
}
}
}複製程式碼
這樣一個複雜的單例模式就建立成功。下一篇我將會講怎麼簡單建立一個單例模式。
4.3次構造方法
假如我們就是想要申請多個構造方法,而且不同的構造方法的初始化邏輯還都不一樣,我們可以這樣去宣告:
class MyView{
constructor(context: Context){
}
constructor(context: Context,attributes: AttributeSet){
}
}複製程式碼
注意,這個類沒有宣告一個主構造方法。這裡就沒有了無引數的構造方法。
如我們想要自定義一個View,並擴充套件父類方法,你可以這樣讓構造方法分別繼承父類構造方法,像這樣:繼承super
class MyView : View {
constructor(context: Context) : super(context) {
//do
}
constructor(context: Context, attributes: AttributeSet) : super(context, attributes) {
//do
}
}複製程式碼
亦或是呼叫自身類的構造方法:繼承this()
class MyView : View {
constructor(context: Context) : this(context,null) {
//do
}
constructor(context: Context, attributes: AttributeSet?) : super(context, attributes) {
//do
}
}複製程式碼
就是任性想要主構造和次構造都寫出來,就是不用預設值:
class Person constructor() {
lateinit var name :String
constructor( name :String):this(){
this.name = name
}
}複製程式碼
很顯然達到你的目的了,但是卻失去原有的簡潔性,也沒有意義。何必呢?
總結,構成方法要麼只有主構造,要麼只有次構造,次構造要麼繼承this,要麼繼承super,但必須繼承一個。這樣使用更加合理簡單。
五 屬性
5.1介面定義屬性
我們在第一篇已經介紹了可變屬性,不可變屬性,重寫get和set方法。這裡我們更深入瞭解一下屬性。
在java中介面中不能定義屬性,只能定義抽象方法。在抽象類中,java可以定義屬性,子類也可以使用屬性。而在Kotlin中,介面可以定義屬性,抽象類和java一樣可以定義屬性,使用屬性。
比如我們在介面P中宣告瞭一個屬性:
interface P {
val name: String
}複製程式碼
那麼這個屬性並沒有對應的get或者set方法的,這裡需要我們子類自己去實現它的get或者set方法,你可以這樣寫:
class Person(override var name: String) :P複製程式碼
或者自己去定義get和set方法。
突然想到我們介面可以去實現方法,那麼我們能不能在介面中去直接把get、set方法定義好呢?當然是可以的!
我們可以直接去在介面中提供get方法,程式碼修改成這樣:
interface P {
val name: String
get()= "張三"
}複製程式碼
且子類可以重寫。
class Person() :P{
// override val name:String
// get() = "李四"
}複製程式碼
注意:如果重寫,列印就是李四,如果不重寫,列印就是張三。
小結:
類之間的整合需要使用open關鍵字,重寫方法必須使用override關鍵字,不想被重寫用final關鍵字。
介面可以寫實體方法,介面可以宣告屬性,但是沒有get和set方法,預設全部是open修飾,抽象類實體方法預設是final狀態。
巢狀類不是內部類,內部類需要使用innter關鍵字,密封類是所有的直接子類都要是巢狀類。
主構造方法和從構造方法一般不會同時出現,過載多個構造方法,繼承super或this。
可見性修飾符public 全部可見,private 類或者檔案內可見,propected子類可見,還有一個模組可見。