Kotlin基础三

771 阅读11分钟

内容:

  • 类,接口和抽象类
  • 继承修饰符和可见性修饰符
  • 嵌套类、内部类和密封类
  • 主构造方法和从构造方法,复杂的单例实现
  • 接口定义属性

一类和接口

在第一篇基础文章中已经介绍了类的有参数构造方法的声明语法,但对于开发是远远不够的,还有很多细节需要我们去注意和学习。

1.1 Kotlin中的类和java中的类区别

Kotlin中的类和java中的类大致一样,但是还是有些许区别,如第一篇讲解的。

  1. Kotlin默认是final和public的,而java却不是。
  2. Kotlin中嵌套类并不是内部类,并没有包含对其外部类的隐式引用。

   3. 之前文章还没有介绍构造方法的方法体。

   4.data类,提供了数据类标准的可用的方法。

   5.更简便的单例模式,伴生对象,以及匿名类,继承和实现接口等语法

1.2 接口的定义

Kotlin中的接口和java类似,使用interface关键字来声明一个Kotlin的接口。
interface Clickable {

    fun click();
}
interface Clickable2{
    fun click2();
}
class Son1(name: String, address: String = "") :Father(name,address),Clickable,Clickable2 {
   
    override fun click2() {}

    override fun click() {}
}
使用冒号来代替Java中的extends和implements关键字。和Java一样,单继承多实现的特性。

       override 修饰符必须要求强制写出。与java不同的是,java中的接口中定义的必须全部是抽象方法,但是在kotlin中接口中可以实体方法,有点和java中的抽象类相似,像这样:

interface Clickable {

    fun click()

    fun showoff() {//接口中定义的默认实现方法,默认是open,可以被子类重写
        Log.e("rrrrrrrrrr", "rrrrrrrr")
    }

}
      如果你实现了这个接口,你需要为click提供一个实现。可以重新定义showoff方法的行为,或者如果你对默认行为感到满意也可以直接省略它。

假设我们再去定义一个接口,也有一个同名方法的默认实现,而子类不去实现,看看会出现什么情况

interface Clickable2{
    fun click2()

    fun showoff() {
        Log.e("rrrrrrrrrr", "Clickable2")
    }
}

发现IDE去要求我们必须实现默认方法,否则会报错

class aaaa : Clickable ,Clickable2{
    override fun click2() {

    }

    override fun showoff() {

    }

    override fun click() {

    }


}

假如我们就是钻牛角尖,就想在这种情况下调用其中一个父类方法,或者两个父类方法都调用,怎么办?

Kotlin工程师给我们提供了显示的调用方式!!!

语法:使用尖括号加上父类型名字的“Super”表明了你想要调用哪一个父类的方法,像这样:

override fun showoff() {
    super<Clickable>.showoff()
    super<Clickable2>.showoff()
}

       事实上,java中接口并不能声明实体方法,那么Kotlin可以这样写,那么它的编译原理是什么呢?它会把每个带默认方法的接口编译成一个普通接口和一个将方法体作为静态函数的类的结合体。

1.3类之间继承和重写

Java允许你创建任意类的子类并重写任意方法,除非显式地使用了final关键字进行标注。而在Kotlin中中默认都是final的。如果你想允许创建一个类的子类,需要使用open符来标示这个类,方法和属性。类似这样:
open class DD {

    open var name: String = ""
    
    open fun setValue(neme: String) {
    }

}
class EE: DD() {

   override var name  = "eee"
    override fun setValue(neme: String) {
        this.name = neme
    }

}
注意,如果你重写了一个基类或者接口的成员,重写了的成员同样默认是open的。如果你想改变这一行为,阻止你的类的子类重写你的实现,可以显式地将重写的成员标注为final。像这样:
open class EE : DD() {

    override var name = "eee"
    
    final override fun setValue(neme: String) {
        this.name = neme
    }
}

延伸: 默认是final的好处,可以更好的支持类型的自动转换。

1.4抽象类的定义

