Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字

目录

    • 3.1 接口
    • 3.2 open,final和abstract修饰符:默认为final
    • 3.3 声明枚举类
    • 3.4 可见性修饰符
    • 3.5 内部类和嵌套类
      • 3.5.1 java和中内部类和嵌套类的区别
      • 3.5.2 密封类:定义受限的类继承结构
    • 3.6 类的构造函数
      • 3.6.1 constructor关键字和init
      • 3.6.2 使用val,var 简化初始化
      • 3.6.3 子类的主构造函数初始化父类
    • 3.7 默认的getter和setter及构造函数
    • 3.8 自定义访问器
    • 3.9 重写类的toString(),equals() ,hashCod()方法
    • 3.10 类委托
    • 3.11 object 关键字
      • 3.11.1 对象声明:创建单例易如反掌
      • 3.11.2 伴生对象
      • 3.11.3 作为普通对象使用的伴生对象
      • 3.11.4 对象表达式:改变写法的匿名内部类

3.1 接口

interface Clickable {
     
    //抽象方法
    fun click()
    //非抽象方法
    fun showOff() = println(" i am clickable")
}


class Button : Clickable{
     

    override fun click() {
                   //在Kotlin中override修饰符是强制要求的
        TODO("Not yet implemented")
    }

    override fun showOff() {
     
        super.showOff()
    }

}

如上为接口的定义和它的实现类

如果一个类实现了两个接口,并且两个接口中有相同的方法(无论是抽象方法还是非抽象方法),这时必须在类中显式实现这个方法。

如下:

interface Clickable {
     
    //抽象方法
    fun click()
    //非抽象方法
    fun showOff() = println(" i am clickable")
}

interface Focusable {
     
    
    //非抽象方法
    fun showOff() = println(" i am Focusable")
}
class Button : Clickable,Focusable{
     

    override fun click() {
                   //在Kotlin中override修饰符是强制要求的
       println("in click")
    }

    override fun showOff() {
     			//此时必须显式实现此方法,否则编译错误。
        super<Focusable>.showOff()		//使用尖括号加上父类型名字的“super”表面你想要调用哪一个父类的方法
    }

}

注意:

  • Kotlin并不支持接口中的默认方法,它会把每个带默认方法的接口编译为一个普通接口和一个将方法体作为静态函数的类的结合体。接口中只包含声明,类中包含了以静态方法存在的所有实现
  • java中接口中不能定义非抽象方法

3.2 open,final和abstract修饰符:默认为final

注意:没有特别需要在子类中被重写的类和方法应该被显式的标注为final

open:

  • 如果类用open修饰符标识:其他类可以继承它
  • 如果方法用open修饰符标识:可以在子类中重写它。
  • 如果重写了一个基类或者接口的成员,重写的成员同样默认是open的
open class RichButton : Clickable{
     

    fun disable(){
     }         // 默认为final,子类不能重写
    open fun animate(){
     }    // 子类可以重写

    override fun click() {
          // 如果重写了一个基类或者接口的成员,重写的成员同样默认是open的
        TODO("Not yet implemented")
    }

}

abstract:

  • 抽象类:这种类不能被实例化。类中包含一些没有实现并且必须在子类重写的抽象成员。抽象成员始终是open的,所以不需要显式的使用open修饰符。
  • 抽象方法:在该类中不实现,必须被子类重写。
  • 有抽象方法的类一定是的抽象类,但抽象方法不一定在抽象类中。接口中的方法都是抽象方法
  • 抽象类中的非抽象函数并不是默认open的,但是可以标注为open
abstract class Animated{
             //抽象类

    abstract fun animate()		//抽象方法

    open fun openFun(){
     }		//抽象类中标注为open的非抽象方法

}

类中的修饰符总结

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

3.3 声明枚举类

枚举类,enum在Kotlin中是一个软关键字:只有当它出现在class前面时才有特殊的意义


enum class Color {
     
    RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET
}
enum class Colorful(val r : Int, val g : Int, val b : Int){
     
