《Kotlin項目實戰開發》第4章 類與面向對象編程

第4章 類與面向對象編程java

在前面的章節中,咱們學習了Kotlin的語言基礎知識、類型系統等相關的知識。在本章節以及下一章中,咱們將一塊兒來學習Kotlin對面向對象編程以及函數式編程的支持。算法

本章咱們介紹Kotlin的面向對象編程。編程

4.1 面向對象編程簡史

50年代後期,在用FORTRAN語言編寫大型程序時,因爲沒有封裝機制,那個時候的變量都是「全局變量」,那麼就會不可避免的常常出現變量名衝突問題。在ALGOL60中採用了以 Begin - End 爲標識的程序塊,使塊內變量名是局部的,以免它們與程序中塊外的同名變量相沖突。在編程語言中首次提供了封裝(保護)的機制。此後,程序塊結構普遍用於Pascal 、Ada、C等高級語言之中。設計模式

60年代中後期,Simula語言在ALGOL基礎上研製開發,它將ALGOL的塊結構概念向前發展一步,提出了對象的概念,並使用了類,也支持類繼承。其後的發展簡史以下圖所示:安全

面向對象發展簡史

阿倫·凱(Alan Kay)是Smalltalk面向對象編程語言的發明人之一,也是面向對象編程思想的創始人之一,同時,他仍是筆記本電腦最先的構想者和現代Windows GUI的建築師。最先提出PC概念和互聯網的也是阿倫·凱,因此人們都尊稱他爲「預言大師」。他是當今IT界屈指可數的技術天才級人物。數據結構

面向對象編程思想主要是複用性和靈活性(彈性)。複用性是面向對象編程的一個主要機制。靈活性主要是應對變化的特性,由於客戶的需求是不斷改變的,怎樣適應客戶需求的變化,這是軟件設計靈活性或者說是彈性的問題。架構

Java是一種面向對象編程語言,它基於Smalltalk語言,做爲OOP語言,它具備如下五個基本特性:app

1.萬物皆對象,每個對象都會存儲數據,而且能夠對自身執行操做。所以,每個對象包含兩部分:成員變量和成員方法。在成員方法中能夠改變成員變量的值。框架

2.程序是對象的集合,他們經過發送消息來告知彼此所要作的事情,也就是調用相應的成員函數。編程語言

3.每個對象都有本身的由其餘對象所構成的存儲,也就是說在建立新對象的時候能夠在成員變量中使用已存在的對象。

4.每一個對象都擁有其類型,每一個對象都是某個類的一個實例,每個類區別於其它類的特性就是能夠向它發送什麼類型的消息,也就是它定義了哪些成員函數。

5.某一個特定類型的全部對象均可以接受一樣的消息。另外一種對對象的描述爲:對象具備狀態(數據,成員變量)、行爲(操做,成員方法)和標識(成員名,內存地址)。

面嚮對象語言實際上是對現實生活中的實物的抽象。

每一個對象可以接受的請求(消息)由對象的接口所定義,而在程序中必須由知足這些請求的代碼,這段代碼稱之爲這個接口的實現。當向某個對象發送消息(請求)時,這個對象便知道該消息的目的(該方法的實現已定義),而後執行相應的代碼。

咱們常常說一些代碼片斷是優雅的或美觀的,實際上意味着它們更容易被人類有限的思惟所處理。

對於程序的複合而言,好的代碼是它的表面積要比體積增加的慢。

代碼塊的「表面積」是是咱們複合代碼塊時所須要的信息(接口API協議定義)。代碼塊的「體積」就是接口內部的實現邏輯(API背後的實現代碼)。

在面向對象編程中,一個理想的對象應該是隻暴露它的抽象接口(純表面, 無體積),其方法則扮演箭頭的角色。若是爲了理解一個對象如何與其餘對象進行復合,當你發現不得不深刻挖掘對象的實現之時,此時你所用的編程範式的本來優點就蕩然無存了。

面向對象編程是一種編程思想,相比於早期的結構化程序設計,抽象層次更高,思考解決問題的方式上也更加貼近人類的思惟方式。現代編程語言基本都支持面向對象編程範式。

