《Kotlin实战》-第04章:类、对象和接口

第四章 类、对象和接口

4.1 定义类继承结构
4.1.1 Kotlin中的接口

1.概念说明
1.1 接口声明

  • Kotlin接口用interface关键字声明
  • Kotlin接口可以有抽象方法,但也允许不存在抽象方法。
  • Kotlin接口可以带默认实现的方法

1.2 类的实现

  • 一个类可以实现多个接口,但只能继承一个类
  • override修饰符用来标注被重写的父类或者接口的方法或属性,且是强制的。
  • 接口里带默认实现的方法在类中可以省略不管,但当实现两个接口中有相同实现方法名称时,则必须重新实现。
    • 可以调用一个接口中的的实现,形式是super<接口名>.方法名
    • Java中是接口名.super.方法名

2.示例:

//接口声明
interface Clickable{
    fun click()
    fun showOff() = println("I'm clickable")
}
interface Focusable{
    fun showOff() = println("I'mFocusable")
}
//实现
class Button:Clickable,Focuseable{
    override fun click() = println("I was clicked")
    override fun showOff(){
        super<Clickable>.showOff()
        super<Focuseable>.showOff()
    }
}
//调用
val button = Button()
button.click()
button.showOff()
4.1.2 open、final和abstract修饰符:默认为final

1.概念说明

  • Kotlin中的类和方法默认是final的,Java中则是默认open的。
    • 默认final有利于规避脆弱基类问题和方便于智能转换中的类型判定
  • 用open修饰的话就可以允许创建子类或者重写方法属性。
  • 接口中的成员始终是open的 ,不能将其声明为final,否则声明接口没有意义。
  • 如果重写了基类或者接口中的成员,重写的成员默认是open。当然也可以显式地将重写成员标注为final来禁止重写。
  • abstract修饰类的时候,类就不能被实例化。用来修饰类中的成员时,则成员始终是open的。

2.示例

open class RichButton:Clickable{ //这个类可以被其他类继承,去掉open则不能
    fun disable(){}    //函数是final的,不能被重写
    open fun animate(){}     //可以被重写
    override fun click(){}    //重写函数默认是open的,可以被重写
    final override fun click2(){}    //不能被重写
}

abstract class Animated{    //不能被实例化
    abstract fun animate()    //必须被子类重写
    open fun stopAnimate(){}    //可以被重写
    fun animateTwice(){}    //默认final,不能被重写
}
4.1.3 可见性修饰符:默认为public

1.Kotlin的可见性修饰符

  • public:默认。公开,可见性最大,哪里都可以引用。
  • internal:内部,仅对模块内可见。
    • 一个模块就是一组一起编译的Koltin文件。可能是一个IDEA模块、一个AS项目等。
    • 优势在于提供了对模块实现细节的真正封装。
  • protected:保护,相当于 private + 子类可见。
  • private:私有,可见性最小,根据声明位置不同可分为类中可见和文件中可见。

2.Java:可见性修饰符

  • public:公开,可见性最大,哪里都可以引用。
  • protected: 表示包内可见 + 子类可见
  • default:包内可见,只有在同一个 package 内可以引用
  • private :类中可见,作为内部类时对外部类「可见」

3.一个通用规则:类的基础类型和类型参数列表中用到的所有类,或者函数的签名,
都有与这个类或者函数本身相同的可见性。或者说高可见性的类不能引用低可见性的类。

4.1.4 内部类和嵌套类:默认是嵌套类

Kotlin可以在一个类中声明另一个类,但这个嵌套类不能访问外部类的实例。
原因是Kotlin中没有显式修饰符的嵌套类默认与Java中的static嵌套类是一样的。但注意不能直接按Java中调用static的形式去调用内部类的方法属性。
如果需要嵌套类来引用外部类的话,需要用inner修饰符,引入语法为this@外部类名

//声明
class Outer{
    inner class Inner{//没有inner的话不能引用
        fun getOuterRef():Outer = this@Outer
        fun innerFun1() = println("this is inner fun1")
    }
}
//调用
val inner = Outer.Inner()
inner.innerFun1()
//下面这种调用是错误的
Outer.Inner.innerFun1()
4.1.5 密封类:定义受限的类继承结构