在Kotlin中,同Java一样,可以将一个类声明为abstract的,这种类不能被实例化,默认是open修饰。含有的抽象方法默认是open的,但是非抽象方法默认是final的,需要的时候可以用open修饰。如:
//抽象类默认是open的
abstract class Animated {
    open fun fun1() {
    //默认是final,可以用open去修饰,打开重写
    }
    //抽象方法,默认是open状态
    abstract fun fun2()

}

到了这里,我们可以看出,接口和抽象类都可以有实体方法,唯一的区别就是抽象类的实体方法默认是final,而接口中实体方法默认是open。

二 继承修饰符和可见性修饰符

2.1继承修饰符

 通过上边类,接口和抽象类的使用可以总结如下:

open: 重写类,方法,属性开关,默认接口都是open修饰,抽象类和抽象方法是open修饰。

final:不能被重写类,方法,属性开关,默认类中全部是final,抽象类中的实体全是final。

abstract :只有抽象类和抽象方法可以使用,默认被它修饰的同时是被open修饰。

override :重写父类的标识符,如果是重写,必须显示的显示出来。

2.2 可见性修饰符

Kotlin中的可见性修饰符与Java中的类似。同样可以使用public、protected和private修饰符。不同的是,默认java是包内可见,而kotlin是public类型。且kotlin增加了一个internal模块内可见修饰符。

我们都知道,kotlin中多了顶层声明的函数或方法,这些修饰符大部分同样适用顶层声明中。


延伸:可见修饰符和java有不同的地方,那么在编译成字节码的时候,kotlin会怎么转化呢?

public还是public,private被转换成包私有,protected和java的一样,internal会变成public

三 嵌套类和内部类

3.1 嵌套类

和java一样,Kotlin也支持在一个类中声明一个类。区别是kotlin的内部类默认情况不能使用外部类对象。

class Student() {
    var name :String?=null

    class Score() {
        
    }

}

默认情况下Score类是不能使用name属性的,因为他们是嵌套类,内部默认情况下是没有持有外部类引用的。

3.2 内部类

如我们因为某种需要,就是想写出类似java中的内部类。这里介绍一个关键字inner,而且引用需要使用this@外部类名字去调用。如:

class Outer {
    var arr = ArrayList<String>()
     inner class Innter {
        val size: Int
            get() = this@Outer.arr.size
    }
}

3.3密封类

为一个类添加一个修饰符sealed,那么所有的直接子类必须嵌套在父类中。

sealed class A{

    open class B : A(){
        
    }

}

但是可以在外部继承B类。

四 构造方法

       总算是说道构造方法了,如果看文章的人,应该内心早就骂娘了,这么久了,还不说构造,你小子到底是不是专业的?讲!这就讲!看官,让你久等了。。。。。

在java中构造方法可以重载,而我们已经学过了Kotlin的重载,是不是传递一个默认值就行了?这样只是简单的重载。构造方法的特殊性,注定它的与众不同,生下来就含着金钥匙。Kotlin区分主构造方法从构造方法。

主构造方法:通常是主要而简洁的初始化类的方法,并且在类体外部声明。

从构造方法:在类体内部声明。

4.1主构造方法和方法体

在第一篇已经简单接触了主构造方法:

class Person(val name: String)

这段被小括号围起来的语句块就叫作主构造方法。他主要有两个目的:

1.声明构成方法的参数;

2.可以把参数声明成类中的属性。

我们还可以这样写:

class Person constructor(name: String) {
    val name: String

    init {
        this.name = name
    }
}
constructor关键字用来开始一个主构造方法或从构造方法的声明。主构造一般情况可以省略。

init关键字用来引入一个初始化语句块。

可以与属性的声明结合。主构造方法没有注解或可见性修饰符,同样可以去掉constructor关键字。像这样:
class Person(name: String) {
    val name: String = name
}
要创建一个类的实例,只需要直接调用构造方法,不需要new关键字,事实上,我们一直在这样做,这里提一下,kotlin中没有new这个关键字。

注意:如果没有给这个类声明任何构造方法或者声明了有参数的构造方法都有默认值,编译器都会额外的生成一个空参数的构造方法。如:

class Person(name: String="") 
class Person

4.2复杂的单例实现

Kotlin中如果我们想要把一个类变成单例模式你可以这样写:

把一个类的构造方法私有化,

class Person private constructor()

而结果却发现没法写静态方法,因为我们还没有学。如果用顶层方法尝试去写,还是创建不出来私有构造方法。这就是第二篇为什么说顶层方法不适合单例模式。

错误代码:

class Person private constructor() {  

}
public  fun  getPerson () =  Person()//这里会报错,因为构造方法是私有的

这里如果提供一个静态方法的声明方法多好,Kotlin其实是支持的,需要使用这样的结构:

//这里边的都是静态方法和静态属性
companion object {

}

所以我们可以把方法写到这里边去创建一个单例模式,之后还会讲解更简单的单例模式写法。

class Person private constructor() {
    //这里边的都是静态方法和静态属性
    companion object {
        private var mInstance: Person? = null//声明一个静态常量
        //生命一个静态属性
        val instance: Person
            //从新写他的get方法
            get() {
                if (mInstance == null) {
                    synchronized(Person::class.java) {
                        mInstance = Person()
                    }
                }
                return mInstance!!
            }
    }

}

这样一个复杂的单例模式就创建成功。下一篇我将会讲怎么简单创建一个单例模式。

4.3次构造方法

假如我们就是想要申请多个构造方法,而且不同的构造方法的初始化逻辑还都不一样,我们可以这样去声明:

class MyView{

    constructor(context: Context){

    }
    constructor(context: Context,attributes: AttributeSet){

    }

}

注意,这个类没有声明一个主构造方法。这里就没有了无参数的构造方法。

如我们想要自定义一个View,并扩展父类方法,你可以这样让构造方法分别继承父类构造方法,像这样:继承super

class MyView : View {

    constructor(context: Context) : super(context) {
      //do
    }

    constructor(context: Context, attributes: AttributeSet) : super(context, attributes) {
        //do
    }

}

亦或是调用自身类的构造方法:继承this()

class MyView : View {

    constructor(context: Context) : this(context,null) {
      //do
    }

    constructor(context: Context, attributes: AttributeSet?) : super(context, attributes) {
        //do
    }

}

就是任性想要主构造和次构造都写出来,就是不用默认值:

class Person constructor() {
    lateinit var name :String

    constructor( name :String):this(){
        this.name = name

    }
}

很显然达到你的目的了,但是却失去原有的简洁性,也没有意义。何必呢?

总结,构成方法要么只有主构造,要么只有次构造,次构造要么继承this,要么继承super,但必须继承一个。这样使用更加合理简单。

五 属性

5.1接口定义属性

我们在第一篇已经介绍了可变属性,不可变属性,重写get和set方法。这里我们更深入了解一下属性。

在java中接口中不能定义属性,只能定义抽象方法。在抽象类中,java可以定义属性,子类也可以使用属性。而在Kotlin中,接口可以定义属性,抽象类和java一样可以定义属性,使用属性。

比如我们在接口P中声明了一个属性:

interface P {
    val name: String
}

那么这个属性并没有对应的get或者set方法的,这里需要我们子类自己去实现它的get或者set方法,你可以这样写:

class Person(override var name: String) :P

或者自己去定义get和set方法。

突然想到我们接口可以去实现方法,那么我们能不能在接口中去直接把get、set方法定义好呢?当然是可以的!

我们可以直接去在接口中提供get方法,代码修改成这样:

interface P {
    val name: String
    get()= "张三"
}

且子类可以重写。

class Person() :P{
//    override val name:String
//    get() = "李四"

}

注意:如果重写,打印就是李四,如果不重写,打印就是张三。


小结:

类之间的集成需要使用open关键字,重写方法必须使用override关键字,不想被重写用final关键字。

接口可以写实体方法,接口可以声明属性,但是没有get和set方法,默认全部是open修饰,抽象类实体方法默认是final状态。

嵌套类不是内部类,内部类需要使用innter关键字,密封类是所有的直接子类都要是嵌套类。

主构造方法和从构造方法一般不会同时出现,重载多个构造方法,继承super或this。

可见性修饰符public 全部可见,private 类或者文件内可见,propected子类可见,还有一个模块可见。