Kotlin核心編程 第三章面向對象

3.1類和構造方法

class Bird{
    val weight:Double = 500.0
    val color:String = "blue"
    val age:Int = 1
    fun fly(){}//全局可見
    
}
///反編譯後的java代碼
public final class Bird{
    private final double weight = 500.0
    @NotNull
    private final String color = "blue"
    private final Int age = 1
    
    public final double getWeight(){
        return this.weight
    }
    @NotNull
    public final String getColor(){
        return this.color
    }
    public final Int getAge(){
        return this.age
    }
    public final void fly(){}
}

不同點:
1不可變的屬性成員。利用java的final修飾符實現不可變的屬性成員。
2屬性默認值。因爲java都有默認值(int爲0,引用類型爲null),所以在聲明屬性的時候我們不需要指定默認值。而在Kotlin中,除非顯示地聲明延遲初始化,否則就要指定屬性的默認值。
3不同的可訪問修飾符。Kotlin類中的成員默認是全局可見的,而java默認是包作用域,因此在java版本中要用public修飾符才能達到相同的效果。

接口:
接口方法的默認實現使得我們在向接口中新增方法的時候,之前繼承過該接口的類則可以不需要實現這個新方法。

public interface Fly{
    public String kind()
    
    default public void fly(){//接口方法的默認實現,java8引入
        System.out.println("i can fly")
    }
}

kotlin的寫法

interface Fly{
    val speed:Int
    fun kind()
    fun fly(){
        println("i can fly")
    }
}

支持抽象屬性(如上面的speed),然而因爲kotlin是支持java6的,那麼他是如何支持這種行爲的呢?轉爲java代碼如下:

public interface Fly{
    int getSpeed();
    void kind();
    void fly();
    public static final class DefaultImpls{
        public static void fly(Fly $this){
            String var1 = "i can fly"
            System.out.println(var1)
        }
    }
}

通過定義一個DefaultImpls靜態內部類,來提供fly方法的默認實現,同時雖然kotlin接口支持屬性聲明,然而他在java源碼中是通過一個get方法來實現的。在接口中屬性並不能像java那樣直接被賦值一個常量。
如下代碼是錯誤的:

interface Fly{
    val height = 1000
//Property initializers are not allowed in interfaces
}

kotlin提供了另一種方式實現這種效果

interface Fly{
    val height 
        get() = 1000
}

接口中的普通屬性,同方法一樣,若沒有指定默認行爲,則在接口的實現類中必須多該屬性進行初始化。

java通過類的構造方法重載實現不同參數組合new對象。不同組合實現其的構造方法非常多;每個構造方法中存在相同賦值操作存在冗餘。
kotlin採用新的構造方法

class Bird(val weight:Double = 0.00,val age:Int = 0,val color:String = "blue")
//可以省略{}
val b1 = Bird(color = "black")
val b2 = Bird(100.0,1,"black")
//val b3 = Bird(100.0,"black")//!!必須按照順序賦值

構造方法中可以沒有var和val,帶上他們之後接等價於在類的內部聲明瞭一個同名的屬性。

class Bird(var name:String,var age:Int ){
    val weight = 1000;
}

等價於

class Bird(name:String,age:Int){
    var name:String? = null
    var age :Int = 0
    val weight = 1000;
    init{
        this.name = name 
        this.age  = age  
    }
}

kotlin引入了一種叫做init語句塊的語法,屬於構造方法的一部分。
kotlin規定所有類中所有非抽象屬性成員都必須在對象創建時被初始化值。
延遲初始化:by lazy 和lateinit

class Bird(){
    val sex:String by lazy{
        "male"
    }
    fun print(){
        println(sex)
    }
}

by lazy特點:
變量必須是應用不可變的,而不能通過var聲明
被首次調用時,纔會執行賦值操作,一旦被賦值之後將不能被更改。(後續返回記錄的結果,還會默認加上同步鎖).

class Bird(){
    val sex:String by lazy(LazyThreadSafetyMode.PUBLICATION){
        "male"
    }
    ...
}

lateinit主要用於var聲明的變量,然而他不能用於基本數據類型,如Int,Long等我們需要用Integer這種包裝類作爲替代。

class Bird(){
    lateinit val sex:String 
    fun print(){
        this.sex =" male"
        println(sex)
    }
}