sealed修饰符:修饰的类对可能创建的子类做出严格限制,所有子类必须嵌套在父类中。

  • sealed修饰的类只有private构造方法。因此不能用于接口。
  • sealed修饰的类隐含这个类是一个open类,可省略open修饰符。

示例:

sealed class Expr{
    class Num(val value:Int):Expr()
    class Sum(val left:Expr,val right:Expr):Expr()     
}
//调用,可以省略when结构中的else了
fun eval(e:Expr):Int = when(e){
    is Expr.Num -> e.value
    is Expr.Sum -> eval(e.right) + eval(e.left)    
}

4.2 声明一个带非默认结构方法或属性的类
4.2.1 初始化类:主构造方法和初始化语句块
  • 创建类的实例时,不需要new关键字
  • 主构造方法:声明类时,constructor(可省略)修饰的放在类名后的部分
  • 初始化语句块:声明类时,类中init修饰的代码块。会在类被创建时执行。
//完整版本
class User constructor(_nickname:String){//带一个参数的主构造方法
    val nickname:String
    init{//初始化代码块
        nickname = _nickname
        //this.nickname = _nickname也可以
    }
}
  • 如果主构造方法没有注解或可见性修饰符,可以去掉constructor关键字。
//简化contructor关键字,代码块用属性
class User(_nickname:String){
    val nickname = _nickname
}
  • 如果属性用相应的构造方法参数类初始化,可以通过把val等关键字加在参数前进行简化。

class User(val nickname:String)//val意味着相应的属性会用改造方法的参数来初始化

  • 可以在构造方法里为参数声明默认值
    • 如果所有构造方法参数都有默认值,编译器会生成一个额外的空构造方法来使用所有的默认值。

class User(val nickname:String = "hehe")

  • 如果没有给一个类声明任何构造方法,将会生成一个不做任何事的默认构造方法

open class Button

  • 当有父类时,主构造方法同样需要初始化父类。
    • 父类名称后面需要一个括号,这是父类构造方法
    • 实现接口时,不需要在接口名称后面加括号,因为接口没有构造方法

class TwitterUser(nickname:String):User(nickname){ ...... }

  • 当把构造方法标记为private时,类就不能被实例化了

class Secretive private constructor(){}

4.2.2 从构造方法:用不同的方式来初始化父类

基本和Java的构造方法一样了

  • 从构造方法:声明类时,在类中的contructor方法。
    • 主构造方法只能有一个,从构造方法可以有多个
open class View{
    constructor(ctx:Context){ ..... }
    constructor(ctx:Context,attr:AttributeSet){ ..... }
}
  • 继承这个类时,也可以用super()调用父类的构造方法,this()调用自己的构造方法
class MyButton:View{
    contructor(ctx:Context):this(ctx,MY_STYLE){ ..... }
    contructor(ctx:Context,attr:AttributeSet):super(ctx,attr){ ..... }
}
4.2.3 实现在接口中声明的属性

以代码为例演示如何实现接口中的属性。

interface User{
    val nickname:String
}

主构造方法实现
class PrivateUser(override val nickname:String):User

从构造方法实现

class SubUser(val email:String):User{
    override val nickname:String
        get() = email.substringBefore('@') //每次调用nickname时都要调用这个函数
}
class FaceUser(val accountId:Int):User{
    override val nickname = getFaceName(accountId)//有支持字段,只会执行函数一次
}
4.2.4 通过getter或setter访问支持字段

只有var变量有setter函数。
setter函数体中,可以用field来访问支持字段的值。
getter只能读取值,setter既能读取也能修改。
只有当显式地引用或者使用默认的访问器,属性才会有支持字段。
示例:

class User(val name:String){
    var address:String = "haha"
        set(value:String){
            println("$name--$field--$value")
            filed = value
        }
}
4.2.5 修改访问器的可见性

