kotlin学习(三)

类、对象和接口

一.定义类继承结构

kotlin中的接口
使用interface关键字

interface clickable{
	fun click()
}

实现接口的方法

class Button : Clickable{
	override fun click() = println("button click")
}

可以看到kotlin使用 : 代替了extends和implements关键字实现继承和实现。和Java一样只能实现单继承和实现多个接口
override关键字在kotlin中用来标注被重写的父类方法,是强制要求的。
接口中默认方法的实现没有特殊注解,直接提供方法体即可。

interface Clickable{
	fun click()//空方法
	fun showOff() = println("default method")//默认实现的方法
}

如果两个接口中有同名的默认方法并且同时实现了这两个类,必须在子类中显式的实现该方法或者通过类名调用

interface A{
    fun test() = print("A")
}
interface B{
    fun test() = print("B")
}
class AB : A,B{
    override fun test() {
        super<A>.test()
        super<B>.test()
    }
}

<分析>由于kotlin兼容Java 6,所以并不支持接口的默认方法。因此它会把每个带默认方法的接口编译成一个普通接口和一个将方法体作为静态函数的类的结合体。

访问修饰符
kotlin中默认的修饰符是final
如果要允许被继承,则需要显式的声明open修饰符

open class RichButton : Clickable{//这个类是可继承的
	fun disable(){}//默认是final,不能在子类中重写该方法
	open fun animate(){}//可以在子类中重写
	override fun click(){}//重写了一个open的函数,因此也是open的
}

如果想阻止类中重写的函数在子类被重写,需要显式的添加final字段

open class RichButton : Clickable{
	final override fun click(){}//没有final的override意味着open
}

抽象类同样是使用abstract修饰,抽象方法也是使用abstract修饰且必须在子类中实现
接口中的成员始终是open的,不能声明为final,不能使用abstract、open或者final

类中访问修饰符的意义

修饰符相关成员评注
final不能被重写类中成员默认使用
open可以被重写需要明确的表示
abstract必须被重写只能在抽象类中使用,抽象成员不能有实现
override重写父类或者接口中的成员如果没有使用final表明,重写的成员默认是open

可见性修饰符

修饰符类成员顶层声明
public所有地方可见所有地方可见
internal模块中可见模块中可见
protected子类中可见
private类中可见文件中可见

需要注意的是internal表示一个模块(一组一起编译的kotlin文件等),提供了对模块实现细节的真正封装
protected成员只在类和他的子类中可见
类的扩展函数不能访问它的private和protected成员
<注>kotlin中的可见性修饰符在编译成字节码时,private会被编译成包私有声明,internal会变成public

内部类和嵌套类
Kotlin中默认是嵌套类,对应Java中static修饰的内部类

类A在另一个类B中声明在Java中在kotlin中
嵌套类(不存储外部类的引用)static class Aclass A
内部类(存储外部类的引用)class Ainner class A
class Outer{
	inner class Inner{
		fun getOuterReference() : Outer = this@Outer
	}
}

密封类
通过sealed关键字修饰的类,对可能创建的子类作出严格的限制。所有的直接子类必须嵌套在父类中。sealed隐含open

sealed class Father{
	class A : Father()
	class B : Father()//将所有可能的类作为嵌套类列出
}
fun test(f : Father) : Father = 
	when(f){
	is A -> A
	is B -> B//when表达式涵盖了所有可能的情况,不再需要额外的else分支
}

在when中使用sealed类并且添加一个新的子类的时候,有返回值的when表达式会编译失败

二.声明一个带非默认构造方法或属性的类

主构造方法

class User(val name : String)//被括号围起来的语句块叫做主构造方法

明确来写是如下写法

class User constructor (_name : String){//constructor用来开始一个主构造方法或构造方法的声明
	val name : String 
    init {//初始化语句块
        name = _name
    }
}
class User(_name : String){//带一个参数的主构造方法
	val name = _name//用参数来初始化属性
}

继承过程中

open class People(val name : String)
class Man(name : String) : People(name)
open class Button//没有声明参数,生成默认构造方法

如果不想类被实例化,需要给构造器添加private修饰符

class User private constructor(){}

同样的,也可以添加默认值

class User(val name :String , val isMan : Boolean = true)

具体初始化过程和Java类似

open class People(var name : String) {
    init {
        println(name + "people")
    }
    open fun one(){
        println("People")
    }
}