主從構造方法:
通過constructor方法定義了一個新的構造方法,被稱爲從構造方法。相應的我們熟悉的在類外部定義的構造方法叫做主構造方法。每個類最多存在一個主構造方法和多個從構造方法,如果柱構造方法存在註解或可見性修飾符,也必須像從構造方法一樣加上constructor關鍵字。
每個從構造方法由兩部分組成,對其他構造方法的委託,花括號包裹的代碼塊。
執行順序上,主構造器>init代碼塊>次構造器

/**
 * kotlin 企鵝類
 */
class Penguin {
    var weight:Int=100  //體重
    var age:Int=0
    var name:String?=null

    //空參次構造方法,如果沒有邏輯,可以省略大括弧
    constructor()
    //有參次構造方法,如果沒有邏輯,可以省略括弧,不過有參的空構造方法是沒有意義的
        //有參次構造器
    constructor(name:String,age:Int){
        this.name=name
        this.age=age
    }
}

當主構造器和次構造器同時存在時,次構造器必須直接或者間接調用主構造器

class Penguin constructor(name: String) {
    var weight:Int=100  //體重
    var age:Int=0
    var name:String?=null

    //空參次構造器,調用了下面的次要構造方法
    constructor():this("奔波兒霸",10)

    //有參次構造器,調用了主構造方法
    constructor(name:String,age:Int):this(name){
        this.name=name
        this.age=age
    }
}

與java相似,當我們不聲明任何構造方法時,Kotlin編譯器會默認給我們增加一個空參構造方法

class Penguin constructor() {
    var weight:Int=100  //體重
    var age:Int=0
    var name:String?=null
}

3.2不同的訪問控制原則

kotlin中沒有個採用java的extends 和implements關鍵詞,而是用:來代替類的繼承和接口的實現
由於kotlin中的類和方法默認是不可被繼承或重寫的,所以必須加上open修飾符

open class Bird(){
    open fun fly(){
        println("i can fly")
    }
}

class Penguin:Bird(){
    override fun fly(){
        println("i can not fly")
    }
}

*子類應該避免重寫父類的非抽象方法--一旦父類方法變化,子類會出錯,李氏替換原則
kotlin中類、方法和屬性默認加了final修飾符


  • 如果你不指定任何可見性修飾符,默認爲 public,這意味着你的聲明將隨處可見;
  • 如果你聲明爲 private,它只會在聲明它的文件內可見;
  • 如果你聲明爲 internal,它會在相同模塊內隨處可見;
  • protected 不適用於頂層聲明。

private 意味着只在這個類內部(包含其所有成員)可見;
protected—— 和 private一樣 + 在子類中可見。
internal —— 能見到類聲明的 本模塊內 的任何客戶端都可見其 internal 成員;
public —— 能見到類聲明的任何客戶端都可見其 public 成員。

3.3解決多繼承的問題

接口實現多繼承
用super關鍵字指定繼承哪個父類接口的方法:

fun main() {
    val b = Bird()
    println(b.kind())//[Flyer] flying animals
}

interface Flyer{
    fun kind() = "[Flyer] flying animals"
}
interface Animals{
    fun kind() = "[Animals] flying animals"
}
class Bird():Flyer,Animals{
    override fun kind () = super<Flyer>.kind()
    //或者主動覆蓋父類接口
    //override fun kind () = "a flying Bird"
}

*kotlin編譯器自動幫我們生成了getter和setter方法。
1用val聲明的屬性將只有getter方法,因爲它不可修改;用var修飾的屬性將同時擁有getter和setter方法。
2用private修飾的屬性編譯器會省略getter和setter方法,因爲類外部已經無法訪問他了,這兩個方法也就沒意義了。

內部類解決多繼承問題的方案
java通過在內部類語法上增加一個static關鍵詞,把它變成嵌套類;kotlin默認是嵌套類,必須加上inner關鍵字纔是一個內部類,也就是說可以把靜態內部類看成嵌套類。
內部類包含着對其外部類實例的引用,在內部類中可以使用外部類中的屬性。

fun main() {
    val b = Bird()
    b.kindFly()
}

open class Flyer{
    fun kind() = println("[Flyer] flying animals")
}
open class Animals{
    fun kind() = println("[Animals] flying animals")
}
class Bird(){
    fun kindFly(){
        FlyerC().kind()
    }
    fun kindAnimals(){
        AnimalsC().kind()
    }
    
