大家好,欢迎加入小李君的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# 那样调取类的静态方法。
这里又是晕晕的,大概的意思都懂,后面会了解到伴随对象的作用和来龙去脉。这里只是稍微提及了一下,老外的思维还是一如既往地跳跃。
今天就写到这了,完。