Kotlin基础之类、继承、可见性修饰符

Posted by AlexWan on 2017-07-03

类与继承

在Kotlin中使用class声明类

class Invoice {
}

类声明由类名,类头部(指定类型参数,首要构造等)和用大括号包裹的类体组成。类头部和类体为可选;如果无类体,可省略花括号

class Empty

构造器

Kotlin的类可以有一个首要构造器和多个次要构造器。首要构造是类头部的一部分,跟在类名之后(参数类型可选)

class Person constructor(firstName: String) {
}

如果首要构造没有任何注解或可见修饰符,constructor关键字可省略。

class Person(firstName: String){
}

首要构造不能含有任何代码。初始代码可以放在初始块中,初始块使用init前缀修饰:

class Customer(name: String){
init {
logger.info("Customer initialized with value ${name}")
}
}

首要构造的参数可以在init代码块中使用,也可以用来初始化类中声明的属性

class Customer(name: String) {
val customerKey = name.toUpperCase()
}

事实上,Kotlin有个简明语法在首要构造器中初始话声明的属性

class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}

与常规属性一样,声明在首要构造的属性可以是可变的(var)或只读(val)

如果构造器有注解或可见修饰符,在构则要加上constructor关键字。

class Customer public @Inject constructor(name: String) { ... }

更多查看可见性修饰符

次要构造器

类可以声明次要构造器,使用custructor前缀修饰

class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}

如果类有首要构造,每个次要构造器都需要委托给首要构造或间接地通过其他次要构造器。使用this关键字完成委托。

class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}

如果不是抽象类并且没有声明任何构造器,则生成一个无参首要构造。构造器可见性为public。如果不希望有public类型构造器,则需要声明一个空的非默认可见性的首要构造器

class DontCreateMe private constructor () {
}

在JVM上,如果首要构造所有参数都有默认值,编译器生成一个使用默认值的额外参数构造器。可以让kotlin和像JacksonJPA这样创建类的实例的库一起使用。

class Customer(val customerName: String = "")

创建类的实例

像常规函数一样调用构造器,来创建一个类的实例

val invoice = Invoice()
val customer = Customer("Joe Smith")

kotlin 没有new关键字

查看关于创建嵌套类的描述

类成员

类包含

继承

所有Kotlin类都有一个共通的超类Any。

class Example // Implicitly inherits from Any

Any不是java.lang.Object;除了equals()hashCode()toString(),没有其他的成员。详情查看Java协同小节

在类头部的尾部来声明明确的父类类型

open class Base(p: Int)
class Derived(p: Int) : Base(p)

如果类有一个首要构造,则必须使用首要构造器初始化基类。

如果类没有首要构造器,则每个次要构造器使用super关键字初始化基类或委托给其他构造执行。

不同次要构造器可以调用不同基类构造

class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

类的open注解与Java的final意义相反:允许其他类继承本类。kotlin默认的所有类都是final类型,对应Effective Java中第17条:要么为继承而设计,并提供文档说明,要么就禁止继承

复写方法

如上面说到,Kotlin 力求事情明确。与Java不同,Kotlin需要明确的可复写的成员(Koltin称为open)和复写

open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}

Derived.v()需要override,如果缺失,编译则给出抗议。如果函数没有open注解,如Base.nv(),则在子类中声明相同签名的方法,无论使用或不使用override注解都是非法的。在final类(没有使用open注解)中,禁止注解open成员。

标记为override的成员函数可以被子类复写。如果禁止复写,使用final关键字

open class AnotherDerived() : Base() {
final override fun v() {}
}

复写属性

复写属性原理与复写方法相似;超类中声明的属性并在派生类中重新声明,必须使用override在前面标记,并且具有兼容的类型。每个声明的属性可以被有初始化操作或有getter方法的属性复写。

open class Foo {
open val x: Int get { ... }
}
class Bar1 : Foo() {
override val x: Int = ...
}