計算機領域中的全部問題,均可以經過向上一層進行抽象封裝來解決.這裏的封裝的本質概念,其實就是「映射」。從面向過程到面向對象,再到設計模式,架構設計,面向服務,Sass/Pass/Iass等等的思想,各類軟件理論思想五花八門,但萬變不離其宗——

  • 你要解決一個怎樣的問題?
  • 你的問題領域是怎樣的?
  • 你的模型(數據結構)是什麼?
  • 你的算法是什麼?
  • 你對這個世界的本質認知是怎樣的?
  • 你的業務領域的邏輯問題,流程是什麼?
    等等。

我對OO編程的目標歷來就不是複用。相反,對我來講,對象提供了一種處理複雜性的方式。這個問題能夠追溯到亞里士多德:您把這個世界視爲過程仍是對象?在OO興起運動以前,編程以過程爲中心--例如結構化設計方法。然而,系統已經到達了超越其處理能力的複雜性極點。有了對象,咱們可以經過提高抽象級別來構建更大的、更復雜的系統--我認爲,這纔是面向對象編程運動的真正勝利。(Grady Booch,統一建模語言UML創始人)

面向對象編程的以現實世界中的事物(對象)爲中心來思考, 認識問題, 並根據這些事物的本質特徵, 把它們抽象表示爲系統中的類。其核心思想能夠用下圖簡要說明:

面向對象編程

面向對象編程基於類編程,更加貼近人類解決問題的習慣方法。讓軟件世界更像現實世界。面向對象編程經過抽象出關鍵的問題域來分解系統。對象不只能表示具體的事物,還能表示抽象的規則、計劃或事件。關於面向對象編程的核心的概念以下圖所示

面向對象編程的核心的概念

4.2 聲明類

本節介紹Kotlin中類和構造函數的聲明。

4.2.1 空類

使用class關鍵字聲明類。咱們能夠聲明一個什麼都不幹的類

class AnEmptyClass

fun main(args: Array<String>) {
    val anEmptyClass = AnEmptyClass() // Kotlin中不須要使用new
    println(anEmptyClass)
    println(anEmptyClass is AnEmptyClass) // 對象實例是AnEmptyClass類型
    println(anEmptyClass::class)
}

輸出

com.easy.kotlin.AnEmptyClass@2626b418
true
class com.easy.kotlin.AnEmptyClass (Kotlin reflection is not available)

4.2.2 聲明類和構造函數

在Kotlin中, 咱們能夠在聲明類的時候同時聲明構造函數,語法格式是在類的後面使用括號包含構造函數的參數列表

class Person(var name: String, var age: Int, var sex: String) { // 聲明類和構造函數
    override fun toString(): String { // override關鍵字,重寫toString()
        return "Person(name='$name', age=$age, sex='$sex')"
    }
}

使用這樣的簡潔語法,能夠經過主構造器來定義屬性並初始化屬性值(這裏的屬性值能夠是var或val)。

在代碼中這樣使用Person類

val person = Person("Jack", 29, "M")
println("person = ${person}")

輸出

person = Person(name='Jack', age=29, sex='M')

另外,咱們也能夠先聲明屬性,等到構造實例對象的時候再去初始化屬性值,那麼咱們的Person類能夠聲明以下

class Person1 {
    lateinit var name: String // lateinit 關鍵字表示該屬性延遲初始化
    var age: Int = 0  // lateinit 關鍵字不能修飾 primitive 類型
    lateinit var sex: String
    override fun toString(): String {
        return "Person1(name='$name', age=$age, sex='$sex')"
    }
}

咱們能夠在代碼中這樣建立Person1的實例對象

val person1 = Person1()
    person1.name = "Jack"
    person1.age = 29
    person1.sex = "M"
    println("person1 = ${person1}")

輸出

person1 = Person1(name='Jack', age=29, sex='M')

若是咱們想聲明一個具備多種構造方式的類,可使用 constructor 關鍵字聲明構造函數,示例代碼以下

