Kotlin 知识梳理(3) 类、对象和接口

1、本文概要

本文是对<<Kotlin in Action>>的学习笔记,若是须要运行相应的代码能够访问在线环境 try.kotlinlang.org,这部分的思惟导图为: java

2、定义类继承结构

2.1 Kotlin 中的接口

Kotlin的接口能够包含如下两种类型的方法:框架

  • 简单的抽象方法
  • 包含默认实现的抽象方法

简单接口

  • 一个简单的Kotlin接口使用 interface 关键字来声明,全部实现这个接口的非抽象类都须要实现接口中定义的抽象方法。
  • Kotlin在类名后面使用 冒号 代替了Java中的extendsimplements关键字,一个类能够实现多个接口,可是只能继承一个类。
  • override修饰符用来标注被重写的父类或者接口的方法和属性,而且是 强制要求 的。

下面的例子中定义了一个接口,并演示了如何实现该接口,以及接口中定义的抽象方法: ide

在接口中定义方法的默认实现

咱们能够给接口的方法提供一个默认的实现,定义的方法和普通函数相同。 函数

若是一个类实现了两个接口,而这两个接口定义了相同的方法,而且都提供了该方法的默认实现,那么该类必须显示实现该方法,不然会在编译时报错:

调用继承自接口的方法的实现

当须要调用一个继承的实现,可使用与Java相同的关键字 super,并在后面的尖括号中指明父类的名字,最后是调用的方法名学习

2.2 访问性修饰符:open、final、abstract

通常类

  • Kotlin中,类和方法默认都是final的,若是想容许建立一个类的子类,须要使用open修饰符来标示这个类,此外还须要给每个容许被重写的属性或方法添加open修饰符。
  • 若是重写了一个基类的成员,重写了的函数一样默认是open的,若是想改变这一行为,能够显示地将重写的成员标注为final

抽象类

咱们能够将一个类声明为abstract,这种类不能被实例化,一个从抽象类一般包含一些没有实现而且必须在子类重写的抽象成员:this

  • 抽象类中的抽象函数:没有函数体就默认是abstract的,不必定要加上关键字,其访问性始终是open的。
  • 抽象类中的非抽象函数:默认是final的,若是须要重写,那么须要加上open修饰符。

小结

openfinalabstract这三个访问修饰符都 只适用于类,不能用在接口 当中:spa

  • open:用于声明一个类能够被继承,或者方法能够被子类重写。
  • final:不容许类被继承,或者不容许方法被重写。
  • abstract:声明抽象类,或者抽象类中的抽象方法。

当咱们须要重写方法时,必须加上override修饰符。设计

2.3 可见性修饰符

Kotlin的可见性修饰符包括如下四种:3d

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

JavaKotlin在可见性上的区别包括如下几点:code

  • Java中默认的可见性是包私有的,而在Kotlin中,默认的可见性是public的。Kotlininternal做为包可见的替代方案,它表示“只在模块内部可见”。
  • Kotlin容许在顶层声明中使用private可见性,包括类、函数和属性,这些声明就只在声明它们的文件中可见,这是隐藏子系统实现细节的很是有用的方式。
  • 类的扩展函数不能访问它的privateprotected成员。
  • Kotlin中,一个外部类不能看到其内部类中的private成员。

2.4 内部类和嵌套类

Kotlin中,若是咱们像Java同样,在一个类的内部定义一个类,那么它并非一个 内部类,而是 嵌套类,区别在于嵌套类不会持有外部类的引用,也就是说它其实是一个静态内部类:

嵌套类
若是要把它嵌套类变成一个 内部类 来持有一个外部类的引用的话须要使用 inner修饰符,而且访问外部类时,须要使用 this@{外部类名}的方式。

内部类

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

以前在介绍when表达式的时候,咱们用了一个表达式的例子,NumSum继承于基类Expr,分别表达数字和两个表达式之和,而对于不属于Expr的子类,咱们须要提供额外的else操做符。

假如咱们给 Expr添加了一个新的子类,编译器并不能发现有地方改变了。若是忘记了添加一个新分支,就会选择默认的选项,这有可能致使潜在的 bug

Kotlin为这个问题提供了一个解决方案:sealed类。为父类添加一个sealed修饰符,对可能建立的子类作出严格的限制,全部的直接子类必须嵌套在父类中。以前的例子修改以下:

这时候,咱们在 when表达式中已经处理了全部 Expr的子类,就再也不须要提供默认的分支,假如这时候咱们给 Expr添加一个新的子类 Multi,可是不修改 when中的逻辑,那么就会致使编译失败:
提示的信息为:
在这种状况下, Expr类有一个只能在类内部调用的 private构造方法,你也不能声明一个 sealed接口,由于若是这样作, Kotlin编译器不能保证任何人都不能在 Java代码中实现这个接口。

3、构造方法

Java中,一个类能够声明一个或多个构造方法,Kotlin则将构造方法分为两类:

  • 主构造方法:主要而简洁的初始化类的方法,而且在 类体外部声明
  • 从构造方法:在 类体内部声明

3.1 初始化类:主构造方法和初始化语句块

假设,咱们须要定义一个包含只读nickname属性的User类,最简单的方式为:

上面这段被括号围起来的语句块就叫作 主构造方法,它有两个目的:

  • 代表构造方法的参数。
  • 定义使用这些参数初始化的属性,也就是nikename

用于完成上面这两个功能的最明确的代码以下所示:

  • constructor:用来开始一个主构造方法和从构造方法的声明。
  • init:引入一个初始化块语句,这种语句块包含了在类被建立时执行的代码,并会与主构造方法一块儿使用,由于主构造方法有语法限制,这就是为何要使用初始化语句块的缘由。

在上面的例子中有几个能够简化的点:

  • 放在初始化语句块的语句能够和nikename的声明结合,所以能够去掉init语句。
  • 若是主构造方法没有注解或可见性修饰符,能够取消constructor关键字。
  • 若是属性用相应的构造方法参数来初始化,代码能够经过把val关键字加在参数前的方式来进行简化。

通过了以上三点,就会获得最前面简化后的结果。

对于构造方法,也能够采用以前在 Kotlin 知识梳理(2) - 函数的定义与调用 中介绍的 命名参数默认参数值 的技巧,若是全部的构造方法都有默认值,编译器会生成一个额外的不带参数的构造方法来使用全部的默认值。

Java中,若是父类定义了一个构造方法,那么在子类的构造方法中,必需要经过super方法初始化父类,例如:

//父类。
public class User {

    private String nikeName;

    User(String nikeName) {
        this.nikeName = nikeName;
    }
}
//子类。
public class TwitterUser extends User {

    public TwitterUser(String nikeName) {
       super(nikeName);
    }
}
复制代码

而在Kotlin中,能够经过在基类列表的父类引用中提供父类构造方法参数的方式来作到这一点:

假如一个类没有声明任何的构造方法,将会生成一个不作任何事的默认构造方法,若是有子类继承了它,那么必须显示地调用父类的构造方法,即便它没有任何的参数。 若是想要确保你的类不被其它代码实例化,必须把构造方法标记为 private,咱们对上面的例子进行修改:

报错的缘由为:

在大多数真实的场景中,类的构造方法是很是简明的:它要么没有参数或者直接与参数对应的属性关联,这就是为了 Kotlin有为主构造方法设计的简洁的语法。

3.2 用不一样的方式来初始化父类

大多数在Java中须要重载构造方法的场景都被Kotlin支持命名参数和参数默认值的语法所覆盖了。

定义从构造方法:constructor

而当咱们须要扩展一个框架来提供多个构造方法,以便于经过不一样的方式来初始化类的时候,就会须要用到从构造方法,从构造方式使用constructor方法引出,例以下面的代码:

运行结果为:

子类调用父类的从构造方法:super

若是想要扩展这个类,能够声明一样的构造方法,并使用super关键字调用对应的父类构造方法:

运行结果为:

子类调用本身的另外一个构造方法:this

若是想要从一个构造方法中,调用你本身的类的另外一个构造方法,那么可使用this关键字:

运行结果为:
须要注意:若是类没有主构造方法,那么每一个从构造方法必须初始化基类(经过 super关键字)或者委托给另外一个这样作了的构造方法(经过 this关键字),也就是说,每一个从构造方法必须以一个朝外的箭头开始,而且结束于任意一个基类构造方法,就像上面例子中 Button的带有两个参数的从构造方法所作的那样。

3.3 实如今接口中声明的属性

Kotlin中,接口能够包含抽象属性的声明:

可是接口并无说明这个值应该存储到一个支持字段仍是经过 getter来获取,接口自己并不包含任何状态,所以只有实现这个接口的类在须要的时候会存储这个值。

下面是三个例子:

  • PrivateUser:直接在主构造方法中声明了这个属性,这个属性实现了来自于User的抽象属性,因此要标记为override
  • SubscribingUser:经过一个自定义的getter实现,这个属性没有一个支持字段来存储它的值,它只有一个getter在每次调用时从email中获得昵称。
  • FacebookUser:在初始化时,将nickname属性与值关联。

接口除了能够声明抽象属性外,还能够包含具备gettersetter的属性,只要它们没有引用一个支持字段(支持字段须要在接口中存储状态,而这是不容许的):

  • email:必须在子类中重写。
  • nickname:有一个自定义的getter,能够被子类继承。

运行结果为:

4.4 经过 getter 和 setter 访问支持字段

如今,咱们已经学习了两种属性的用法:

  • 存储值的属性
  • 具备自定义访问器在每次访问时计算值的属性

如今,咱们结合以上两种,来实现一个既能够存储值,又能够在值被访问和修改时提供额外逻辑的属性:

运行结果为:
上面的 address就是 有支持字段的属性,它和 没有支持字段的属性 的区别在于:

  • 若是显示地引用或者使用默认的访问器实现,编译器会为属性生成支持字段。
  • 若是你提供了一个自定义的访问器实现而且没有使用field,支持字段就不会被呈现出来。

4.5 修改访问器的可见性

访问器的可见性默认与属性的可见性相同,可是若是须要能够经过在getset关键字前放置可见性修饰符的方式来修改它,例如在下面的例子中,咱们将setter的可见性修改成private

运行结果为:


更多文章,欢迎访问个人 Android 知识梳理系列:

相关文章
相关标签/搜索