    RED(255,0,0),
    ORANGE(255,165,0),
    YELLOW(255,255,0),
    GREEN(0,255,0),
    BLUE(0,0,255),
    INDIGO(75,0,130),
    VIOLET(238,130,238);                //这里必须要有';'

    fun rgb() = (r * 256 + g) * 256 + b         //给枚举类定义一个方法
}

注意:上面的例子是Kotlin语法中唯一使用分号的地方:如果要在枚举类中定义任何方法,需要用分号分割枚举常量列表和方法定义

3.4 可见性修饰符

修饰符 类成员|顶层声明
public(默认) 所以地方可见
internal 模块中可见
protected 子类中可见
private 类中可见

注意:

  • 默认的可见性和java中不同,java中的默认可见性——包私有,在Kotlin中并没有将其作为可见性控制

  • internal:模块中可见,一个模块就是一组一起编译的文件。

  • Kotlin允许在顶层声明中使用private可见性(比如类可以私有)。

  • 类的基础类型和类型函数列表中用到的所有类,或者函数签名都要有与这个类或者函数本身相同的可见性。

  • protected修饰符在java和Kotlin中有不同的行为。在java中,可以从一个包中访问一个protected成员。但在Kotlin中不允许这样做。

  • 类的扩展函数不能访问这个类的private和protected成员。

3.5 内部类和嵌套类

3.5.1 java和中内部类和嵌套类的区别

类A在另一个类B中声明 在java中 在Kotlin中
嵌套类(不存储外部类的引用) static class A class A
内部类(存储外部类的引用) class A inner class A

在Kotlin中引用外部类实例的语法也与java不同,在Kotlin中要使用this@Outer

class Outer{
     
    inner class Inner{
     
        fun getOuter() : Outer = this@Outer 
    }
}

3.5.2 密封类:定义受限的类继承结构

demo:

sealed class  Expr{
     
    class Num(val value : Int) : Expr()
    class Sum(val left : Expr, val right : Expr ) : Expr()
}
fun eval (e : Expr) : Int =
        when(e){
     
            is Expr.Num -> e.value
            is Expr.Sum -> eval(e.right) + eval(e.left)
        }

sealed关键字:定义密封类

注意:

  • 密封类的子类必须在密封类的同一文件中声明。
  • sealed修饰符隐含这个类是一个open类
  • 密封类本身是抽象的,不能从中实例化对象
  • 不能创建密封类的非私有构造函数,他们的构造函数默认是private的。

枚举和密封Sealed类之间的区别:

  • 枚举类和密封类非常相似,枚举类型的值集也和密封类一样收到限制
  • 区别:枚举只能有一个实例,而密封类的子类可以有多个实例。

3.6 类的构造函数

3.6.1 constructor关键字和init

java中的构造函数是与类名相同即可,kotlin里面的构造函数是用constructor关键字表示。

kotlin里面的构造函数分为主构造函数和次构造函数。主构造函数只能有一个,次构造函数个数不限制,可以有一个或者多个

//主构造方法如下,跟在类名后面
class Person constructor(name:String){
     
    
}

class Person constructor(){
     
    
}
//当主构造方法没有任何注解或者可见性修饰符时,可以省略,写成下面这样
class Person {
     
    
}

//这种就是有注解标记的主构造方法,不能省略
class Person @Inject internal constructor(){
     
    
}

//次构造方法,一个无参的次构造方法,一个有一个参数的次构造方法

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

以看到主构造方法是没有方法体的,那么,我们需要初始化的数据应该放到哪呢?kotlin提供了init方法,给我们初始化数据。

class Person constructor(){
     
    init{
     
        print("111")
    }
    
    init{
     
        println()
        print("222")
    }
}

var p = Person()
//这里我们会看到打印台打印:111,换行打印222
//这里构造方法是按顺序执行的

对于次构造函数:

class Person{
     
    constructor(){
     
        println()
        print("111")
    }
    
    init{
     
        print("222")
    }
}