    private inner class FlyerC:Flyer()
    private inner class AnimalsC:Animals()
}

使用委託代替多繼承:by 關鍵字

fun main() {
    val flyer = Flyer()
    val animal = Animal()
    
    val b = Bird(flyer,animal)
    b.fly()
    b.eat()
}
class Bird(flyer:Flyer,animal:Animal):CanFly by flyer,CanEat by animal{}

interface CanFly{
    fun fly()
}
interface CanEat{
    fun eat()
}

open class Flyer:CanFly{
    override fun fly(){
        println("i can cly")
    }
}

open class Eater:CanEat{
    override fun eat(){
        println("i can eat")
    }
}


open class Animal :CanEat{
    override fun eat(){
        println("Animal can eat")
    }
}

//
i can cly
Animal can eat

委託使使用時更加直觀,比如需要繼承A,委託對象是BC,具體調用時不是A.B.method()而是A.method()。

真正的數據類 data class

data class Bird(var weight:Double,var age:Int)

一行代碼就擁有了getter/setter,equals,hashcode,構造函數,還有兩個特別的方法copy和componentN

*data class 之前不能用abstract、open、inner、sealed修飾

3.5從static到object

伴生對象companion object
伴生是相對一個類而言,意爲伴隨某個類的對象,因此伴生對象跟java中static修飾效果性質一樣,全局只有一個單例。他需要聲明在類的內部,在類被裝載時會被初始化。

fun main() {
    println("Hello, world!!!")

    val prize = Prize("紅包",10,Prize.TYPE_REDPACK)
    println(Prize.isRedPack(prize))
}
class Prize(val name:String,val count:Int,val type:Int){
    companion object{
        val TYPE_REDPACK = 0
        val TYPE_COUPON = 1
        fun isRedPack(prize :Prize):Boolean{
            return prize.type == TYPE_REDPACK
        }
    }
}

伴生對象也是實現工廠方法模式的一種思路

fun main() {
    val redpackPrize = Prize.newRedPackPrize("紅包",10)
    val couponPrize = Prize.newCouponPrize("代金券",10)
    val commonPrize = Prize.defaultCommonPrize()
}
class Prize(val name:String,val count:Int,val type:Int){
    companion object{
        val TYPE_COMMON = 1
        val TYPE_REDPACK = 2
        val TYPE_COUPON = 3
        
        val defaultCommonPrize = Prize("普通獎品",10,Prize.TYPE_COMMON)
        
        fun newRedPackPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_REDPACK)
        fun newCouponPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_COUPON)
        fun defaultCommonPrize() = defaultCommonPrize
    }
}

天生的單例object
可以直接用它來實現單例

object DatabaseConfig{
    var host :String = "127.0.0.1"
    var post :int= 3306
}

由於object全局聲明的對象只有一個,所以他並不用語法上的初始化,甚至都不需要構造方法。

他還有一個作用就是替代java中的匿名內部類

List <String> list = ArrayList.asList("red","ss","cc")
Collections.sort(list,new Comparator<String>(){
    @Override
    public int compare(String s1,String s2){
        if(s1==null)
        return -1;
        if(s2==null)
        return 1;
        return s1.cpmpareTo(s2)
    }
    
})
//利用object表達式對它進行改善
val comparator = object:Compararor<String>{
    override fun compare(s1:String?,s2:String?):Int{
        if(s1==null)
            return -1
        else if(s2 == null)
            return 1
        return s1.compareTo(s2)
    }
}
Collections.sort(list,comparator)

優點:
object表達式可以賦值給一個變量,重複使用時減少很多代碼;
object可以繼承和實現接口,匿名內部類只能繼承和實現一個接口,而object卻沒有這個限制。

用於代替匿名內部類的object表達式在運行中不像我們在單例中那樣全集只有一個對象,而是每次運行時都會生成一個對象

當匿名內部類使用的類的接口只需要實現一個方法時,使用lambda表達式更適合,當匿名內部類有多個方法實現的時候,使用object表達式更加合適。

val comparator = Compararor<String>{s1,s2->
    if(s1==null)
        return @Comparator-1
    else if(s2 == null)
        return @Comparator1
    return s1.compareTo(s2)
}
Collections.sort(list,comparator)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章