【Kotlin學習日記】Day8:類和繼承

大家好,歡迎加入小李君的Kotlin學習之旅。今天是小李君學習 Kotlin 的第八天。

寫了幾天日記,小李君發現,其實帶著大家一起讀官方文檔應該會更加有趣。比起自己從頭開始寫(畢竟不是漢語言文學專業畢業的,想想都覺得害怕),人工意譯官方文檔和加一些吐槽批注和學習提示應該會讓《Kotlin學習日記》本身更具可讀性,小李君還在不斷學習探索中,謝謝大家支持。下面就開始今天的學習專題:類和繼承。

官方文檔:

Classes and Inheritance - 類和繼承

Classes - 類

Classes in Kotlin are declared using the keyword class{: .keyword }:
類在Kotlin里面就是用關鍵字 class 來聲明的,就跟其他語言一樣,除了C語言:

class Student {     // 我是學生類,啦啦啦
}

class Student(name:String, age:Int) {
    val myName = name
    var myAge = age
}

class 類名 類頭 {        
     // 類體                    
}                                      

The class declaration consists of the class name, the class header (specifying its type parameters, the primary constructor etc.) and the class body, surrounded by curly braces. Both the header and the body are optional; if the class has no body, curly braces can be omitted.
類的聲明由類名類頭以及類體組成,類頭包含了形參和首要構造器等等,類體被大括號包圍著。類頭和類體都是可寫可不寫,如果一個類沒有類體,那么大括號就可以直接省略掉。

class Student

Constructors - 構造器 - 構造函數

A class in Kotlin can have a primary constructor and one or more secondary constructors. The primary constructor is part of the class header: it goes after the class name (and optional type parameters).
一個 Kotlin 的類可以有一個首要構造器和多個次要構造器。而首要構造器是類頭的一部分。他通常出現在類名的后面。

class Person constructor(firstName: String) {  // 我是首要構造器
}

If the primary constructor does not have any annotations or visibility modifiers, the constructor{: .keyword }
keyword can be omitted:
如果首要構造器沒有被任何注解或者修飾符所修飾。那么 constructor 這個關鍵字可以直接省略掉。感覺就像 JavaScript 的 Function 聲明的類:

// javascript
function Person(firstName) {
}

// kotlin
class Person(firstName: String) {
}

The primary constructor cannot contain any code. Initialization code can be placed in initializer blocks, which are prefixed with the init{: .keyword } keyword:
首要構造器不可以包含任何初始化的代碼。而初始化的代碼可以寫在初始化代碼塊,這種代碼塊用 init {...} 表示:

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

Note that parameters of the primary constructor can be used in the initializer blocks. They can also be used in property initializers declared in the class body:
需要注意的是,首要構造器的形參可以直接用在初始化代碼塊上。這些形參也可以用在類體的字段的初始化聲明上。

class Student(name: String) {
    val name = name.toUpperCase()  // 我是字段
}

In fact, for declaring properties and initializing them from the primary constructor, Kotlin has a concise syntax:
事實上,Kotlin 針對于首要構造器的字段的聲明和初始化代碼,發明了一個簡潔的語法糖,小李君表示非常喜歡:

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

Much the same way as regular properties, the properties declared in the primary constructor can be mutable (var{: .keyword }) or read-only (val{: .keyword }).
首要構造器里面可以聲明 val 常量字段和 var 變量字段。

If the constructor has annotations or visibility modifiers, the constructor{: .keyword } keyword is required, and the modifiers go before it:
如果構造器有注解或修飾符修飾,那么就要在修飾符后面顯式地寫出 "constructor" 這個關鍵字。貌似這個前面已經提到了,嘮叨~

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

For more details, see Visibility Modifiers.
想了解更多,請看 Visibility Modifiers

Secondary Constructors - 次要構造器

The class can also declare secondary constructors, which are prefixed with constructor{: .keyword }:
類也可以聲明次要構造器,用 constructor{...} 來寫:

class Person {
    constructor(parent: Person) {   // 次要構造器參上
        parent.children.add(this)
    }
}

If the class has a primary constructor, each secondary constructor needs to delegate to the primary constructor, either directly or indirectly through another secondary constructor(s). Delegation to another constructor of the same class is done using the this{: .keyword } keyword:
如果一個類已經有一個主要構造器,那么每個次要構造器都需要代理這個主要構造器,無論是直接地代理還是通過其他次要構造器間接地代理。這種代理機制可以用 this(...) 來表示:

class Person(val name: String) {
    // 次要構造器代理調用了首要構造器,有點像 C++
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

If a non-abstract class does not declare any constructors (primary or secondary), it will have a generated primary constructor with no arguments. The visibility of the constructor will be public. If you do not want your class to have a public constructor, you need to declare an empty primary constructor with non-default visibility:
如果一個非抽象類沒有聲明任何構造器,那么這個類就會生成一個無參的主要構造器(就像 Java 那樣)。通常這種構造器都是 public 的。如果你不想讓一個類有 public 的無參構造器,那么你就需要聲明 private

// 私有的無參構造器通常用于單例模式
class DontCreateMe private constructor () {
}

NOTE: On the JVM, if all of the parameters of the primary constructor have default values, the compiler will generate an additional parameterless constructor which will use the default values. This makes it easier to use Kotlin with libraries such as Jackson or JPA that create class instances through parameterless constructors.
注意:在JVM里頭,如果主要構造器上面所有的參數都有默認值,那么編譯器就會生成一個額外的無參構造器用于默認值賦值。這樣能夠更加容易地使用 Kotlin 一些例如 Jackson 或 JPA 這些可以通過無參構造器創建實體的庫。

class Customer(val customerName: String = "")

Creating instances of classes - new一個對象

To create an instance of a class, we call the constructor as if it were a regular function:
直接調用構造器函數來創建實體。不用 new 一個:

val student = Student()

val person = Person("Joe Smith")

Note that Kotlin does not have a new{: .keyword } keyword.
Kotlin 就是沒有 new 關鍵字。不服來戰。

Creating instances of nested, inner and anonymous inner classes is described in Nested classes.
關于創建復合類,內部類,匿名內部類這些信息請看這里
Nested classes

Class Members - 類的成員

Classes can contain
類的成員有:

  • Constructors and initializer blocks - 構造器和初始化代碼塊
  • Functions - 函數 - 方法
  • Properties - 成員變量 - 字段
  • Nested and Inner Classes - 復合類和內部類
  • Object Declarations - 對象聲明
    講真,老外喜歡賣關子,思維非常跳躍。小李君建議先不管上面這些概念,以后再看,先把這章搞完。

Inheritance - 繼承

All classes in Kotlin have a common superclass Any, that is a default super for a class with no supertypes declared:
所有的類都有一個共同的父類 Any,默認就有了的(是不是很像 Java 的Object):

class Student      // 隱式地繼承 Any

Any is not java.lang.Object; in particular, it does not have any members other than equals(), hashCode() and toString().
Any 類并非是 java.lang.Object;事實上,Any除了 equals(),hashcode(),toString() 以外并沒有任何成員。(沒有 wait() notify() 你懂的)。

Please consult the Java interoperability section for more details.
更多請看 與 Java 的愛恨情仇篇

To declare an explicit supertype, we place the type after a colon in the class header:
要顯式地聲明一個父類,就需要在類頭的冒號后面加上父類的類型:

open class Base(p: Int)    // open 表示該類能夠被繼承

class Derived(p: Int) : Base(p)   // 有點像 C++

If the class has a primary constructor, the base type can (and must) be initialized right there,using the parameters of the primary constructor.
如果一個類有主要構造器,那么父類就必須在該類的主要構造器那里初始化。(真的很像 C++)

If the class has no primary constructor, then each secondary constructor has to initialize the base type using the super{: .keyword } keyword, or to delegate to another constructor which does that. Note that in this case different secondary constructors can call different constructors of the base type:
如果一個沒有主要構造器,那么每個次要構造器都要初始化父類,并調用父類的主要構造器或次要構造器。需要注意的是,不同次要構造器可以調用不同的父類構造器:

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

The open{: .keyword } annotation on a class is the opposite of Java's final{: .keyword }: it allows others to inherit from this class. By default, all classes in Kotlin are final, which corresponds to Effective Java, Item 17:
那個 open 注解與 Java 的 final 唱反調。這么設計是有根據的,具體在 Effective Java

Design and document for inheritance or else prohibit it.
繼承這事決不能有半點含糊

Overriding Methods - 復寫方法

As we mentioned before, we stick to making things explicit in Kotlin. And unlike Java, Kotlin requires explicit annotations for overridable members (we call them open) and for overrides:
就像之前提到的,Kotlin 一切都是顯式設計。不像 Java 那樣(這黑的~),Kotlin 要求顯式地注解需要被復寫的成員(用的就是 open),例如:

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

The override{: .keyword } annotation is required for Derived.v(). If it were missing, the compiler would complain. If there is no open{: .keyword } annotation on a function, like Base.nv(), declaring a method with the same signature in a subclass is illegal, either with override{: .keyword } or without it. In a final class (e.g. a class with no open{: .keyword } annotation), open members are prohibited.
override 注解在 ‘Derived.v()’ 那里是必須要寫的。如果沒寫,則會有編譯錯誤。如果沒有 open 注解修飾在一個方法,例如 ‘Base.nv()’,那么聲明一個同名的方法于一個子類是不合法的,無論寫沒寫 open 注解。在一個 final 類里面(這個類沒有修飾 open),open的成員都是被禁止復寫的。反正繼承復寫這事兒,Kotlin 都是明明白白的,不像 Java那樣糊里糊涂的(又在黑 Java ~)。

A member marked override{: .keyword } is itself open, i.e. it may be overridden in subclasses. If you want to prohibit re-overriding, use final{: .keyword }:
一個被標記為override的成員,自身也是 open 的,即,其可以被子類所復寫。如果想要禁止這種再復寫的行為,可以使用 final 來修飾:

open class AnotherDerived() : Base() {
    // AnotherDerived 的子類不可以復寫該方法了
    final override fun v() {}    
}

Overriding Properties - 復寫字段

Overriding properties works in a similar way to overriding methods; properties declared on a superclass that are then redeclared on a derived class must be prefaced with override{: .keyword }, and they must have a compatible type. Each declared property can be overridden by a property with an initializer or by a property with a getter method.
復寫字段與復寫方法一樣;字段聲明在父類,想要復寫它,就必須要在子類的字段那里修飾override,并且字段類型必須相同。每個聲明的字段可以被帶有初始化操作的字段或者有 getter 方法的字段所復寫。

這段內容讀得有點難懂,畢竟字段的學習還沒展開,這里只留個心眼就行。

open class Foo {
    open val x: Int get { ... }   // getter
}

class Bar1 : Foo() {
    override val x: Int = ...     // initializer
}

You can also override a val property with a var property, but not vice versa. This is allowed because a val property essentially declares a getter method, and overriding it as a var additionally declares a setter method in the derived class.
可以用 var 字段復寫 val 字段,但不可以反過來同理可得。之所以允許用 var 復寫 val 是因為一個 val 字段必須聲明一個 getter 方法,而且用 var 來復寫就需要在子類中聲明一個 setter 方法。

Note that you can use the override{: .keyword } keyword as part of the property declaration in a primary constructor.
注意,override 可以用在主要構造器上。

interface Foo {        // 第一次出現了接口
    val count: Int
}

class Bar1(override val count: Int) : Foo    // 實現了接口

class Bar2 : Foo {
    override var count: Int = 0
}

Overriding Rules - 復寫的規則

In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits many implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones). To denote the supertype from which the inherited implementation is taken, we use super{: .keyword } qualified by the supertype name in angle brackets, e.g. super<Base>:
在 Kotlin 的世界里,實現繼承是非常有規律的:如果一個類繼承父類了多個相同成員的實現,它就必須復寫其成員,以及提供它自己的實現(或使用其中繼承父類的一部分實現)。為了在一個繼承的實現中表示一個父類的類型,使用 super 來表示。例如 super<Base>

