簡述
Kotlin同java一樣也是一門面向對象的語言,作為比java更高級的語言,當然有它獨有的特點:
*構造函數有主次之分
*不需要new 關鍵字
*所有類都有一個共同的超類 Any
*默認情況下所有類都是final 不可繼承,想要被繼承就要用到open、abstract關鍵字
*類的方法和屬性同樣被final關鍵字限制不能被繼承和重寫
*被繼承的屬性或方法必須加override關鍵字,并且父類里要聲明為 open,但接口就不需要聲明
*Kotlin 中的類如果是空類,沒有任何語句,則可以省略大括號
*類不存在static方法,如果需要就要用到伴生對象
*接口與 Java 8 類似,既包含抽象方法的聲明,也包含實現。
*Kotlin 中有四個可見性修飾符:private、protected、internal和public
*Kotlin 在不繼承類的情況下支持擴展函數和擴展屬性
*不光有枚舉,還有數據類,密封類
*支持類委托,事實證明類委托是繼承以外更好的實現方式
看到這是不是覺得有很多東西要學,一份耕耘一份收獲,繼續往下,go!下面將對具體的特點展開講解。
類構造
1. 主構造函數寫在類頭中,有且只有一個
例1:這個類定義一個 String 類型的成員變量 name,然后定義了帶有一個 String 類型參數的構造函數,這個構造函數把參數列表中的 name 賦給了成員變量 name。
//用constructor關鍵字定義帶有一個 String 類型參數的主構造函數。
class Person constructor( name :String) {
? ? val name :String
? ? init{ ? ? ? ? ? ? ? ? ? ? ? ? ? ?//使用 init 關鍵字定義主構造函數的行為
? ? ? ? this.name=name
? ? }
}
這么寫也不比java 省多少事撒,作為現代語言肯定不能就這么算了,當然你可以簡化成這樣
class Person(val name :String)
這樣就夠簡潔了吧,但這么寫就有了默認的約束:1.如果主構造函數沒有任何修飾符,則可以去掉 constructor 關鍵字 2.如果主構造函數中定義的參數使用 val (只讀)或者 var(讀寫) 修飾,則會創建與這個參數同名的成員變量,并使用傳入的參數值初始化這個成員變量
例2:如果一個非抽象類沒有聲明任何(主或次)構造函數,它會有一個生成的不帶參數的主構造函數。構造函數的可見性是 public。如果你不希望你的類有一個公有構造函數,你需要聲明一個帶有非默認可見性的空的主構造函數:
class DontCreateMe private constructor() {
}
例3:如果構造函數有注解或可見性修飾符,這個constructor關鍵字是必需的,并且這些修飾符在它前面:
class Customer public @Inject constructor( name : String) { …… }
例4: 主構造的參數可以在初始化塊中使用。它們也可以在類體內聲明的屬性初始化器中使用:
class Customer(name : String) {
? ? val customerKey= name.toUpperCase()
}
2. 次構造函數寫在類語句中,可以有多個,也可以沒有。
例1:聲明前綴有constructor的次構造函數
class Person{? ?
? ? ?constructor(parent : Person) {?
? ? ? ? ? ?parent.children.add(this) ? ?
? ? ?}
例2:如果類有一個主構造函數,每個次構造函數需要委托給主構造函數, 可以直接委托或者通過別的次構造函數間接委托。委托到同一個類的另一個構造函數用this關鍵字即可
class Person(val name : String) {? ?
? ? ? ? var old :Int=0
? ? ? ? constructor(name : String , parent : Person , old :Int) : this(name) { ?//加粗的就是委托代碼
? ? ? ? ? ? ? parent.children.add(this)?
? ? ? ? ? ? ? this.old=old
? ? ? ? }
}
例3:次構造函數不能在參數列表中聲明并初始化成員變量,還是上面的代碼,“old : Int”前面為什么沒有 val ,因為次構造函數會改動 old 的值,所以 old 必須聲明為 var
屬性
與java相比,java中包下面有類,接口等。而Kotlin中在包下面是可以直接有屬性的,并且所有非抽象屬性都強制要求初始化,沒有初始化的屬性無法通過編譯(除標記為 lateinit var 的屬性外),下面將對屬性的聲明,getter/setter ,類外屬性、以及延遲屬性進行Demo 代碼詳解:
聲明
用var聲明可變,或者用val 聲明不可變,在Kotlin中完整的聲明格式如下:
[修飾符] val|var 屬性名[: 屬性類型][= 初始化賦值]
? ? ? [get() = getter 語句]
? ? ? [set() = setter 語句]
例1:Person類定義了name屬性,String類型,初始化為空, 這里省略了Getter,Setter。雖然省略了,通常可以通過person.name 調用,看似沒有調用getter,但實際應用中,Kotlin會自動調用,而且是強制的。
class Person{
? ? ?var name: String =""
}
例2:通過上面我們知道,Kotlin會默認生成Getter、Setter,如果要修改Getter,Setter方法的話,如下:
class Person{
? ? ? var name: String =""
? ? ? ? ? ?get() = field.toUpperCase() ? //這里field 可以看成是name字段的中介,它的變化會反應在當前的name字段上,姑且你把它看成是name字段的別名吧。
? ? ? ? ? ?set(value){? ? ? ? ? ?
? ? ? ? ? ? ? ? ? field ="Name: $value"
? ? ? ? ? ?}
}
如果需要在getter和setter中訪問這個屬性自身的值,它需要創建一個backing field??梢允褂胒ield這個預留字段來訪問,它會被編譯器找到正在使用的并自動創建。
類外屬性
在 Kotlin 類外定義的屬性有兩種,一是直接寫在類外并初始化的包級屬性,二是使用const val定義的編譯期常量。
例1:包級屬性下編譯這個文件,實際上會形成兩個類,一個是我們定義的 Person 類,另一個是“文件類”PersonKt。
// oop.Person.kt
class Person(val name :String)
val maxID=Int.MAX_VALUE
maxID 被編譯為這個類私有的靜態字段,并擁有一個默認的 getter 方法。在java中是直接通過類名.maxID,而在Kotlin中是這么用的:
// Kotlin 調用包級屬性
import oop.maxID// 因為是包級變量,所以使用“包名.屬性名”的方式導入
val a=maxID// 直接調用,不需要標明變量所在的文件
例2:編譯期常量與包級屬性類似,看代碼:
// oop.Person.kt
const val maxID=Int.MAX_VALUE // 將 maxID 聲明為編譯期常量
與java相比,Kotlin中 常量有幾個限制:
1.只能定義在類外或對象(Object)內;
2.只能使用 String 或原生類型(Int、Double 等)初始化;
3.不能自定義 getter(直接調用,不需要 getter)
還有一點注意:Kotlin 中,使用在注解參數中的屬性,只能是編譯期常量(其他形式的屬性都不能使用在注解的參數里):
const val DEPRECATED_MESSAGE = "This is deprecated."
@Deprecated(DEPRECATED_MESSAGE) fun foo() {……}
延遲屬性
Kotlin中為什么要有延遲屬性呢?那是因為Kotlin中類內屬性,默認情況下都是要強制初始化的,但在實際應用中,確實有一些屬性不需要初始化,為了避免編譯器錯誤,這時候就需要用lateinit修飾符標記該屬性。
例如:這里就是對hello屬性進行延遲初始化。
class Person(val name:String){
? ? lateinit var hello :String
? ? fun initHello(){
? ? ? ? ? hello="Hello, my name is $name"
? ? }
}
在使用過程中需要注意:一定要先調用initHello()方法之后在去使用hello屬性,否則就會報錯。
繼承
與java相比,Kotlin的超類是Any。在沒有父類的情況下默認繼承,下面主要列舉顯示繼承:
例1:父類Base有一主構造,在被Derived類繼承過程中,必須同樣覆蓋主構造方法,否則會報錯,這點跟java一樣。注意:被繼承的類必須聲明open關鍵字
open class Base(p :Int)
class Derived(p :Int) : Base(p)
例2:? 如果沒有主構造函數,那么每個次構造函數必須使用super關鍵字初始化其基類型,或委托給另一個構造函數做到這一點。
class MyView :View {
? ? ? ? constructor(ctx : Context) : super(ctx)? ?
? ? ? ? constructor(ctx : Context, attrs : AttributeSet) : super(ctx, attrs)
}
例3:java中經常要覆蓋父類的方法去實現一些業務邏輯,在Kotlin中除非是抽象類的方法是默認可以覆蓋的,正常的使用open修飾的父類,它的方法也要聲明為open后才能被子類覆蓋。
open class Person(val name :String){
? ? open var age=0 ?//要被繼承就必須添加open關鍵字
? ? open fun say()= "My name is $name, $age years old."
}class Student(name : String ) : Person(name){
? ? override var age=0? ? //繼承的屬性必須加override ?下面方法同樣適用
? ? override fun say()="I'm a student named $name, $age years old."
}
在覆蓋屬性的時候需要注意:不允許用 val 屬性覆蓋 var 屬性,這點仔細理解下確實是這個道理,就像java里的 父類protected修飾的方法,子類中只能是protected或者public,肯定不能private。
例4:如果繼承的父類與繼承的接口中有同樣的函數怎么辦,這個時候就要子類必須表明自己的實現,是都覆蓋,還是覆蓋其中之一。下面代碼演示:
//接口
interface B {
? ? ? ?fun f(){
? ? ? ? ? ?print("B")
? ? ? ?} ?// 接口成員默認就是“open”的
? ? ? ?fun b(){
? ? ? ? ? ? print("b")
? ? ? ?}
}open class A{
? ? open fun f(){ ?//open修飾可以被子類繼承
? ? ? ? print("A")
? ? }
? ? fun a(){ ?//不可被繼承
? ? ? ? print("a")
? ? ?}
}class C : A() , B { // 編譯器要求覆蓋 f(): 下面的實現調用其中之一也是可以的
? ? ? override fun f(){
? ? ? ? ? ? super.f() // 調用 A.f()
? ? ? ? ? ? super.f()// 調用 B.f()
? ? ? }
}
總結
到此為止,Kotlin中的類,構造,類屬性,繼承就學習完了,為了避免博客太長,下面另開博客繼續講解抽象類與接口。進入下一章節