class Person2() { // 無參的主構造函數
    lateinit var name: String
    var age: Int = 0
    lateinit var sex: String

    constructor(name: String) : this() { // this 關鍵字指向當前類對象實例
        this.name = name
    }

    constructor(name: String, age: Int) : this(name) {
        this.name = name
        this.age = age
    }

    constructor(name: String, age: Int, sex: String) : this(name, age) {
        this.name = name
        this.age = age
        this.sex = sex
    }

    override fun toString(): String {
        return "Person1(name='$name', age=$age, sex='$sex')"
    }
}

上面的寫法,整體來看也有些樣板代碼,其實在IDEA中,咱們寫上面的代碼,只須要寫下面的3行,剩下的就交給IDEA自動生成了

class Person2 {
    lateinit var name: String
    var age: Int = 0
    lateinit var sex: String
}

自動生成構造函數的操做示意圖

1.在當前類中「右擊」鼠標操做,選擇Generate (在Mac上的快捷鍵是 Command + N)

右擊鼠標操做

  1. 點擊以後,跳出對話框:生成次級構造函數

選擇Generate

  1. 選擇構造函數的參數

生成次級構造函數

選中相應的屬性,點擊OK,便可生成。

一個屬性都不選,生成

constructor()

選擇一個 name 屬性,生成

constructor(name: String) {
        this.name = name
    }

選擇name,age屬性生成

constructor(name: String, age: Int) : this(name) {
        this.name = name
        this.age = age
    }

3個屬性都選擇,生成

constructor(name: String, age: Int, sex: String) : this(name, age) {
        this.name = name
        this.age = age
        this.sex = sex
    }

最後,咱們能夠在代碼中這樣建立Person2的實例對象

val person21 = Person2()
    person21.name = "Jack"
    person21.age = 29
    person21.sex = "M"
    println("person21 = ${person21}")

    val person22 = Person2("Jack", 29)
    person22.sex = "M"
    println("person22 = ${person22}")

    val person23 = Person2("Jack", 29, "M")
    println("person23 = ${person23}")

實際上,咱們在編程實踐中用到最多的構造函數,仍是這個

class Person(var name: String, var age: Int, var sex: String)

而當確實須要經過比較複雜的邏輯來構建一個對象的時候,可採用構建者(Builder)模式來實現。

4.3 抽象類與接口

抽象類表示「is-a」的關係,而接口所表明的是「has-a」的關係。

抽象類用來表徵問題領域的抽象概念。全部編程語言都提供抽象機制。機器語言是對機器的模仿抽象,彙編語言是對機器語言的高層次抽象,高級語言(Fortran,C,Basic等)是對彙編的高層次抽象。而咱們這裏所說的面向對象編程語言是對過程函數的高層次封裝。這個過程以下圖所示

編程語言的抽象機制

抽象類和接口是Kotlin語言中兩種不一樣的抽象概念,他們的存在對多態提供了很是好的支持。這個機制跟Java相同。

4.3.1 抽象類與抽象成員

抽象是相對於具象而言。例如設計一個圖形編輯軟件,問題領域中存在着長方形(Rectangle)、圓形(Circle)、三角形(Triangle)等這樣一些具體概念,它們是具象。可是它們又都屬於形狀(Shape)這樣一個抽象的概念。它們的關係以下圖所示

形狀Shape的抽象繼承關係

對應的Kotlin代碼以下

package com.easy.kotlin

abstract class Shape

class Rectangle : Shape() // 繼承類的語法是使用冒號 : , 父類須要在這裏使用構造函數初始化

class Circle : Shape()

class Triangle : Shape()

由於抽象的概念在問題領域中沒有對應的具體概念,因此抽象類是不可以實例化的。下面的代碼編譯器會報錯

val s = Shape() // 編譯不經過!不能實例化抽象類

咱們只能實例化它的繼承子類。代碼示例以下

val r = Rectangle()
println(r is Shape) // true

