第五章 Kotlin 面向對象程式設計(OOP)
正式上架:《Kotlin極簡教程》Official on shelves: Kotlin Programming minimalist tutorial
京東JD:
https://item.jd.com/12181725.html 天貓Tmall: https://detail.tmall.com/item.htm?id=558540170670
1. 面向對象的 HelloWorld
HelloWorld
開篇我們來看一個OOP版本的HelloWorld:
package com.easy.kotlin
class Greeter(
val name: String// 屬性
) {
// 行為
fun greet() {
println("Hello, ${name}");
}
}
fun main(args: Array<String>) {
Greeter("Jack").greet() // Kotlin建立對象,不再使用`new` keyword
}
Kotlin 同Java、 Scala、Groovy 一樣,都使用關鍵字
class
來定義類。
建立一個類的執行個體無需像 Java 一樣使用 new 關鍵字,直接調用構造函數
Greeter("Jack")
即可。
Kotlin 檔案名以
.kt
為字尾,源代碼檔案中可以定義多個類。
Greeter.kt
經過編譯之後的類檔案預設命名是檔案名加上
Kt
結尾,即為
GreeterKt.class
。
2. 面向對象程式設計思想簡述
在20世紀60年代,軟體曾出現過嚴重危機,由軟體錯誤而引起的資訊丢失、系統報廢事件屢有發生。為此,1968年,荷蘭E.W.Dijkstra提出了程式設計中常用的GOTO語句的三大危害:
破壞了程式的動靜一緻性;
程式不易測試;
限制了代碼優化。
此舉引起了軟體界長達數年的論戰,并由此産生了結構化程式設計方法,同時誕生了基于這一設計方法的程式設計語言Pascal。
由瑞士Niklaus Wirth開發的Pascal,具備優秀的資料結構和控制結構,為程式員提供了極大的友善性與靈活性,大受歡迎。筆者中學時候,第一門啟蒙語言就是Pascal。至今還清晰記得那台式螢幕上藍色的Turbo Pascal界面,閃爍着白色的代碼的場景。
結構化程式設計思想采用了子產品分解與功能抽象和自頂向下、分而治之的方法,進而有效地将一個較複雜的程式系統設計任務分解成許多易于控制和處理的子程式,便于開發和維護。是以,結構化方法迅速走紅,并在整個20世紀70年代的軟體開發中占絕對統治地位。
但是,到了70年代末期,随着計算機科學的發展和應用領域的不斷擴大,對計算機技術的要求越來越高。結構化程式設計語言和結構化分析與設計已無法滿足使用者需求的變化,于是面向對象程式設計(OOP)技術随之而來。 面向對象程式設計在未來的軟體開發領域引起了大的變革,極大地提高了軟體開發的效率。
面向對象語言借鑒了20世紀50年代的人工智能語言LISP,引入了動态綁定的概念和互動式開發環境的思想;始于20世紀60 年代的離散事件模拟語言SIMULA67,引入了類和繼承。于20世紀70年代的Smalltalk逐漸發展成熟。Java借鑒了SmallTalk,統治了網際網路開發領域的大片江山。
面向對象程式設計思想,是為了解決現實問題而應運而生的。面向對象程式設計是一種自頂向下的程式設計方法.萬事萬物都是對象,對象有其行為(方法), 狀态(成員變量,屬性)。
所謂“類”和“對象”,對應過程式語言(例如,C語言)裡面的結構體(struct),本質是一個邏輯抽象的代碼 “映射”(map)(一切皆是映射)。
計算機領域中的所有問題,都可以通過向上一層進行抽象封裝來解決.這裡的封裝的本質概念,其實就是”映射“。
從面向過程到面向對象,再到設計模式,架構設計,面向服務,各種軟體理論五花八門,但萬變不離其宗——你要解決一個怎樣的問題?你對這個世界的本質認知是怎樣的?你的業務領域的邏輯問題,流程等等。
Grady Booch:我對OO程式設計的目标從來就不是複用。相反,對我來說,對象提供了一種處理複雜性的方式。這個問題可以追溯到亞裡士多德:您把這個世界視為過程還是對象?在OO興起運動之前,程式設計以過程為中心--例如結構化設計方法。然而,系統已經到達了超越其處理能力的複雜性極點。有了對象,我們能夠通過提升抽象級别來建構更大的、更複雜的系統--我認為,這才是面向對象程式設計運動的真正勝利。
人的生命隻有一次。生命太短暫,是以不要去做一些重複無聊的事情。能交給計算機做的,就盡量交給計算機去做。此乃程式設計的濫觞之地。
縱覽整個計算機的發展史,最重要的思想非“抽象”莫屬。
一層層的抽象封裝了實作的細節,計算機開疆擴土,南征北戰,發展到了今天蔚為壯觀的網際網路,雲計算,大資料,機器智能的時代。
同時,也使得程式員寫代碼,從最初的拿着符号表在紙袋上打孔,到使用近似自然語言的進階程式設計語言來程式設計,以及當今各種庫,api,架構,內建開發工具集,智能化的編碼提示,代碼生成等等技術,使得我們現在程式員,能更多的去關注問題本身以及邏輯的實作。
從隻有少數技術人會用的指令行的作業系統unix、dos,到人性化的GUI圖形界面作業系統,以及移動網際網路時代的智能裝置,計算機越來越融入到人類生活的方方面面。
正如解決數學問題通常我們會談“思想”,諸如反證法、化繁為簡等,解決計算機問題也有很多非常出色的思想。思想之是以稱為思想,是因為“思想”有拓展性與引導性,可以解決一系列問題。
解決問題的複雜程度直接取決于抽象的種類及品質。過将結構、性質不同的底層實作進行封裝,向上提供統一的API接口,讓使用者覺得就是在使用一個統一的資源,或者讓使用者覺得自己在使用一個本來底層不直接提供、“虛拟”出來的資源。
計算機中的所有問題 , 都可以通過向上抽象封裝一層來解決。
《易傳·系辭上傳》:“易有太極,是生兩儀,兩儀生四象,四象生八卦。” 如今的網際網路世界,其基石卻是01(陰陽),不得不佩服我華夏先祖的博大精深的智慧。
就好比通過的電子電路中的電平進行01邏輯映射,布爾代數邏輯體系映射到了數字邏輯電路系統;
我們最早使用機器碼01來控制電平高低,進而控制數字電路操作寄存器,CPU等嘗試着表達思想中的邏輯, 控制硬體計算和顯示, 發現是可行的。
後來,我們把其中的最常用的操作步驟,進一步封裝抽象成CPU指令集映射,于是誕生了彙編助記符語言——比機器指令更容易記憶;
再接着, 創造了編譯器、解釋器和計算機進階語言。通過彙編語言的向上抽象封裝一層編譯器,于是有了pascal,fortran,C語言;在犧牲少量性能的情況下, 獲得比彙編語言更強且更容易使用的語句控制能力:條件、分支、循環, 以及更多的語言特性: 指針、結構體、聯合體、枚舉等, 還創造了函數, 能夠将一系列指令封裝成一個獨立的邏輯塊反複使用;再對核心函數api進行封裝形成開發包(Development Kit) 。
逐漸地,産生了面向過程的程式設計方法;
後來, 人們發現将資料和邏輯封裝成對象, 更接近于現實世界, 且更容易維護大型軟體, 又出現了面向對象的程式設計語言和程式設計方法學, 增加了新的語言特性: 繼承、 多态、 模闆、 異常錯誤。
為了不必重複開發常見工具和任務, 人們創造和封裝了容器及算法、SDK, 垃圾回收器, 甚至是并發庫;
為了讓計算機語言更有力更有效率地表達各種現實邏輯, 消解軟體開發中遇到的沖突, 還在語言中支援了元程式設計、 高階函數, 閉包 等有用特性。
為了更高效率地開發可靠的軟體和應用程式, 人們逐漸建構了代碼編輯器、 IDE、 代碼版本管理工具、公共庫、應用架構、 可複用元件、系統規範、網絡協定、 語言标準等, 針對遇到的問題提出了許多不同的思路和解決方案, 并總結提煉成特定的技術和設計模式, 還探讨和形成了不少軟體開發過程, 用來保證最終釋出的軟體品質。 盡管編寫的這些軟體和工具還存在不少 BUG ,但是它們都“奇迹般地存活”, 并共同建構了今天蔚為壯觀的網際網路時代的電商,網際網路金融,雲計算,大資料,物聯網,機器智能等等的“虛拟世界”。
縱觀計算機程式設計發展史,人類的大腦不斷抽象、努力封裝一個又一個邏輯體系——使得我們逐漸能夠以更加友好、更加自然的方式去編寫程式。
我們知道,程式設計的本質就是在創造世界。當然,這是一個虛拟的世界。是人類大腦對我們真實的世界的邏輯映射。既然是一個世界,就必然會有
存在(對象,資料結構)
, 以及無限可能變化的
運動(算法,方法,函數)
不管是面向對象(存在)程式設計,還是函數式(運動(算法,方法,函數))程式設計,都是我們人類大腦對我們現實世界的問題的解決方案過程中,所建立的思維模型。模型畢竟還是模型,不可能裝下全部的真實的世界。正是有像
01
,
陰陽
這種形而上的哲學概念,才有了世界的無數種可能。
以上算是一些關于程式設計的粗淺的思考。好了,下面言歸正傳,進入正題。
3.Kotlin 面向對象程式設計(OOP)
3.1 聲明類
Kotlin使用關鍵字*class *聲明類
class Book {
}
這個類聲明被花括号包圍,包括
- 類名
- 類head頭(指定其類型參數,主構造函數等)
- 類body。
類頭和主幹都是可選的。如果這個類沒有body,花括号可以被省略。
class Empty
3.2 類修飾符
open 修飾符
Kotlin 預設會為每個變量和方法添加 final 修飾符。也就是說,在 Kotlin 中預設每個類都是不可被繼承的。這麼做的目的是為了程式運作的性能。
其實在 Java 程式中,你也應該盡可能為每個類添加final 修飾符( 見 《Effective Java 》第四章 17 條)。
如果你确定這個類是會被繼承的,那麼你需要給這個類添加 open 修飾符。
internal 修飾符
Java 有三種通路修飾符,public/private/protected。沒有修飾符,是預設的包級别通路權限。
在 Kotlin 中,有private、protected、internal以及 public等四種修飾符,它們可用于修飾類、對象、接口、構造器、函數、屬性、以及屬性的set方法等。預設的通路權限是 public。其中 internal,是子產品級别的通路權限。
子產品(module)是指一起編譯的一組 Kotlin 源代碼檔案:
- 一個 IntelliJ IDEA 子產品
- 一個 Maven 工程, 或 Gradle 工程
- 通過 Ant 任務的一次調用編譯的一組檔案
3.3 構造函數
在Kotlin中的類可以有主構造函數(Primary Constructor)和一個或多個二級構造函數(Secondary Constructor)。在 Scala 中稱為 Main Constructor 和 Slave Constructor,Kotlin換了個名字,意思基本相同。
主構造函數是類頭的一部分, 代碼示例如下:
class Book1 constructor(val name:String, val author:String){
}
如果這個主構造函數沒有任何注解或者可見的修飾符,這個constructor關鍵字可以被省略
class Book2 (val name:String, val author:String){
}
主構造方法的參數可以聲明為 val 或 var ,使用方法與其聲明為成員變量時相同。
這個主構造函數不能包含任何的代碼。不過,初始化的代碼可以被放置在initializer blocks(初始的語句塊),以init為字首作為關鍵字。該語句塊中的所有可執行語句都屬于主構造器,在對象被建立時都會被調用。
class Book2 (val name:String, val author:String){
init {
println("Book2 initialized with value (name= ${name}, author=${author})")
name = name.toUpperCase()
}
}
完整代碼示例:
package com.easy.kotlin
/**
* Created by jack on 2017/5/30.
*/
class Empty
class Book1 constructor(val name:String, val author:String){
override fun toString(): String {
return "Book1(name='$name', author='$author')"
}
}
class Book2 (var name:String, val author:String){
init {
println("Book2 initialized with value (name= ${name}, author=${author})")
name = name.toUpperCase()
}
override fun toString(): String {
return "Book2(name='$name', author='$author')"
}
}
fun main(args:Array<String>){
println(Empty())
println(Book1("Easy Kotlin", "Jack"))
println(Book2("Easy Kotlin", "Jack"))
}
運作輸出:
com.easy.kotlin.Empty@4b1210ee
Book1(name='Easy Kotlin', author='Jack')
Book2 initialized with value (name= Easy Kotlin, author=Jack)
Book2(name='EASY KOTLIN', author='Jack')
次(擴充)構造函數
類也可以擁有被稱為"二級構造函數"(實作多個構造函數),通常被加上字首"constructor"。
1、次構造函數不能有聲明 val 或 var
2、如果類有一個主構造函數(無論有無參數),每個次構造函數需要直接或間接委托給主構造函數(即調用主構造方法或其它次構造函數),用this關鍵字來調用。
class Person {
var name: String = ""
var age: Int = 0
constructor() {
println("constructor 1 called!")
}
constructor(name: String) : this() {
println("constructor 2 called!")
this.name = name
}
constructor(name: String, age: Int) : this(name) {
println("constructor 3 called!")
this.name = name
this.age = age
}
override fun toString(): String {
return "Person(name='$name', age=$age)"
}
}
fun main(args: Array<String>) {
println(Person())
println(Person("Jack"))
println(Person("Jack",29))
}
constructor 1 called!
Person(name='', age=0)
constructor 1 called!
constructor 2 called!
Person(name='Jack', age=0)
constructor 1 called!
constructor 2 called!
constructor 3 called!
Person(name='Jack', age=29)
我們可以看出,調用構造函數
Person("Jack",29)
建立對象,編譯器通過層層向上委托,最終完成該對象的建立。
構造函數傳參
fun main(args: Array<String>) {
val pair = Pair(1, "one")
val (num, name) = pair
println("num = $num, name = $name")
val triple = Triple(10,"B",10.0)
val (a,b,c) = triple
println("a=$a, b=$b, c=$c")
}
class Pair<K, V>(val first: K, val second: V) {
operator fun component1(): K {
return first
}
operator fun component2(): V {
return second
}
}
class Triple<K,V,T>(val first: K,val second:V,val third:T){
operator fun component1():K{return first}
operator fun component2():V{return second}
operator fun component3():T{return third}
}
運作結果:
num = 1, name = one
a=10, b=B, c=10.0
3.4 建立類的執行個體
要建立一個類的執行個體,我們隻要像普通的函數那樣調用其構造函數即可:
val person = Person("Jack",29)
Kotlin中,不再使用
new
關鍵字
類成員
類可以包括
- 構造和初始化子產品
- 函數
- 屬性
- 匿名類
- 内部類
- 對象聲明
3.5 繼承
在Kotlin所有的類中都有一個共同的父類
Any
(這跟Scala一樣),這是一個預設的open根父類。我們看一下Any類的源碼吧:
package kotlin
/**
* The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
*/
public open class Any {
/**
* Indicates whether some other object is "equal to" this one. Implementations must fulfil the following
* requirements:
*
* * Reflexive: for any non-null reference value x, x.equals(x) should return true.
* * Symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
* * Transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true
* * Consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
*
* Note that the `==` operator in Kotlin code is translated into a call to [equals] when objects on both sides of the
* operator are not null.
*/
public open operator fun equals(other: Any?): Boolean
/**
* Returns a hash code value for the object. The general contract of hashCode is:
*
* * Whenever it is invoked on the same object more than once, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.
* * If two objects are equal according to the equals() method, then calling the hashCode method on each of the two objects must produce the same integer result.
*/
public open fun hashCode(): Int
/**
* Returns a string representation of the object.
*/
public open fun toString(): String
}
仔細閱讀注釋部分的内容,我們基本就能知道這個Any是啥了。
我們使用Kotlin程式設計,聲明的所有類,都預設繼承這個Any類。
class Example // Implicitly inherits from Any
Kotlin中所有的類預設都是不可繼承的(
final
),是以我們隻能繼承那些明确聲明
open
或者
abstract
的類。
當我們隻有單個構造器時,我們需要在從父類繼承下來的構造器中指定需要的參數。
代碼示例:
open class Person {
var name: String = ""
var age: Int = 0
constructor() {
println("constructor 1 called!")
}
constructor(name: String) : this() {
println("constructor 2 called!")
this.name = name
}
constructor(name: String, age: Int) : this(name) {
println("constructor 3 called!")
this.name = name
this.age = age
}
override fun toString(): String {
return "Person(name='$name', age=$age)"
}
fun sayHi() {
val name = this.name
println("${name} say Hi to u!")
val b = Book("Easy Kotlin")
println(b)
}
class Book {
var name: String = ""
constructor() {
}
constructor(name: String) : this() {
this.name = name
}
override fun toString(): String {
return "Book(name='$name')"
}
}
}
class Student : Person {
var id: String = ""
var sex: String = ""
constructor() {
}
constructor(id: String, sex: String) : super() {
this.id = id
this.sex = sex
}
constructor(name: String, id: String, sex: String) : super(name) {
this.id = id
this.sex = sex
}
constructor(name: String, age: Int, id: String, sex: String) : super(name, age) {
this.id = id
this.sex = sex
}
override fun toString(): String {
return "Student(id='$id', sex='$sex', name=${super.name}, age = ${super.age})"
}
}
我們使用
super.name
,
super.age
來調用父類中的屬性字段。
3.6 接口和抽象類
Kotlin接口使用interface關鍵字。Kotlin 的接口類似于 Java 8。可以包含抽象方法,以及方法的實作。和抽象類不同的是,接口不能儲存狀态;可以有屬性但必須是抽象的 或 提供通路實作。
Kotlin抽象類使用
abstract
關鍵字聲明。
Kotlin中的繼承抽象類,實作接口的處理方式,跟Java一樣,采用“單繼承,多實作”的方式。代碼示例如下:
abstract class A {
abstract fun fa()
abstract fun f()
}
interface B {
fun fb() {
print("FB")
}
fun f() {
print("B")
}
}
class C : A, B {
override fun fa() {
}
override fun fb() {
}
override fun f() {
}
constructor() {
}
}
我們直接使用
class C : A, B
這樣的寫法。比Java中使用extends, implements要簡潔。
接口和抽象類的函數,預設是
open
的。我們可以不用标注。
另外,我們可以重寫一個
open
非抽象類的
open
函數,得到一個抽象類的抽象函數。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
3.7 實作接口
package com.easy.kotlin
/**
* Created by jack on 2017/5/30.
*/
interface Clickable {
fun click()
}
class Button : Clickable {
override fun click() = println("I was clicked")
}
fun main(args: Array<String>) {
Button().click()
}
Kotlin中實作接口,使用冒号:關鍵字辨別。實作類前使用override 關鍵字修飾。
3.8 override重寫覆寫父類函數
Kotlin追求簡潔顯式的風格。
Kotlin在繼承父類并覆寫父類函數時,要求父類必須有
open
标注,被覆寫的函數必須有
open
标注,并且子類的函數必須加
override
标注:
open class Base {
open fun v() {}
fun notopenv() {}
}
class Derived : Base() {
override fun v() {}
// override fun notopenv(){}// 'notopenv' in 'Base' is final, so it cannot be overridden
// fun notopenv() {} // 這樣寫也是不允許的
}
Derived.v()函數上必須加上
override
标注。如果沒寫,編譯器将會報錯。
如果父類的這個函數
open fun v() {}
沒有标注
open
,則子類中不允許定義同名函數,不論加不加
override
成員标記為override的本身是開放的,也就是說,它可以在子類中重寫。如果你想禁止重寫,使用final關鍵字:
open class AnotherDerived() : Base() {
final override fun v() {}
}
3.9 使用伴生對象聲明靜态類和方法
Kotlin中的伴生對象(companion objects),應用的場景就是Java或C#單例類。伴生對象裡面的函數,對應的就是靜态方法。
代碼示例:
java代碼
package com.restfeel.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@Configuration
@PropertySource(value = {"classpath:common.properties"})
public class PropertyConfig {
public PropertyConfig() {}
@Bean
public static PropertySourcesPlaceholderConfigurer myPropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile({"devlopment", "default"})
@PropertySource(value = {"classpath:env-development.properties"})
static class Dev {
}
/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile("test")
@PropertySource(value = {"classpath:env-test.properties"})
static class Test {
}
/**
* Properties to support the 'production' mode of operation.
*/
@Configuration
@Profile("production")
@PropertySource(value = {"classpath:env-production.properties"})
static class Production {
// Define additional beans for this profile here
}
}
對應的
kotlin代碼
:
package com.restfeel.config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.context.annotation.PropertySource
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer
/**
* Created by jack on 2017/3/29.
*/
@Configuration
@PropertySource(value = *arrayOf("classpath:common.properties"))
class ApplicationConfig {
@Bean
fun myPropertySourcesPlaceholderConfigurer(): PropertySourcesPlaceholderConfigurer {
return PropertySourcesPlaceholderConfigurer();
}
//靜态類,伴生對象
companion object {
/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile(*arrayOf("devlopment", "default"))
@PropertySource(value = *arrayOf("classpath:env-development.properties"))
class Dev {
}
/**
* Properties to support the 'test' mode of operation.
*/
@Configuration
@Profile("test")
@PropertySource(value = *arrayOf("classpath:env-test.properties"))
class Test {
}
/**
* Properties to support the 'production' mode of operation.
*/
@Configuration
@Profile("production")
@PropertySource(value = *arrayOf("classpath:env-production.properties"))
class Production {
// Define additional beans for this profile here
}
}
}
//靜态類,伴生對象
companion object {}
來聲明靜态類和方法。
3.10 嵌套類Nested Class
類可以嵌套在其他類中:
class Outer {
private val bar: Int = 1
class Nested {
fun foo1() = 2
//fun foo11() = bar// 通路不到
}
}
fun main(args: Array<String>) {
val nestedDemo = Outer.Nested().foo1() // 2
println(nestedDemo)
}
但是,這兩個類
Outer
Nested
對象成員之間不能直接通路。比如說,
class Nested {
fun foo1() = 2
//fun foo11() = bar// 通路不到
}
Nested
裡面的函數
foo11
是不能直接使用
Outer
的成員變量
bar
的。要想通路外部類的成員變量,我們可以使用内部類。
3.11 内部類Inner Class
類可以标記為
inner
以便能夠通路外部類的成員。内部類會帶有一個對外部類的對象的引用:
class Outer {
private val bar: Int = 1
class Nested {
fun foo1() = 2
//fun foo11() = bar// 通路不到
}
inner class Inner {
fun foo2() = bar // inner class 能夠通路外部類的成員
}
}
fun main(args: Array<String>) {
val nestedDemo = Outer.Nested().foo1() // 2
val innerDemo = Outer().Inner().foo2() // 1
println(nestedDemo)
println(innerDemo)
}
3.12 使用Kotlin的對象表達式建立匿名内部類
Kotlin使用對象表達式建立匿名内部類執行個體:
window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}
override fun mouseEntered(e: MouseEvent) {
// ……
}
})
如果對象是函數式 Java 接口(即具有單個抽象方法的 Java 接口)的執行個體,
我們可以直接使用lambda表達式建立它:
val listener = ActionListener { println("clicked") }
多個超類型可以由跟在冒号後面的逗号分隔的清單指定:
open class AA(x: Int) {
open val y: Int = x
}
interface BB {}
val ab: AA = object : AA(1), BB {
override val y = 100
}
如果我們隻需要“一個對象”,那麼我們可以簡單地寫:
fun adHocf() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
println("adHoc.x + adHoc.y = " + (adHoc.x + adHoc.y))
}
package com.easy.kotlin
/**
* Created by jack on 2017/5/30.
*/
class Outer {
private val bar: Int = 1
class Nested {
fun foo1() = 2
//fun foo11() = bar// 通路不到
}
inner class Inner {
fun foo2() = bar // inner class 能夠通路外部類的成員
}
}
open class AA(x: Int) {
open val y: Int = x
}
interface BB {}
val ab: AA = object : AA(1), BB {
override val y = 100
}
class Hoc{
fun adHocf() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
println("adHoc.x + adHoc.y = " + (adHoc.x + adHoc.y))
}
}
fun main(args: Array<String>) {
val nestedDemo = Outer.Nested().foo1()
val innerDemo = Outer().Inner().foo2()
println(nestedDemo)// 2
println(innerDemo)// 1
val h = Hoc()
println(h.adHocf())
val a = AA(1)
println(a.y) //1
println(ab.y) // 15
}
3.13 資料類data class
data 修飾的類稱之為資料類。它通常用在我們寫的一些 POJO 類上。
data class User(val name: String, val age: Int)
這個對應我們寫的Java的Bean類。這些類有如下基本方法:
- equals()/ hashCode() 函數
- toString() 預設格式是: "User(name=John, age=42)",
- componentN() 函數 corresponding to the properties in their order of declaration,
- copy() 函數
其中,componentN()函數是通過屬性位置來直接通路屬性值。
package com.easy.kotlin
/**
* Created by jack on 2017/5/30.
*/
data class User(val name: String, val id: Int, val password: String)
fun main(args: Array<String>) {
val user = getUser()
println("name = ${user.name}, id = ${user.id}, password = ${user.password}")
val (name, id) = getUser()
println("name = $name, id = $id")
println("name = ${getUser().component1()}, id = ${getUser().component2()}")
}
fun getUser(): User {
return User("Alex", 1, "123456")
}
資料庫實體類bean例子
package jason.chen.mini_springboot.restful.entity
import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
@Entity
class Customer(
val firstName: String,
val lastName: String,
val gmtCreated: Date,
val gmtModified: Date,
val isDeleted: Int, //1 Yes 0 No
val deletedDate:Date,
@Id @GeneratedValue(strategy = GenerationType.AUTO)
val id: Long = -1) {
override fun toString(): String {
return "Customer(firstName='$firstName', lastName='$lastName', gmtCreated=$gmtCreated, gmtModified=$gmtModified, isDeleted=$isDeleted, deletedDate=$deletedDate, id=$id)"
}
}
data class Shop(val name: String, val customers: List<Customer>)
data class Customer(val name: String, val city: City, val orders: List<Order>) {
override fun toString() = "$name from ${city.name}"
}
data class Order(val products: List<Product>, val isDelivered: Boolean)
data class Product(val name: String, val price: Double) {
override fun toString() = "'$name' for $price"
}
data class City(val name: String) {
override fun toString() = name
}
3.14 建造者模式:建構一個對象
定義Rectangle對象,代碼如下:
package geometry.shapes
import java.util.Random
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}
fun createRandomRectangle(): Rectangle {
val random = Random()
return Rectangle(random.nextInt(), random.nextInt())
}
3.15 封裝一個日期工具類
package jason.chen.mini_springboot.restful.utils
import java.text.SimpleDateFormat
import java.util.*
/**
* Created by jack on 2017/3/11.
* @author jack
* @date 2017/03/11
*
* val date = Date()
date + 1 //後一天
date - 1 //前一天
date + Month(2) //後2月
date - Year(3) //前3年
date++ //本月的最後一天
date-- //本月的第一天
取年月日時分秒 date[0] date[1] date[2] 。。。
//日期比較
if( date1 > date2){
}
*/
enum class DateOptUnit {
YEAR,MONTH,DATE;
fun parseType():Int{
var value = Calendar.DATE
when(this){
YEAR -> value = Calendar.DATE
MONTH -> value = Calendar.MONTH
DATE -> value = Calendar.DATE
}
return value
}
}
data class DateOperator(val unit :DateOptUnit,val value: Int)
fun Any.year(value:Int):DateOperator {
return DateOperator(DateOptUnit.YEAR,value)
}
fun Any.month(value:Int):DateOperator {
return DateOperator(DateOptUnit.MONTH,value)
}
fun Any.day(value:Int):DateOperator {
return DateOperator(DateOptUnit.DATE,value)
}
/**
* date+1
* 往後的幾天
*/
operator fun Date.plus(nextVal:Int):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(Calendar.DATE, nextVal)
return calendar.time
}
/**
* date-1
*/
operator fun Date.minus(nextVal:Int):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(Calendar.DATE, nextVal*-1)
return calendar.time
}
/**
* date+year(3)
* 往後的幾天
*/
operator fun Date.plus(nextVal:DateOperator):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(nextVal.unit.parseType(), nextVal.value)
return calendar.time
}
/**
* date-month(4)
*/
operator fun Date.minus(nextVal:DateOperator):Date{
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(nextVal.unit.parseType(), nextVal.value*-1)
return calendar.time
}
/**
* 得到月末
*/
operator fun Date.inc():Date {
val calendar = GregorianCalendar()
calendar.time = this
calendar.add(Calendar.MONTH, 1);
calendar.set(Calendar.DAY_OF_MONTH, 0);
return calendar.time
}
/**
* 得到月初
*/
operator fun Date.dec():Date {
val calendar = GregorianCalendar()
calendar.time = this
calendar.set(Calendar.DAY_OF_MONTH, 1)
return calendar.time
}
/**
* 取 年月日時分秒 0 - 5
* 例如 2015-12-21 22:15:56
* date[0]:2015 date[1]:12 date[2]:21
*/
operator fun Date.get(position:Int):Int {
val calendar = GregorianCalendar()
calendar.time = this
var value = 0
when(position) {
0 -> value = calendar.get(Calendar.YEAR)
1 -> value = calendar.get(Calendar.MONTH)+1
2 -> value = calendar.get(Calendar.DAY_OF_MONTH)
3 -> value = calendar.get(Calendar.HOUR)
4 -> value = calendar.get(Calendar.MINUTE)
5 -> value = calendar.get(Calendar.SECOND)
}
return value
}
/**
* 比較2個日期
* if(date1 > date2) {
* }
*/
operator fun Date.compareTo(compareDate : Date):Int {
return (time - compareDate.time).toInt()
}
/**
* 日期轉化為字元串
*/
fun Date.stringFormat(formatType:String):String{
return SimpleDateFormat(formatType).format(this)
}
3.16 枚舉類
在 Kotlin 中,每個枚舉常量都是一個對象。枚舉常量用逗号分隔。 代碼示例:
package jason.chen.mini_springboot.restful.config
/**
* Created by jack on 2017/6/1.
*/
enum class KotlinBin(val binPath: String) {
KOTLINC("src/main/resources/kotlinc/bin/kotlinc "),
KOTLIN("src/main/resources/kotlinc/bin/kotlin ")
}
代碼這樣調用
package jason.chen.mini_springboot.restful.service
import jason.chen.mini_springboot.restful.config.KotlinBin
import org.springframework.stereotype.Service
import java.io.File
/**
* Created by jack on 2017/5/31.
*/
@Service
class KotlincService {
fun kotlinc(ktFile:String){
val file = File(".")
file.listFiles().forEach(::println)
val kotlinc = KotlinBin.KOTLINC.binPath + ktFile
val runtime: Runtime = Runtime.getRuntime()
val process: Process = runtime.exec(kotlinc)
val exitValue = process.waitFor()
if (exitValue != 0) {
println("exit with $exitValue")
return
}
process.inputStream.bufferedReader().lines().forEach {
println(it)
}
}
fun kotlin(ktFile:String):String{
kotlinc(ktFile)
val ktClass = ktFile.substring(0, ktFile.indexOf(".kt")) + "Kt"
val kotlin = KotlinBin.KOTLIN.binPath + ktClass
val runtime: Runtime = Runtime.getRuntime()
val process: Process = runtime.exec(kotlin)
val exitValue = process.waitFor()
if (exitValue != 0) {
println("exit with $exitValue")
return "exit with $exitValue"
}
var result=""
process.inputStream.bufferedReader().lines().forEach {
println(it)
result+=it
}
return result
}
}
3.17 sealed 密封類
sealed 修飾的類稱為密封類,用來表示受限的類層次結構。例如當一個值為有限集中的 類型、而不能有任何其他類型時。在某種意義上,他們是枚舉類的擴充:枚舉類型的值集合也是受限的,但每個枚舉常量隻存在一個執行個體,而密封類的一個子類可以有可包含狀态的多個執行個體。
小結
參考文檔:
https://github.com/kymjs/KotlinDoc-cn/blob/master/unit3/ClassesInheritance.md