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。一样一样的