這段都不知道在講什么,還是看代碼吧。

open class A {
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // interface members are 'open' by default
    fun b() { 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()
    }
}

It's fine to inherit from both A and B, and we have no problems with a() and b() since C inherits only one implementation of each of these functions. But for f() we have two implementations inherited by C, and thus we have to override f() in C and provide our own implementation that eliminates the ambiguity.
直接繼承 A 和 B 是可以的,而且C 繼承了 a() 和 b(),沒啥問題。但是 C 的 f() 復寫了父類的 f() 。所以,要顯式地用 super 來指明調用哪個父類的 f()。

這段雖然講得復雜點,但是看代碼還是能夠看懂的,關鍵都在 super

Abstract Classes - 抽象類

A class and some of its members may be declared abstract{: .keyword }. An abstract member does not have an implementation in its class. Note that we do not need to annotate an abstract class or function with open – it goes without saying.
一個類及其成員都可以聲明 abstract 。一個抽象的成員不可以有實現的代碼。要知道,修飾了 abstract 的成員不再需要修飾 open

We can override a non-abstract open member with an abstract one
用一個抽象的成員來復寫一個非抽象且 open 成員。

open class Base {
    open fun f() {}
}

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

Companion Objects - 伴隨對象(什么鬼)

In Kotlin, unlike Java or C#, classes do not have static methods. In most cases, it's recommended to simply use package-level functions instead.
對于 Kotlin 來說,不像 Java 或 C#,類是不會有靜態方法的。在大多數情況下,Kotlin 更加推薦用簡單的包級函數來替代。(也許跟內存優化有關)

If you need to write a function that can be called without having a class instance but needs access to the internals of a class (for example, a factory method), you can write it as a member of an object declaration inside that class.
如果你需要寫一個函數,這函數可以不用通過任何類的對象來調用但卻需要訪問一個類的內部部分(例如一個工廠方法),你可以寫一個 對象聲明 的成員。

Even more specifically, if you declare a companion object inside your class, you'll be able to call its members with the same syntax as calling static methods in Java/C#, using only the class name as a qualifier.
尤其是,如果你聲明了一個 伴隨對象 于你的類,你可以直接調用它,就像 Java 或 C# 那樣調取類的靜態方法。

這里又是暈暈的,大概的意思都懂,后面會了解到伴隨對象的作用和來龍去脈。這里只是稍微提及了一下,老外的思維還是一如既往地跳躍。

今天就寫到這了,完。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容