如今咱們有了抽象類,可是沒有成員。一般一個類的成員有屬性和函數。抽象類的成員也必須是抽象的,須要使用abstract 關鍵字修飾。下面咱們聲明一個抽象類Shape,並帶有width ,heigth,radius屬性和 area() 函數, 代碼以下

abstract class Shape {
    abstract var width: Double
    abstract var heigth: Double
    abstract var radius: Double
    abstract fun area(): Double
}

這個時候,繼承抽象類Shape的方法以下

class Rectangle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() { // 聲明類的同時也聲明瞭構造函數
    override fun area(): Double {
        return heigth * width
    }
}

class Circle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() {
    override fun area(): Double {
        return 3.14 * radius * radius
    }
}

其中,override 是覆蓋寫父類屬性和函數的關鍵字。

在代碼中這樣調用具體實現的類的函數

fun main(args: Array<String>) {
    val r = Rectangle(3.0, 4.0, 0.0)
    println(r.area()) // 12.0
    val c = Circle(0.0, 0.0, 4.0)
    println(c.area()) // 50.24
}

抽象類中能夠有帶實現的函數,例如咱們在抽象類Shape中添加一個函數onClick()

abstract class Shape {
    ...
    fun onClick() { // 默認是final的,不可被覆蓋重寫
        println("I am Clicked!")
    }
}

那麼,咱們在全部的子類中均可以直接調用這個onClick()函數

val r = Rectangle(3.0, 4.0, 0.0)
    r.onClick() // I am Clicked!
    val c = Circle(0.0, 0.0, 4.0)
    c.onClick() // I am Clicked!

父類Shape中的onClick()函數默認是final的,不可被覆蓋重寫。若是想要開放給子類從新實現這個函數,咱們能夠在前面加上open 關鍵字

abstract class Shape {
    ...
    open fun onClick() {
        println("I am Clicked!")
    }
}

在子類中這樣覆蓋重寫

class Rectangle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() {
    override fun area(): Double {
        return heigth * width
    }

    override fun onClick(){
        println("${this::class.simpleName} is Clicked!")
    }
}

fun main(args: Array<String>) {
    val r = Rectangle(3.0, 4.0, 0.0)
    println(r.area())
    r.onClick()
}

其中,this::class.simpleName 是Kotlin中的反射的API,在Gradle工程的build.gradle中須要添加依賴 compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" ,咱們將在後面的章節中詳細介紹。

上面的代碼運行輸出

12.0
Rectangle is Clicked!

當子類繼承了某個類以後,即可以使用父類中的成員變量,可是並非徹底繼承父類的全部成員變量。具體的原則以下:

1.可以繼承父類的public和protected成員變量;不可以繼承父類的private成員變量;

2.對於父類的包訪問權限成員變量,若是子類和父類在同一個包下,則子類可以繼承;不然,子類不可以繼承;

3.對於子類能夠繼承的父類成員變量,若是在子類中出現了同名稱的成員變量,則會發生隱藏現象,即子類的成員變量會屏蔽掉父類的同名成員變量。若是要在子類中訪問父類中同名成員變量,須要使用super關鍵字來進行引用。

4.3.2 接口

接口是一種比抽象類更加抽象的「類」。接口自己表明的是一種「類型」的概念。但在語法層面,接口自己不是類,不能實例化接口,咱們只能實例化它的實現類。

接口是用來創建類與類之間的協議。實現該接口的實現類必需要實現該接口的全部方法。在Java 8 和Kotlin中,接口能夠實現一些通用的方法。

接口是抽象類的延伸,Kotlin跟Java同樣,不支持同時繼承多個父類,也就是說繼承只能存在一個父類(單繼承)。可是接口不一樣,一個類能夠同時實現多個接口(多組合),無論這些接口之間有沒有關係。這樣能夠實現多重繼承。

和Java相似,Kotlin使用interface做爲接口的關鍵詞:

interface ProjectService

Kotlin 的接口與 Java 8 的接口相似。與抽象類相比,他們均可以包含抽象的方法以及方法的實現:

interface ProjectService {
    val name: String
    val owner: String
    fun save(project: Project)
    fun print() {
        println("I am project")
    }
}