class Man constructor(_name : String): People("People"){
    init {
        println(name + "Man")
        name = _name
        println(name + "Man")
    }
    override fun one(){
        print("Man")
        println(name)
    }
}
在main函数中
Man("hello").one()

最后输出

1 Peoplepeople
2 PeopleMan
  helloMan
3 Manhello

分析:
1)首先是父类的初始化init调用
2)接着调用子类init方法,此时可以看到子类的构造方法并不是val name : String的形式,如果这么写编译器会报错,提示隐藏了父类中的参数
3)多态,同Java

其他构造方法

open class View{
	constructor(ctx : Context){}
	constructor(ctx : Context, attr : AttributeSet){}//没有声明主构造方法,但是声明了两个从构造方法
}

子类的继承

class Button : View{
	constructor(ctx : Context) : super(ctx){}
	constructor(ctx : Context, attr : AttributeSet) : super(ctx,attr){}//调用父类的构造方法
	也可以通过this调用另一个构造方法
	//constructor(ctx : Context) : this(ctx,MY_STYLE)
}

如果类没有主构造方法,那么每个从构造方法必须初始化基类或者委托给另一个这样做的构造方法

实现在接口中声明的属性

interface User{
	val nickname : String
}
//主构造方法属性,实现了来自User的抽象属性,需要标记为override
class PrivateUser(override val nickname : String) : User
//自定义getter,这个字段没有支持字段来存储,只有一个getter在每次调用时获得数值
class EmailUser(val email : String) : User{
	override val nickname : String
		get() = email.substringBefore('@')
}
//属性初始化
class QQUser(val openId : Int) : User{
	override val nickname = getOpenId(openId) 
}

通过getter/setter访问支持字段

class CustomSetter(val name : String){
    var address : String = "unknown"
        set(value : String){
            println("""
                Address was changed for $name:
                "$field"->"$value".//field用来访问支持字段的值
                """.trimIndent())
            field = value
        }
}
在main方法中调用CustomSetter("XXD").address = "123"
会输出
Address was changed for XXD:
"unknown"->"123".

修改访问器的可见性
通过在get/set关键字前面添加可见性修饰符来修改

三.编译器生成的方法:数据类和类委托

通用对象方法

open class User(val user : String , val phone : String){
    override fun toString() = "user : $user , phone : $phone"
    override fun hashCode() = user.hashCode() * 31 + phone.hashCode()
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is User)
            return false
        else
            return user == other.user && phone == other.phone
    }
}

很明显的三个重写方法,其中需要注意的是在kotlin中,==用来比较内容是否相等(equals),使用=来比较引用

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

但是如果每个类都要重写这些方法,相对于Java而言,kotlin并没有优势,所以通过添加data修饰符

data class User(val user : String , val phone : String)
//此时得到了一个重写所有Java标准的方法
//equals和hashCode方法会将所有在主构造方法中生命的属性纳入考虑
//同时还会生成copy方法,用于在copy实例的时候修改某些属性
val user = User("xxd","123")
println(user.copy(phone="234"))
==>user : xxd , phone : 234

类委托:使用by关键字

class testCollection<T> (innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList{
	//重写或者不重写
}

接口的实现被委托给了另一个对象innerList,编译器会默认生成各种方法;当需要修改行为时,可以重写相应的方法

object关键字,将声明一个类与创建一个实例结合起来

对象声明
通过关键字object引入,与普通类不同的是没有构造方法
其在定义的时候就立即创建了,不需要在代码的其他地方调用构造方法

object MyComparator : Comparator<String>{
    override fun compare(str1 : String, str2 : String): Int {
        return str1.compareTo(str2)
    }
}

在Java中调用时,使用MyComparator.INSTANCE.compare(str1,str2)的形式
同样可以在类中声明对象

伴生对象
在类中定义的对象可以使用关键字companion,这样可以通过容器类名称来直接访问这个对象的方法和属性

class Student private constructor(val name : String){
    companion object {
        fun getStudentByScore(score : Int) = Student(queryByScore(score))
        fun getStudentById(id : Long) = Student(queryById(id))
    }
}

简单来说就是一个工厂方法的使用

作为普通对象使用的伴生对象

可以给伴生对象起一个名字诸如 companion object Head,省略了则默认分配Companion

对象表达式:改变写法的匿名内部类

window.addMouseListener(object : MouseAdapter(),Serializable{
        //重写的方法
    })

可以看到,和Java匿名内部类不同的是,kotlin的匿名对象可以实现多个接口或者不实现接口
同时访问的变量并没有限制为final

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值