可以使用var属性来复写val属性,反过来不可。因为val属性本质上声明了getter方法,复写为var属性在派生类中声明了setter方法

可以在首要构造器中使用override关键字来声明属性

interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}

复写规则

Koltin定义了实现继承的规则。如果类继承它直接超类的相同成员的多个实现,则必须复写这个方法并且提供自己实现。为了表示采用的继承实现父型,需要使用super修饰尖括号中的超类类型,如:super<Base>

open class A{
open fun f(){ print("A") }
fun a() { print("a") }
}
open class B{
fun f(){ print("B") }
funcb() { print("b") }
}
class C() : A() , B{
// The compiler requires f() to be overridden:
override fun f() {
super<A>.f() // call to A.f()
super<B>.f() // call to B.f()
}
}

同时继承A和B,对于函数a()和函数b()没有问题,因为C只继承了这些函数一个实现。但是对于函数f(),有C的两个实现,因此必须要复写f(),并由C提供自己的显示来消除歧义。

抽象类

类和它一些成员可以声明为abstract。抽象成员在当前类没有具体实现。

不言而喻,抽象类或抽象函数无须使用open注解

可以使用抽象来复写非抽象的open函数

open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}

伴生对象

与Java,C++不同的是,Kotlin没有静态方法。在多数类中,建议使用包级函数替代。

如果需要写一个不用类的实例来调用的函数,但要通过访问类的内部信息(如:工厂方法),可以在类中写作为对象声明成员。

更为特殊的情况,如果在类中声明伴生对象,就可以只使用类的名称作为限定符来调用成员,就好像Java/C#中调用静态方法那样。

可见性修饰符

类、对象、接口、构造器、函数、属性和他们的setter都可以有可见性修饰符(Getters与属性相同的修饰符)。Kotlin有四种可见性修饰符:privateprotectedinternalpublic。默认可见性为public

函数,属性,和类,对象和接口都可以在上层中声明。如:直接在包中

// file name: example.kt
package foo
fun baz() {}
class Bar {}

  • 如果没有指定任何可见性修饰符,默认为public,意味:声明随处可见
  • 如果标记为private,则只在声明的文件中可见
  • 如果标记为internal,则在相同模块中可见
  • 标记为protected,则不能为顶层访问
    // file name: example.kt
    package foo
    private fun foo() {} // 在example.kt内部可访问
    public var bar: Int = 5 // 在任何地方可见
    private set // setter 方法只在example.kt中可见
    internal val baz = 6 // 在相同module中可见

类与接口

对于类中声明的成员

  • private:只在声明的类中可见(包括类所有的成员函数)
  • protected:与private相似,但子类也可访问
  • internal:相同模块的任何client都访问类的internal成员
  • public:任何client都可访问类的public成员

Java开发注意:在kotlin中,外部类无法访问内部类的私有成员

如果复写protected成员且没有显式指定可见修饰符,复写后的成员也具有protected可见性。

open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // public by default
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a 不可见
// b, c 和 d 可见
// Nested 和 e 可见
override val b = 5 // 'b' 为 protected
}
class Unrelated(o: Outer) {
// o.a, o.b 不可见
// o.c 和 o.d are 可见(相同模块)
// Outer.Nested 不可见且 Nested::e 也不可见
}

构造器

使用下面的语法来指定类的首要构造器的可见性

需要显式添加constructor关键字

class C private constructor(a: Int) { ... }

类C的构造器为私有。默认的构造器都为public属性,也说明了只要可访问类,就能使用对应类的构造器。(如 internal类型的类,只在相同模块中访问)

局部变量

局部变量,函数和类没有可见性修饰符

模块

internal修饰符表示成员只能被相同模块访问。明确地说,Kotlin的模块是共同编译的一组文件:

  • IntelliJ IDEA模块
  • Maven或Gradle项目
  • 使用Ant构建编译的文件