接口是沒有構造函數的。咱們使用冒號: 語法來實現一個接口,若是有多個用逗號隔開:

class ProjectServiceImpl : ProjectService // 跟繼承抽象類語法同樣,使用冒號
class ProjectMilestoneServiceImpl : ProjectService, MilestoneService // 實現多個接口使用逗號( ,) 隔開

在重寫print()函數時,由於咱們實現的ProjectService、MilestoneService都有一個print()函數,當咱們直接使用super.print()時,編譯器是沒法知道咱們想要調用的是那個裏面的print函數的,這個咱們叫作覆蓋衝突,以下圖所示

覆蓋衝突

這個時候,咱們可使用下面的語法來調用:

super<ProjectService>.print()
super<MilestoneService>.print()

4.4 object對象

單例模式很經常使用。它是一種經常使用的軟件設計模式。例如,Spring中的Bean默認就是單例。經過單例模式能夠保證系統中一個類只有一個實例。即一個類只有一個對象實例。

Kotlin中沒有 靜態屬性和方法,可是可使用關鍵字 object 聲明一個object 單例對象:

package com.easy.kotlin

object User {
    val username: String = "admin"
    val password: String = "admin"
    fun hello() {
        println("Hello, object !")
    }
}

fun main(args: Array<String>) {
    println(User.username) // 跟Java的靜態類同樣的調用形式
    println(User.password)
    User.hello()
}

Kotlin中還提供了 伴生對象 ,用companion object關鍵字聲明:

class DataProcessor {
    companion object DataProcessor {
        fun process() {
            println("I am processing data ...")
        }
    }
}

fun main(args: Array<String>) {
    DataProcessor.process() // I am processing data ...
}

一個類只能有1個伴生對象。

4.5 數據類

顧名思義,數據類就是隻存儲數據,不包含操做行爲的類。Kotlin的數據類能夠爲咱們節省大量樣板代碼(Java 中強制咱們要去寫一堆getter、setter,而實際上這些方法都是「不言自明」的),這樣最終代碼更易於理解和便於維護。

使用關鍵字爲 data class 建立一個只包含數據的類:

data class LoginUser(val username: String, val password: String)

在IDEA中提供了方便的Kotlin工具箱,咱們能夠把上面的代碼反編譯成等價的Java代碼。步驟以下

1.菜單欄選擇:Tools -> Kotlin -> Show Kotlin Bytecode

菜單欄選擇:Tools -&gt; Kotlin -&gt; Show Kotlin Bytecode

  1. 點擊Decompile

點擊Decompile

  1. 反編譯以後的Java代碼

反編譯以後的Java代碼

上面這段反編譯以後的完整的Java代碼是

public final class LoginUser {
   @NotNull
   private final String username;
   @NotNull
   private final String password;

   @NotNull
   public final String getUsername() {
      return this.username;
   }

   @NotNull
   public final String getPassword() {
      return this.password;
   }

   public LoginUser(@NotNull String username, @NotNull String password) {
      Intrinsics.checkParameterIsNotNull(username, "username");
      Intrinsics.checkParameterIsNotNull(password, "password");
      super();
      this.username = username;
      this.password = password;
   }

   @NotNull
   public final String component1() {
      return this.username;
   }

   @NotNull
   public final String component2() {
      return this.password;
   }

   @NotNull
   public final LoginUser copy(@NotNull String username, @NotNull String password) {
      Intrinsics.checkParameterIsNotNull(username, "username");
      Intrinsics.checkParameterIsNotNull(password, "password");
      return new LoginUser(username, password);
   }

   // $FF: synthetic method
   // $FF: bridge method
   @NotNull
   public static LoginUser copy$default(LoginUser var0, String var1, String var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.username;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.password;
      }

