1、擴充方法
實際就是繼承=>派生的意思
被繼承需要使用open關鍵字修飾
使用類名.方法名的方式為父類擴充方法,這樣父類和子類都可以共享該方法
open class Base{
fun test(){
println("這是基類的方法")
}
}
class Sub:Base(){
fun ext(){
println("這是子類的方法")
}
}
//為基類擴充一個方法
fun Base.info(){
println("擴充出來的方法")
}
fun main(args:Array<String>){
var base=Base()
base.test()
var son=Sub()
son.ext()
base.info()
son.info()
}
列印
這是基類的方法
這是子類的方法
擴充出來的方法
擴充出來的方法
2、擴充的實作機制
擴充實際就是調用了定義的擴充方法
成員方法執行動态解析(由運作時類型決定);擴充方法執行靜态解析(由編譯類型決定)
3、為可空類型擴充方法
允許對可空類型擴充方法,由于可空類型允許接收null值,這樣使得null值也可調用該擴充方法
fun Any?.equals(other:Any?):Boolean{
if(this==null){
return if (other==null) true else false
}
return other.equals(other)
}
fun main(args:Array<String>){
var a=null
println(a.equals(null))
println(a.equals("kotlin"))
}
列印:
true
false
4、擴充屬性
擴充屬性實際上市通過添加getter和setter方法實作的,擴充的屬性隻能是計算屬性
- 擴充屬性不能有初始值
- 不能用field關鍵字顯示通路幕後字段
- 擴充隻讀屬性必須提供getter方法,擴充讀寫屬性必須提供getter和setter方法
class User(var firstname:String, var lastname:String){}
//為User擴充屬性
var User.fullname:String
get()="${firstname}.${lastname}"
set(value){
var tokens=value.split(".")
firstname=tokens[0]
lastname=tokens[1]
}
fun main(args:Array<String>){
var user=User("悟空","孫")
println(user.fullname)
user.fullname="八戒.豬"
println(user.firstname)
println(user.lastname)
}
列印:
悟空.孫
八戒
豬
5、以成員方式定義擴充
以類成員的方式定義擴充–就向為類定義方法、屬性哪樣定義擴充
class A{
fun bar()= println("A的bar方法")
}
class B{
fun baz()= println("B的baz方法")
//以成員方法為A擴充foo()方法
fun A.foo(){
//在該擴充方法裡面,既可以調用類A的成員,也可調用類B的成員
bar()
baz()
}
fun test(target:A) {
//調用A對象的成員方法
target.bar()
//調用A對象的擴充方法
target.foo()
}
}
fun main(args:Array<String>){
var b=B()
b.test(A())
}
列印:
A的bar方法
A的bar方法
B的baz方法
程式在擴充方法中調用兩個類都包含方法的時候,系統總是優先調用被擴充類的方法,為了讓系統調用擴充定義所在類的方法,必須使用帶标簽的this進行限定:[email protected]
class Tiger{
fun foo(){
println("Tiger類的foo()方法")
}
}
class Bear{
fun foo(){
println("Bear類的foo()方法")
}
//以成員方式為Tiger類擴充test方法
fun Tiger.test(){
//同名函數調用時,優先調用被擴充類的test()方法
foo()
//使用帶标簽的this指定Bear的foo()方法
[email protected]()
}
fun info(tiger: Tiger){
tiger.test()
}
}
fun main(args:Array<String>){
var b=Bear()
b.info(Tiger())
}
列印:
Tiger類的foo()方法
Bear類的foo()方法
6、帶接收者的匿名函數
kotlin制程為類擴充匿名函數,該擴充函數所屬的類也是該函數的接收者。這種匿名函數稱之為“帶接收者的匿名函數”
val factorial=fun Int.():Int{
//該匿名函數的接收者是Int對象
//是以在該匿名函數中,this代表調用該匿名函數的Int對象
if (this<0){
return -1
}else if (this==1){
return 1
}else{
var result=1
for (i in 1..this){
result*=i
}
return result
}
}
fun main(args:Array<String>){
println(6.factorial())
}
列印
720
與普通函數想類似的是,帶接收者的匿名函數也有自身的類型,即帶接收者的函數類型。如上例factorial變量的類型為:Int.()->Int
class HTML{
fun body(){
println("<body></body>")
}
fun head(){
println("<head></head")
}
}
//定義一個類型為HTML.()->Int的形參(帶接收者的匿名函數)
//這樣在函數中HTML對象就增加了一個init方法
fun html(init:HTML.()->Unit){
println("<html>")
val html=HTML()//建立接收者對象
html.init()
println("</html>")
}
fun main(args:Array<String>){
//調用html函數,需要傳入HTML.()->Unit類型的參數
//此時系統可推斷出接收者的類型,故可以用lambda表達式代替匿名函數
html{
//lambda表達式中的this就是該方法的調用者
head()
body()
}
}
列印:
<html>
<head></head
<body></body>
</html>
7、何時使用擴充
兩個作用
- 擴充可動态地為已有的類添加方法或屬性
- 擴充能以更好的形式組織一些工具方法
8、可執行“宏替換”的常量
kotlin的final修飾符不能修飾局部變量,是以open自然也不能修飾局部變量
kotlin不允許使用final定義“宏變量”
“宏替換”的常量除使用const修飾之外,還必須滿足如下條件
- 位于頂層或者是對象表達式的成員
- 初始值為基本類型值或者字元串字面值
- 沒有自定義的getter方法
const val MAX_AGE=100
fun main(args:Array<String>){
println(MAX_AGE)
}
列印:
100
9、final屬性
final表示該屬性不能被重寫,為所有的屬性自動添加final修飾
10、final方法
final表示該方法不能被重寫,為所有的方法自動添加final修飾
11、final類
用final修飾的類不可以有子類,為所有類自動添加final修飾
12、不可變類(immutable)
不可變類的意思是建立該類的執行個體後,該執行個體的屬性值是不可改變的
如果需要建立自定義的不可變類,可遵循如下規則
- 提供帶參的構造器,用于根據傳入的參數來初始化類中的屬性
-
定義使用final修飾的隻讀屬性,避免程式通過setter方法改變屬性值
如果有必要,則重寫Any類的hashCode()和equals()方法,equals方法将關鍵屬性作為兩個對象是否相等的标準。除此之外,還應保證兩個用equals()方法判斷為相等的對象的hashcode()也相等
當建立不可變類時,如果她包含的成員屬性的類型是可變的,那麼其對象的屬性值依然是可變的。
class Name(var firstName:String="",var lastName:String=""){//兩個形參(屬性)是可變的
}
class Person(val name:Name){//兩個形參是不可變的
}
fun main(args:Array<String>){
val n=Name("悟空","孫")
var p=Person(n)
//Person對象的name的fistname為“悟空”
println(p.name.firstName)//連續傳遞對象及其屬性過來
//改變Person對象的name的firstname值
n.firstName="八戒"
//Person對象的name的firstname被改為“八戒”,因為Name中的兩個屬性實際上是可變的
println(p.name.firstName)//連續傳遞對象及其屬性過來
}
列印:
悟空
八戒
為了保持Person對象的不可變性,必須好保護好Person對象的應用類型的屬性:name,讓程式無法通路到Person對象的name屬性的幕後變量,也就無法利用name屬性的可變性來改變Person對象了
13、抽象成員和抽象類
使用abstract修飾的成員,無須使用open修飾。
當使用abstract修飾方法、屬性時,說明該類需要被繼承,方式屬性需要被子類重寫。
包含抽象成員的類隻能定義成抽象類,抽象類中可以沒有抽象成員
抽象方法和抽象類的規則
- 類和抽象成員必須使用abstract來修飾
- 抽象類不能被執行個體化
- 抽象類可以包含屬性、方法(普通方法和抽象方法都可以)、構造器、初始化塊、嵌套類(接口、枚舉)成員。抽象類的構造器不能用于建立執行個體,隻能被其子類調用
- 含有抽象成員的類隻能被定義成抽象類。
abstract class Shape{
init {
println("執行Shape抽象類的方法")
}
var color=""
//定義一個周長的抽象方法
abstract fun calPerimete():Double
//定義一個代表形狀的抽象的隻讀屬性,抽象屬性不需要初始值
abstract val type:String
//定義shape的構造器,該構造器并不是用于建立shape對象的,而是用于被子類調用
constructor()
constructor(color:String){
println("執行Shape的構造器")
this.color=color
}
}
//定義三角形的三遍
class Triangle(color: String,var a:Double,var b:Double,var c:Double):Shape(color){
fun setSides(a:Double,b:Double,c:Double){
if(a>=b+c||b>a+c ||c>=a+b){
println("三角形兩邊之和必須大于第三邊")
return
}
this.a=a
this.b=b
this.c=c
}
//重寫shape類計算周長的方法
override fun calPerimete(): Double {
return a+b+c
}
//重寫shape類代表形狀的抽象屬性
override val type:String="三角形"
}
//定義一個圓
class Circle(color: String,var radius:Double):Shape(color){
//重寫shape類計算周長的方法
override fun calPerimete(): Double {
return 2*Math.PI*radius
}
//重寫shape類代表形狀的抽象屬性
override val type:String="圓形"
}
fun main(args:Array<String>){
var s1:Shape=Triangle("黑色",3.0,4.0,5.0)
var s2:Shape=Circle("黃色",3.0)
println(s1.type)
println(s1.calPerimete())
println(s2.type)
println(s2.calPerimete())
}
列印:
執行Shape抽象類的方法
執行Shape的構造器
執行Shape抽象類的方法
執行Shape的構造器
三角形
12.0
圓形
18.84955592153876
14、抽象類的作用
從多個具有相同特征的類中抽象出一個抽象類,以這個抽象類作為子類的模闆,進而避免了子類設計的随意性。
抽象類是一種模闆模式的的設計,抽象類作為多個子類的通用模闆,子類在抽象類的基礎上進行擴充、改造,但子類總體上會大緻保留抽象類的行為方式
//定義帶轉速屬性的主構造器
abstract class SpeedMeter(var turnRate:Double){
//把傳回車輪半徑的方法定義成抽象方法
abstract fun calGrith():Double
//定義計算速度的通用算法
fun getSpeed():Double{
//速度等于車輪周長*轉速
return calGrith()*turnRate
}
}
//這裡抽象方法中無法定義倫則的周長(實際是輪子的半徑)
public class CarSpeedMeter(var radius:Double):SpeedMeter(0.0){
override fun calGrith(): Double {
return radius*2*Math.PI
}
}
fun main(args:Array<String>){
val csm=CarSpeedMeter(0.28)
csm.turnRate=15.0
println(csm.getSpeed())
}
列印:
26.389378290154266
SpeedMeter類提供了車速表的通用算法,但是具體到實作的細節,則推遲到其子類CarSpeedMeter類中實作,這是一種典型的模闆模式
模闆模式的一些簡單規則
- 抽象父類可以隻定義需要使用的某些方式,把不能實作的部分抽象成抽象方法,留給子類去實作
- 父類中可能包含需要調用其他系列方法的方法,這些被調用方法既可以由父類實作,也可以由其子類實作。父類中提供的方法隻是定義了一個通用算法,其實作也許并不完全由自身來完成,而必須依賴其子類的輔助
15、密封類
密封類是 一種特殊的抽象類,專門用于派生子類。
密封類使用sealed關鍵字
密封類與普通抽象類的差別在于
- 密封類的子類是固定的,密封類的子類必須與密封類本身在同一個檔案中,在其他檔案中則不能為密封類派生子類,這樣就限制了在其他檔案中派生子類
- 密封類的所有構造方法都是private的,自動添加
- 密封類的直接子類必須在同一個檔案中,但是間接子類不需要在同一個檔案中
- 使用密封類的好處是密封類的子類數量是有限的(在同一個檔案中不可能擁有太多直接子類)
//定義一個密封類,其實就是抽象類
sealed class Apple{
abstract fun taste()
}
open class RedFuJi:Apple(){
override fun taste() {
println("紅富士蘋果香甜可口")
}
}
data class Gala(var weight:Double):Apple(){
override fun taste() {
println("嘎啦果更清脆,重量為:${weight}")
}
}
fun main(args:Array<String>){
//使用Apple聲明變量,用子類執行個體指派
var ap1:Apple=RedFuJi()
var ap2:Apple=Gala(2.3)
ap1.taste()
ap2.taste()
}
列印
紅富士蘋果香甜可口
嘎啦果更清脆,重量為:2.3
16、接口的定義
與java的接口非常相識
基本文法如下
【修飾符】interface 接口名:父接口1,父接口2...{
零到多個屬性定義
零到多個方法定義
零到多個嵌套類,嵌套接口,嵌套枚舉定義
}
- 接口隻能繼承接口不能繼承類
- 預設采用public ,可以采用public、internal、private修飾
- 接口定義的是一種規範,是以接口中不能包含構造器和初始化塊定義,
- 接口中定義的方法既可以是抽象方法,也可以是非抽象方法,如果一個方法沒有方法體,或一直隻讀屬性沒有定義getter方法,或者一個讀寫屬性沒有定義getter方法和setter方法,則該屬性或方法為抽象的
- 需要被實作類重寫的方法,用public修飾,預設也是public
- 不需要被實作類重寫的方法可以用public也可以用private修飾
interface Outputable{
//隻讀屬性定義了getter方法,非抽象屬性
val name:String
get()="輸出裝置"
//隻讀屬性沒有定義getter方法,抽象屬性
val brand:String
//讀寫屬性沒有定義getter、setter方法,抽象屬性
var category:String
//接口定義的抽象方法
fun out()
fun getData(msg:String)
//在接口中定義的非抽象方法,可使用private修飾
fun print(vararg msgs:String){
for (msg in msgs){
println(msg)
}
}
//在接口中定義的非抽象方法,可使用private修飾
fun test(){
println("接口中test()方法")
}
}
17、接口的繼承
接口支援多繼承:一個接口可以有多個直接父接口,并獲得父接口中定義的所有方法、屬性
多繼承接口排在:後面,用逗号,隔開
interface InterfaceA{
val propA:Int
get()=5
fun testA()
}
interface InterfaceB{
val propB:Int
get() = 6
fun testB()
}
interface interfaceC:InterfaceA,InterfaceB{
val propC:Int
get() = 7
fun testC()
}
18、使用接口
接口不能用于建立執行個體,但可以用于聲明變量,當使用接口來聲明變量時,這個引用類型的變量必須引用到其實作類的對象。
接口的主要用途
- 就是被實作類實作
- 定義變量,也可以用于強制類型轉換
- 被其他類實作
一個類可以實作多個接口,直接将被實作的多個接口、父類放在英文冒号之後,且父類、接口之間沒有順序要求,隻需要用英文逗号隔開即可
interface Outputable{
//隻讀屬性定義了getter方法,非抽象屬性
val name:String
get()="輸出裝置"
//隻讀屬性沒有定義getter方法,抽象屬性
val brand:String
//讀寫屬性沒有定義getter、setter方法,抽象屬性
var category:String
//接口定義的抽象方法
fun out()
fun getData(msg:String)
//在接口中定義的非抽象方法,可使用private修飾
fun print(vararg msgs:String){
for (msg in msgs){
println(msg)
}
}
//在接口中定義的非抽象方法,可使用private修飾
fun test(){
println("接口中test()方法")
}
}
interface Product{
fun getProductTime():Int
}
const val MAX_CACHE_LINE=10
//讓printer類實作Outputable和Product接口
class Printer:Outputable,Product{
private val printData=Array<String>(MAX_CACHE_LINE,{""})
//用以記錄目前需列印的作業數
private var dataNum=0
override val brand:String="HP"
//重寫接口的抽象隻讀屬性
override var category:String="輸出外設"
override fun out() {
//隻要還有作業就一直列印
while (dataNum>0){
println("列印機列印:"+printData[0])
//把作業隊列整體前移一位,并将剩下的作業數減1
System.arraycopy(printData,1,printData,0,--dataNum)
}
}
override fun getData(msg: String) {
if (dataNum>=MAX_CACHE_LINE){
println("輸出隊列已滿,添加失敗")
}else{
//把列印資料添加到隊列裡,已儲存資料的數量加1
printData[dataNum++]=msg
}
}
override fun getProductTime(): Int {
return 45
}
}
fun main(args:Array<String>){
//建立一個Printer對象,當成Output使用
var o:Outputable=Printer()
o.getData("輕量級javaEE企業應用實戰")
o.getData("瘋狂java講義")
o.out()
o.getData("瘋狂安卓講義")
o.getData("瘋狂ajax講義")
o.out()
//調用Outputable接口中定義的非抽象方法
o.print("孫悟空","豬八戒","白骨精")
o.test()
//建立一個printer對象,當成product使用
val p:Printer=Printer()
println(p.getProductTime())
//所有接口類型的應用變量都可以直接指派給Any類型的變量
val obj:Any=p
}
列印:
列印機列印:輕量級javaEE企業應用實戰
列印機列印:瘋狂java講義
列印機列印:瘋狂安卓講義
列印機列印:瘋狂ajax講義
孫悟空
豬八戒
白骨精
接口中test()方法
45
19、接口和抽象類
同java。一樣一樣的