//这里我们会看到打印台打印:222,换行打印111

结论:不管是什么构造方法,先执行init模块逻辑,后执行构造方法的逻辑

注意:如果一个类有主构造函数,只要他除了主构造函数还有其他次构造函数,那么这些次构造函数就必须调用主构造函数,方式可以不同

  • 方式1:每个次构造函数都调用主构造函数 :即直接委托

    class Parent(name: String) {
           
     
        var age = 0;
        var sex = "man"
     
        constructor(name: String, age: Int) : this("Main name 1") {
           
            this.age = age;
            println("constructor 1 $name , $age , $sex")
        }
     
        constructor(nickName: String, sex: String) : this("Main name 2") {
           
     
            this.sex = sex;
            println("constructor 2 $nickName , $age , $sex")
        }
     
        open fun learn() {
           
            println(" learn ")
        }
    }
     
    fun main() {
           
        Parent("lucy", "woman").learn()
    }
    
  • 方式2: 次构造函数2 ————> 次构造函数1 ,而 次构造函数1 ————> 主构造函数 ,,即间接委托

    class Parent(name: String) {
           
     
        var age = 0;
        var sex = "man"
        
        //次级构造函数1
        constructor(name: String, age: Int) : this("Main name 1") {
           
            this.age = age;
            println("constructor 1 $name , $age , $sex")
        }
     
        //次级构造函数2
        constructor(nickName: String, sex: String) : this("nickName jj", 12) {
           
     
            this.sex = sex;
            println("constructor 2 $nickName , $age , $sex")
        }
     
        open fun learn() {
           
            println(" learn ")
        }
    }
     
    fun main() {
           
        Parent("lucy", "woman").learn()
    }
    

如果一个类没有主构造函数,次级构造函数就不必显示调用主构造函数

class Parent {
     
 
    init {
     
         // 初始化代码块本质就是主构造函数的方法体
         // 因为主构造函数在类名头部声明不能带有方法体
          
        println("Main constructor")
    }
 
    constructor(sex: String) {
     
        println("constructor  , $sex")
    }
 
    open fun learn() {
     
        println(" learn ")
    }
}
 
fun main() {
     
    Parent("woman").learn()
}

3.6.2 使用val,var 简化初始化

val:

如下所示的类中的属性定义:

class Users (_nickname : String){
     
    val nickname : String

    init {
     
        nickname = _nickname
        
    }

}

可以简化为如下:

class Users (val _nickname : String){
     			//相当于有了get方法
    
}
fun main(args : Array<String>){
     
    val ure = Users("adc")
 
    println(ure._nickname)

}

var:

Kclass Users (var _nickname : String){
     		//相当于有了get和set方法

}
fun main(args : Array<String>){
     

    val ure = Users("adc")
    ure._nickname = "d"
    println(ure._nickname)
}

3.6.3 子类的主构造函数初始化父类

open class ClassUser (val className  : String) {
     

}
class Users (var _nickname : String) : ClassUser(_nickname) {
     

}

注意:即使父类没有有参构造方法,也要显式的调用父类的构造方法

open class ClassUser () {
     

}
class Users (var _nickname : String) : ClassUser() {
     

}

3.7 默认的getter和setter及构造函数

在java中,默认的get,set方法非常复杂,且全是重写。在Kotlin中可以更方便的表达

//People.java
public class People {
     
    private  String name;
    private int isAge;
    
    public String getName() {
     
        return name;
    }

    public void setName(String name) {
     
        this.name = name;
    }

    public int getIsAge() {
     
        return isAge;
    }

    public void setIsAge(int isAge) {
     
        this.isAge = isAge;
    }
}

//People.kt
class People {
     
    var name: String? = null
    var isAge = 0
}

注意:从java到Kotlin的转换过程中public修饰符消失了,在Kotlin中public是默认的可见性,所以可以省略

在java中调用:

//Kotlin的属性 name 会把name对应的getter方法暴露给java(即就是getName()),
People people = new People();
people.setAge(12);
people.setName("liSuo");
        