      return var0.copy(var1, var2);
   }

   public String toString() {
      return "LoginUser(username=" + this.username + ", password=" + this.password + ")";
   }

   public int hashCode() {
      return (this.username != null ? this.username.hashCode() : 0) * 31 + (this.password != null ? this.password.hashCode() : 0);
   }

   public boolean equals(Object var1) {
      if (this != var1) {
         if (var1 instanceof LoginUser) {
            LoginUser var2 = (LoginUser)var1;
            if (Intrinsics.areEqual(this.username, var2.username) && Intrinsics.areEqual(this.password, var2.password)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

編譯器會從主構造函數中聲明的屬性,自動建立如下函數:

  • equals() / hashCode() 函數
  • toString() 格式爲"LoginUser(username=" + this.username + ", password=" + this.password + ")"
  • component1(),component2() 函數返回對應下標的屬性值,按聲明順序排列
  • copy() 函數: 根據舊對象屬性從新 new LoginUser(username, password) 一個對象出來

若是這些函數在類中已經被明肯定義了,或者從超類中繼承而來,編譯器就再也不生成。

數據類有以下限制:

  • 主構造函數至少包含一個參數
  • 參數必須標識爲val 或者 var
  • 不能爲 abstract, open, sealed 或者 inner
  • 不能繼承其它類 (但能夠實現接口)

另外,數據類能夠在解構聲明中使用:

package com.easy.kotlin

data class LoginUser(val username: String, val password: String)

fun main(args: Array<String>) {
    val loginUser = LoginUser("admin", "admin")
    val (username, password) = loginUser
    println("username = ${username}, password = ${password}") // username = admin, password = admin
}

Kotlin 標準庫提供了 Pair 和 Triple數據類 。

4.6 註解

註解是將元數據附加到代碼中。元數據信息由註解 kotlin.Metadata定義。

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
internal annotation class Metadata

這個@Metadata信息存在於由 Kotlin 編譯器生成的全部類文件中, 並由編譯器和反射讀取。例如,咱們使用Kotlin聲明一個註解

annotation class Suspendable // Java中使用的是@interface Suspendable

那麼,編譯器會生成對應的元數據信息

@Retention(RetentionPolicy.RUNTIME)
@Metadata(
   mv = {1, 1, 7},
   bv = {1, 0, 2},
   k = 1,
   d1 = {"\u0000\n\n\u0002\u0018\u0002\n\u0002\u0010\u001b\n\u0000\b\u0086\u0002\u0018\u00002\u00020\u0001B\u0000¨\u0006\u0002"},
   d2 = {"Lcom/easy/kotlin/Suspendable;", "", "production sources for module kotlin_tutorials_main"}
)
public @interface Suspendable {
}

Kotlin 的註解徹底兼容 Java 的註解。例如,咱們在Kotlin中使用Spring Data Jpa

interface ImageRepository : PagingAndSortingRepository<Image, Long> {
   
    @Query("SELECT a from #{#entityName} a where a.isDeleted=0 and a.isFavorite=1 and a.category like %:searchText% order by a.gmtModified desc")
    fun searchFavorite(@Param("searchText") searchText: String, pageable: Pageable): Page<Image>

    @Throws(Exception::class)
    @Modifying
    @Transactional
    @Query("update #{#entityName} a set a.isFavorite=1,a.gmtModified=now() where a.id=?1")
    fun addFavorite(id: Long)
}

用起來跟Java的註解基本同樣。再舉個Kotlin使用Spring MVC註解的代碼實例

@Controller
class MeituController {
    @Autowired
    lateinit var imageRepository: ImageRepository

    @RequestMapping(value = *arrayOf("/", "meituView"), method = arrayOf(RequestMethod.GET))
    fun meituView(model: Model, request: HttpServletRequest): ModelAndView {
        model.addAttribute("requestURI", request.requestURI)
        return ModelAndView("meituView")
    }
}

從上面的例子,咱們能夠看出Kotlin使用Java框架很是簡單方便。

4.7 枚舉

Kotlin中使用 enum class 關鍵字來聲明一個枚舉類。例如

enum class Direction {
    NORTH, SOUTH, WEST, EAST // 每一個枚舉常量都是一個對象, 用逗號分隔
}

相比於字符串常量,使用枚舉可以實現類型安全。枚舉類有兩個內置的屬性:

public final val name: String
    public final val ordinal: Int

分別表示的是枚舉對象的值跟下標位置。例如上面的Direction枚舉類,它的枚舉對象的信息以下

>>> val north = Direction.NORTH
>>> north.name
NORTH
>>> north.ordinal
0
>>> north is Direction
true

每個枚舉都是枚舉類的實例,它們能夠被初始化:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

枚舉Color的枚舉對象的信息以下

>>> val c = Color.GREEN
>>> c
GREEN
>>> c.rgb
65280
>>> c.ordinal
1
>>> c.name
GREEN

4.8 內部類

4.8.1 普通嵌套類

Kotlin中,類能夠嵌套。一個類能夠嵌套在其餘類中,並且能夠嵌套多層。

class NestedClassesDemo {
    class Outer {
        private val zero: Int = 0
        val one: Int = 1

        class Nested {
            fun getTwo() = 2
            class Nested1 {
                val three = 3
                fun getFour() = 4
            }
        }
    }
}

測試代碼:

val one = NestedClassesDemo.Outer().one
    val two = NestedClassesDemo.Outer.Nested().getTwo()
    val three = NestedClassesDemo.Outer.Nested.Nested1().three
    val four = NestedClassesDemo.Outer.Nested.Nested1().getFour()

咱們能夠看出,代碼中 NestedClassesDemo.Outer.Nested().getTwo() 訪問嵌套類的方式是直接使用 類名.來訪問, 有多少層嵌套,就用多少層類名來訪問。

普通的嵌套類,沒有持有外部類的引用,因此是沒法訪問外部類的變量的:

class NestedClassesDemo {
class Outer {
        private val zero: Int = 0
        val one: Int = 1

        class Nested {
            fun getTwo() = 2
            fun accessOuter() = {
                println(zero) // error, cannot access outer class
                println(one)  // error, cannot access outer class
            }
        }
}
}

4.8.2 嵌套內部類

若是一個類Inner想要訪問外部類Outer的成員,能夠在這個類前面添加修飾符 inner。內部類會帶有一個對外部類的對象的引用:

package com.easy.kotlin

class NestedClassesDemo {
    class Outer {
        private val zero: Int = 0
        val one: Int = 1

        inner class Inner {
            fun accessOuter() = {
                println(zero) // works
                println(one) // works
            }

        }
    }
}

fun main(args: Array<String>) {
    val innerClass = NestedClassesDemo.Outer().Inner().accessOuter()
}

咱們能夠看到,當訪問inner class Inner的時候,咱們使用的是Outer().Inner(), 這是持有了Outer的對象引用。跟普通嵌套類直接使用類名訪問的方式區分。

4.8.3 匿名內部類

匿名內部類,就是沒有名字的內部類。既然是內部類,那麼它天然也是能夠訪問外部類的變量的。

咱們使用對象表達式建立一個匿名內部類實例:

class NestedClassesDemo {
    class AnonymousInnerClassDemo {
        var isRunning = false
        fun doRun() {
            Thread(object : Runnable { // 匿名內部類
                override fun run() {
                    isRunning = true
                    println("doRun : i am running, isRunning = $isRunning")
                }
            }).start()
        }
    }
}

若是對象是函數式 Java 接口,即具備單個抽象方法的 Java 接口的實例,例如上面的例子中的Runnable接口:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

咱們可使用lambda表達式建立它,下面的幾種寫法都是能夠的:

fun doStop() {
                var isRunning = true
                Thread({
                    isRunning = false
                    println("doStop: i am not running, isRunning = $isRunning")
                }).start()
            }

            fun doWait() {
                var isRunning = true
                val wait = Runnable {
                    isRunning = false
                    println("doWait: i am waiting, isRunning = $isRunning")
                }
                Thread(wait).start()
            }

            fun doNotify() {
                var isRunning = true
                val wait = {
                    isRunning = false
                    println("doNotify: i notify, isRunning = $isRunning")
                }
                Thread(wait).start()
            }

更多關於Lambda表達式以及函數式編程相關內容,咱們將在下一章節中介紹。

本章小結

相關文章
相關標籤/搜索