第4章 類與面向對象編程java
在前面的章節中,咱們學習了Kotlin的語言基礎知識、類型系統等相關的知識。在本章節以及下一章中,咱們將一塊兒來學習Kotlin對面向對象編程以及函數式編程的支持。算法
本章咱們介紹Kotlin的面向對象編程。編程
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創始人)
面向對象編程的以現實世界中的事物(對象)爲中心來思考, 認識問題, 並根據這些事物的本質特徵, 把它們抽象表示爲系統中的類。其核心思想能夠用下圖簡要說明:
面向對象編程基於類編程,更加貼近人類解決問題的習慣方法。讓軟件世界更像現實世界。面向對象編程經過抽象出關鍵的問題域來分解系統。對象不只能表示具體的事物,還能表示抽象的規則、計劃或事件。關於面向對象編程的核心的概念以下圖所示
本節介紹Kotlin中類和構造函數的聲明。
使用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)
在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)
選中相應的屬性,點擊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)模式來實現。
抽象類表示「is-a」的關係,而接口所表明的是「has-a」的關係。
抽象類用來表徵問題領域的抽象概念。全部編程語言都提供抽象機制。機器語言是對機器的模仿抽象,彙編語言是對機器語言的高層次抽象,高級語言(Fortran,C,Basic等)是對彙編的高層次抽象。而咱們這裏所說的面向對象編程語言是對過程函數的高層次封裝。這個過程以下圖所示
抽象類和接口是Kotlin語言中兩種不一樣的抽象概念,他們的存在對多態提供了很是好的支持。這個機制跟Java相同。
抽象是相對於具象而言。例如設計一個圖形編輯軟件,問題領域中存在着長方形(Rectangle)、圓形(Circle)、三角形(Triangle)等這樣一些具體概念,它們是具象。可是它們又都屬於形狀(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關鍵字來進行引用。
接口是一種比抽象類更加抽象的「類」。接口自己表明的是一種「類型」的概念。但在語法層面,接口自己不是類,不能實例化接口,咱們只能實例化它的實現類。
接口是用來創建類與類之間的協議。實現該接口的實現類必需要實現該接口的全部方法。在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()
單例模式很經常使用。它是一種經常使用的軟件設計模式。例如,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個伴生對象。
顧名思義,數據類就是隻存儲數據,不包含操做行爲的類。Kotlin的數據類能夠爲咱們節省大量樣板代碼(Java 中強制咱們要去寫一堆getter、setter,而實際上這些方法都是「不言自明」的),這樣最終代碼更易於理解和便於維護。
使用關鍵字爲 data class 建立一個只包含數據的類:
data class LoginUser(val username: String, val password: String)
在IDEA中提供了方便的Kotlin工具箱,咱們能夠把上面的代碼反編譯成等價的Java代碼。步驟以下
1.菜單欄選擇:Tools -> Kotlin -> Show Kotlin Bytecode
上面這段反編譯以後的完整的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; } } }
編譯器會從主構造函數中聲明的屬性,自動建立如下函數:
若是這些函數在類中已經被明肯定義了,或者從超類中繼承而來,編譯器就再也不生成。
數據類有以下限制:
另外,數據類能夠在解構聲明中使用:
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數據類 。
註解是將元數據附加到代碼中。元數據信息由註解 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框架很是簡單方便。
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
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 } } } }
若是一個類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的對象引用。跟普通嵌套類直接使用類名訪問的方式區分。
匿名內部類,就是沒有名字的內部類。既然是內部類,那麼它天然也是能夠訪問外部類的變量的。
咱們使用對象表達式建立一個匿名內部類實例:
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表達式以及函數式編程相關內容,咱們將在下一章節中介紹。