System.out.println(people.getName() + "   "+ people.isAge());
//getter和setter的命名规则有一个例外:如果属性名称以is开头,
//对应的getter方法不会加任何的前缀

在Kotlin中调用

	val people1 = People()
    people1.isAge = 12
    people1.name = "liSuo"
    println(people1.name + "   "+ people1.isAge)

如果对如上的类加上构造函数:

//People.java
public class People {
     
    private  String name;
    private int isAge;

    public People(String name, int isAge) {
     
        this.name = name;
        this.isAge = isAge;
    }

    public String getName() {
     
        return name;
    }

    public void setName(String name) {
     
        this.name = name;
    }

    public int getIsAge() {
     
        return isAge;
    }

    public void setIsAge(int isAge) {
     
        this.isAge = isAge;
    }
}



//People.kt
class People(var name: String, var isAge: Int)

注意:从java到Kotlin的转换过程中public修饰符消失了,在Kotlin中public是默认的可见性,所以可以省略

在java中调用:

//Kotlin的属性 name 会把name对应的getter方法暴露给java(即就是getName()),
People people = new People("bob",12);
System.out.println(people.getName());
//getter和setter的命名规则有一个例外:如果属性名称以is开头,对应的getter方法不会加任何的前缀
System.out.println(people.isAge());

在Kotlin中调用

	val people = People("bob",12)
    people.isAge = 8
    println(people.name)
    println(people.isAge)

3.8 自定义访问器

class Users (_nickname : String)  {
     

    var nickname : String = "unspecified"
        set(value : String) {
     
            field = value
            println("cheang nickname to $field")

        }
}
  • Kotlin的setter函数体中通过特殊的标识符field来访问支持字段的值。
  • 在getter中,只能读取值;而在setter中,既能读取值又能修改它。

此外还可以修改访问器的可见性。

var nickname : String = "unspecified"
    private set(value : String) {
     
        field = value
        println("cheang nickname to $field")

    }

此时无法通过setter方法修改nickname的值。

3.9 重写类的toString(),equals() ,hashCod()方法

class Client( val name : String, val postalCode : Int){
     
    override fun toString(): String {
     
        return "Client(name = $name , postalCode = $postalCode)"
    }

    override fun equals(other: Any?): Boolean {
     
        if( other !is Client){
     
            return false
        }
        return other.name == name && other.postalCode == postalCode
    }

    override fun hashCode(): Int {
     
        return name.hashCode() * 31 + postalCode;
    }
}

在Kotlin中不必在重写生成这些方法了,只要为你的类添加data修饰符,必要的方法将会自动生成好。

data class Client( val name : String, val postalCode : Int){
     
	
	//作用和上面完全一样
}

3.10 类委托

无论什么时候实现一个接口,你都可以使用by关键字将接口的实现委托到另一个对象。

class CountingSet<T>(private 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(elements: Collection<T>): Boolean {
     
        objectsAdded+=elements.size;
        return innerSet.addAll(elements)
    }
}

3.11 object 关键字

object关键字在多种情况下出现,它的核心理念为:这个关键字 定义一个类并同时创建一个实例,下面我们介绍它的三个应用场景:

  • 对象声明 :是定义单例的一种方式。
  • 伴生对象 :可以持有工厂方法和其它与这个类相关,但在调用时并不依赖类实例的方法,它们的成员可以通过类名来访问。
  • 对象表达式: 用来替代Java的匿名内部类。

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

在Java中,单例模式通常是使用private构造方法,并且用静态字段来持有这个类仅有的实例。

而在Kotlin中,通过使用对象声明功能,将类声明与该类的单一实例声明结合到了一起。

object Person2{
     
    var name  : String = "default Name"
    var age : Int = 15
}

//调用
print(Person2.name)
  • 对象声明通过object关键字引入,一个对象声明可以非常高效地以一句话来定义一个类和一个该类的变量。
  • 一个对象声明可以包含属性、方法、初始化语句块等的声明,但是 不允许声明构造方法,这是因为对象在定义的时候就已经创建了,不需要在其他地方调用构造方法。
  • 对象声明允许使用对象名加.字符的方式来调用方法和访问属性。