可以通过在get或者set关键字前放置可见性修饰符来修改属性的访问器可见性。
当set私有后就不能在类外部修改这个属性,但还可以在类内部修改。
示例:

class LengthCouter{
    var counter:Int = 0
        private set
    fun addWord(word:String){
        counter += word.length
    }
}
4.3 编译器生成的方法:数据类和类委托
4.3.1 通用对象方法

本节只是讨论了三个非常常用的对象方法:toString(),equals(),hashCode()
tips:
1.==相等性
Java中,用来比较基本数据类型和引用类型,但基本数据类型比较的是值,引用类型比较的是引用。
引用类型比较值用equals.
Kotlin中,值相等就全用
来比较,===来比较对象引用。
2.is
Kotlin中is检查相当于Java中instanceOf,用来检查一个值是否是一个指定的类型。
!is就是is检查的非运算。

4.3.2 数据类:自动生成通用方法的实现

data关键字:修饰类的时候,就可以自动重写所有标准Java方法的类:

  • equals:用来比较示例,会将所有在主构造方法中声明的属性纳入考虑。
  • hashCode:考虑所有在主构造方法中声明的属性,用来作为例如HashMap这种基于哈希容器的键
  • toString
    虽然data修饰class时不要求主构造方法参数一定是val修饰,但还是要慎用var修饰,因为很可能var属性被修改后造成放进对象的容器的无效。
  • copy:允许复制类的实例的方法,并在复制的同时修改某些属性的值。
    模仿实现copy
class Client(val name:String,val postalCode:Int){
    fun copy(name:String = this.name,postalCode:Int = this.postalCode) = 
        Client(name,postalCode)
}
4.3.3 类委托:使用by关键字

为了优雅实现类似装饰器模式的功能,Kotlin将委托作为一个语言级别的功能做了头等支持。
使用by关键字将接口的实现委托到另一个对象。
示例:

class CoutingSet<T>(val innerSet:MutableCollection<T> = HashSet<T>())
    :MutableCollection<T> by innerSet{
//下面的代码是不使用委托时的实现,使用委托的话,就不需要写下面的代码了
    var objectsAdded = 0
    override fun add(element:T):Boolean{
        objectsAdded++
        return innerSet.add(element)
    }
    override fun addAll(c:Collection<T>):Boolean{
        objectsAdded += c.size
        return innerSet.addAll(c)
    }
}
//调用
val cset = CoutingSet<Int>()
cset.addAll(listOf(1,2,3,4))
println("${cset.size}")
4.4 object关键字:将声明一个类与创建一个实例结合起来

Kotlin中object关键字在多种情况下出现,但都是定义一个类并同时创建一个实例。

4.4.1 对象声明:创建单例易如反掌

1.概念说明

  • object+类的声明,就创建了个单一实例,也就是对象声明。
  • 对象声明时不允许有任何构造方法,因为不需要在其他地方调用,构造方法是没有意义的。
  • 对象声明允许使用对象的名加.字符的方法来调用方法和访问属性。
  • 对象声明可以继承类和接口,但一般并不包含任何状态,用来实现类似Comparator这样的接口比较合适。
  • 可以类中声明对象,这样的对象只有一个单一实例,同Java中static修饰一样。

2.示例:

//声明
object Payroll{
    val allEmployee = arrayListOf<Person>()
    fun cal(){ ... }
}
//调用
Payroll.allEmployee.add(Person(....))
Payroll.cal()

//类内声明
data class Person(val name:String){
    object NameComparator:Comparator<Person>{
        override fun compare(p1:Person,p2:Person):Int = p1.name.compareTo(p2.name)
    }
}
//调用
val persons = listOf( ...... )
println(persons.sortedWith(Person.NameComparator))
4.4.2 伴生对象:工厂方法和静态成员的地盘
  • 伴生对象:用companion object修饰的在类中定义的对象,这样的话就可以直接通过容器类名称来访问这个对象的方法和属性,不再需要显示的指定对象的名称。
    每个类中只允许最多一个伴生对象。伴生对象可以没有名称。
    示例:
//声明
class A{
    companion object{
        fun bar(){ ...... }
    }
}
//调用
A.bar()
  • 一个典型应用场景:工厂方法
class User private constructor(val nickname:String){
    companion object{
        fun newSubUser(email:String) = User(email.substringBefor('@'))
        fun newFaceUser(accountId:Int) = User(getFace(accountId))
    }
}
//调用
val subUser = User.newSubUser("aa@qq.com")

tips:
Kotlin中没有static关键字。

4.4.3 作为普通对象使用的伴生对象
  • 伴生对象可以有名字,如companion object AA
  • 伴生对象可以继承接口和类
  • 伴生对象可以有扩展函数
4.4.4 对象表达式:改变写法的匿名内部类

1.概念说明
匿名内部类:object:类名(),也可能有参数,看主构造方法。

  • 可以实现多个接口
  • 可以访问创建它的函数中的变量
  • 可以存储到变量中

2.示例

button.addOnClickListener(object:OnClickListener(){ ..... })
val listener = object:OnClickListener(){ ..... }
button.addOnClickListener(listener)
4.5 小结

特别章节

读完本篇,有几个定义概念很可能在脑子里比较浆糊:
嵌套类和内部类、object修饰类,companion object修饰类,object加:修饰的类。
首先嵌套类等于内部类,两者只是叫法不同而已。后面用内部类表示。
本章节里说的内部类和object相关的几个概念里的区别是:

  • 内部类是类的声明,object修饰相关概念是对象声明。因此内部类还可以被实例化,而object相关类则不能再被实例化了。
  • 内部类被使用时是一定要实例化的,不论在外部类的内部还是外部。被实例化后就可以正常使用它。
  • object+类名和companion object+类名,这两者几乎是一样的使用方法,只不过companion修饰后就可以直接用外部类名调用伴生对象内的方法属性。
    • 伴生对象只能有一个,object+类名则可以有多个
    • 伴生对象可以省略名称,object+类名则不能省略
    • 两者的属性方法都可以在外部类内部被调用,但不能被外部类的实例对象所调用。
    • 两者都不能直接调用外部类的方法属性。
  • object加:修饰的类比较简单,就是直接实例化一个类,不带名字而已。

代码示例:

//内部类声明
class Outer{
    class Inner{//没有inner的话不能引用
        fun innerFun1() = println("this is inner fun1")
    }
    fun useInner(){
        println("this is useInner")
        val inner = Inner()
        inner.innerFun1()
    }
 }
//内部类调用
val outer = Outer()
outer.useInner()
val inner = Outer.Inner()
inner.innerFun1()
// Outer.Inner.innerFun1()
>>>>>
this is useInner
this is inner fun1
this is inner fun1


//object+类声明
class ObjectOuter{
    object ObjectInner{
        fun innerFun1() = println("this is object inner fun1")
        fun useOuterFun(){
            //不能调用外部类的属性方法
        }
    }
    object ObjectInner2{}
   
    fun useObjectInner(){
        println("this is useObjectInner")
        ObjectInner.innerFun1()
        //innerFun1()这样直接调用是不可以的
    }
 }
//object+类调用
ObjectOuter.ObjectInner.innerFun1()
val objectOuter = ObjectOuter()
objectOuter.useObjectInner()
//objectOuter.ObjectInner.innerFun1()
>>>>>>
this is object inner fun1
this is useObjectInner
this is object inner fun1


//companion object类声明
class CompanionObjectOuter{
    companion object {
        fun companionInnerFun1() = println("this is companion object inner fun1")
    }
     
    fun useCompanionObjectInner(){
        println("this is useCompanionObjectInner")
        companionInnerFun1()
    }
 }

//companion object类调用
CompanionObjectOuter.companionInnerFun1()
val companionObjectOuter = CompanionObjectOuter()
//companionObjectOuter.companionInnerFun1()
companionObjectOuter.useCompanionObjectInner()
>>>>>>
this is companion object inner fun1
this is useCompanionObjectInner
this is companion object inner fun1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值