继承自接口的对象声明

对象声明可以继承自类和接口,这通常在你使用的框架需要去实现一个接口,但是你的实现不包含任何状态的时候很有用。
Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第1张图片

在类中声明对象

在类中使用对象声明时,这样的对象同样只有一个单一实例:它们在每个容器类的实例中具有相同的实例。

Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第2张图片

运行结果为:

在这里插入图片描述

在 Java 中使用 Kotlin 对象
如果要在Java中使用Kotlin中的声明对象,可以通过访问静态的INSTANCE字段:

在这里插入图片描述
在这里插入图片描述

3.11.2 伴生对象

Kotlin的类不能拥有静态成员,作为替代,Kotlin依赖包级别函数(在大多数情形下能够替代Java的静态方法)和对象声明(在其他情况下替代Java的静态方法,同时还包括静态字段),在大多数情况下,推荐使用顶层函数,但是顶层函数不能访问类的private变量。

因此,如果你需要写一个 在没有类实例的情况下 调用但是需要 访问类内部的函数,可以将其写成那个类中的 对象声明的成员。

在类中定义的对象之一可以使用一个特殊的关键字来标记 companion,如果这样做,就获得了直接 通过容器类名称来访问这个对象的方法和属性的能力,不再需要显示地指明对象的名称,下面是一个基础的示例:
Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第3张图片

运行的结果为:
在这里插入图片描述

使用工厂方法创建对象
Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第4张图片

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

伴生对象是一个声明在类中的普通对象,它可以有名字,实现一个接口或者有扩展函数或属性。假设我们需要在对象和JSON之间进行序列化和反序列化,可以将序列化的逻辑放在伴生对象中。

Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第5张图片

运行结果为:
在这里插入图片描述

在大多数情况下,通过包含伴生对象的类的名字(也就是例子中的Person类)来引用伴生对象,所以不必关心它的名字,如果省略了伴生对象的名字,默认的名字将会分配为Companion。

在伴生对象中实现接口

就像其它对象声明一样,伴生对象也可以实现接口,可以将包含它的类的名字当做实现了该接口的对象实例来使用。

Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第6张图片

运行结果为:

在这里插入图片描述

伴生对象扩展

举例来说,如果类有一个伴生对象,可以通过在其上定义扩展函数来做到这一点,即类C有一个伴生对象,并且在C.Companion上定义了一个扩展函数func,则可以通过C.fun()来调用它。

下面,我们为Person类的伴生对象定义一个扩展函数:

Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第7张图片

当调用toJson就像是它是一个伴生对象定义的方法一样,但是实际上它是作为扩展函数在外部定义的。而为了能够为你的类定义扩展,必须在其中声明一个对象,即使是空的。

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

object关键字不仅能够用来表明单例式的对象,还能用来声明 匿名对象,它替代了Java中匿名内部类的用法。例如,让我们来看看怎样将一个典型的匿名内部类用法转换成Kotlin:

Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第8张图片

运行结果为:
在这里插入图片描述

将匿名对象存储到变量中

除了去掉对象的名字外,语法与对象声明相同的。对象表达式声明了一个类并创建了该类的一个实例,但是没有给这个类或是实例分配一个名字。通常来说,它们都是不需要名字的,因为你会将这个对象用作一个函数调用的参数。如果你需要给对象分配一个名字,可以将其存储到一个变量中:

Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第9张图片

在对象表达式中修改变量的值
与Java的匿名类一样,在对象表达式中的代码可以访问创建它的函数中的变量,但是与Java不同,访问并被限制在final变量,还可以在对象表达式中修改变量的值。

Kotlin学习篇(3)—— Kotlin的 类,对象,接口,object关键字_第10张图片

运行结果为:

在这里插入图片描述

你可能感兴趣的:(Kotlin学习笔记,kotlin)