Kotlin-类与对象

类与继承

类的基本结构

一个普通类应当包含属性、函数,构造函数,基本的样子

以下三种都是合规的申明。

1
2
3
4
5
6
7
8
9
10
11
12
class A

class B {

}

class C() {
val name: String
fun hello() {
println("hello my namnis $name")
}
}

主构造函数

一个对象产生必定经过类的构造函数进行构造。在类名后面使用constructor进行标志,并且添加构造参数。这就是主构造函数

1
2
3
class A constructor(name: String) {

}

同时constructor前面是可以写入访问控制符和注解的

1
2
3
class A private constructor(name: String) {

}

如果没有注解或者是访问修饰符。则就是可以略去constructor

1
2
3
class A(name: String) {

}

主构造函数看出来是只有申明的构造参数,那么在哪里进行使用这些构造参数呢,Kotlin为我们准备了一个init初始块。就相当于init就是主构造函数的函数体了。

1
2
3
4
5
6
7
8
class A(name: String) {
private val _name: String
private val _name2: String = name
init {
println("constructor param is $name")
_name = name
}
}

我们可以在init块进行初始参数的初始化操作。或者直接赋值到属性。如果是赋值到属性的话可以继续简写

1
2
class A(private val name: String) {
}

等同于

1
2
3
class A(name: String) {
private val name = name
}

就相当于直接将属性直接写到构造函数里了

次构造函数

次构造函数则相对与在类名主构造函数而言的。同样也是使用constructor来申明,同时次构造函数必须委托给主构造函数,也即是要调用主构造函数,使用后面加上:this()来进行委托。同样次构造函数之间存在重载,允许有多个

1
2
3
4
5
class A(val name: String) {
constructor(name: String, age: Int): this(name) {
println("次构造函数")
}
}

创建实例

1
val a = A("jack")

类的成员

  • 函数
    • 成员函数
    • 构造函数
  • 属性
    • 普通属性
    • 对象声明即匿名类对象
    • 伴生对象
    • 嵌套类
    • 内部类
  • init

继承

kotlin的继承是单继承的

所有的类都是Any的子类,这个和Java的Object很相似,但是只包含了equals()hashCode()toString()这三个函数。

默认情况下使用class进行声明的类都是不可被继承,相对与final类。若想被继承需要加上open关键字

使用冒号语法在类名后加上继承或者要实现的类或接口,如果父类的有主构造函数,需要显示调用。如下所示

1
2
3
4
5
6
7
8
9
10
interface I {

}
open class Parent(firstName: String) {

}

class son(firstName: String, lastName: String): Parent(firstName),I {

}

覆盖函数

在继承中就会涉及到覆盖重写的问题。一个方法需要配重写,则也是使用open进行修饰。而覆盖的函数需要加上override修饰。而override本身是继续开放的,需要关闭继承加上final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
open class A{
open fun sayHello() {
//开放继承
}
}

open class B: A {
override fun sayHello() {
//重写,继续开放继承
}
}

class C: B {
final override fun sayHello() {
//重写,不开放继承
}
}

覆盖属性

类似覆盖函数,同样使用open和override进行。

1
2
3
4
5
6
7
open class A {
open val name: String = "jack"
}

class B: A {
override val name: String = "jackie"
}

注意var可以覆盖val,反之不行

调用超类

使用super关键字

1
2
3
4
5
6
7
8
9
10
11
open class A {
open fun test() {

}
}

class B: A {
override fun test() {
super.test()
}
}

以及父类或者接口具有同名覆盖方法时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
open class A {
open fun test() {}
}

interface B {
fun test() {}
}

class C: A, B{
override fun test() {
super<A>.test()//调用A的实现
super<B>.test()//调用B的实现
}
}

以及当环境不同时,比如在内部类中要访问外部类的超类实现。通过@限定来实现。

1
super@Outer.func()

this也有同样的使用方式。调用外部类实现

1
this@Outer.func()

接口和抽象类

注意:接口方法是和Java8一样是可以存在默认实现的

1
2
3
4
5
6
7
8
9
abstract class A{
abstract fun test()
}

interface I {
fun test() {
//可以存在默认实现
}
}

属性与字段

其实本来这两者是作为方便使用,可以看成是对应着Java里的字段和get/set方法。只不过这里的话通过声明属性,可以不需要手动写get/set函数了。

字段

首先字段其实就是类中的一个量。通过varval,因为默认下kotlin的非空元素,所以必须要声明时指定值,或设置懒初始化。

1
2
3
class A {
private var name: String = "jack"
}

属性

其实属性和字段的区别就是,属性 = 字段 + get / set 函数。而此时这个属性在内部使用存储标志就是field关键字标志的幕后字段。

1
2
3
4
5
6
7
8
9
10
class A {
var name: String = "jack"
set(value) {
println("set")
field = value
}
get() {
println("get")
}
}

同样我们可以不用幕后字段,使用我们自己的字段,即用private声明的量

1
2
3
4
5
6
7
8
class A {
private var _name: String = "jack"
val name: String
get() = _name
set(value) {
_name = value
}
}

其实说白了就是将get/set和变量放到了一起,在不自定义时,使用默认的的get/set实现以消除模板代码。对于属性的访问也是通过get/set函数进行访问获取修改的。

如果还是糊涂的可以,将上述的例子进行反编译,看看java,会发现特别简单。就是帮助写模板的get/set函数的。

延迟初始化

val的量一定要一开始初始化,要么直接赋值,要么在init块中初始化

var的量要么一开始初始化赋值,要么在前面加上lateinit指定随后在某个地方延迟初始化。

1
2
3
4
5
6
7
class A {
private val name: String
private lateinit var age: String//随后在某个地方初始化
init{
name = "jack"
}
}

判断是否初始化,通过获得字段的引用对象判断。

1
2
3
4
5
6
7
8
9
class A {
private lateinit var name: String

fun test() {
if(A::name.isInitialized) {
println("inited")
}
}
}

可见修饰符

对于类、对象、函数存在可见修饰符。注意,函数同时也包括了,构造函数,get/set函数等函数

public

默认不指定即为public,则随处可见

private

对于文件来说即使当前文件可见,对于类来说就当前类可见

internal

本模块可见,模块即一块儿编译的代码集合。反正android中项目模块对应这个

protected

本类 + 子类可见

扩展

扩展函数

kotlin允许为某个类进行静态增加函数。我们只需要以fun 类名.扩展函数名(){}进行书写即可,并且函数内部是可以获得到该对象的,即this

1
2
3
4
5
6
7
8
fun Int.sayHello() {
println("sayHello by " + this)
}

fun main() {
val age: Int = 10
age.sayHello()//输出
}

注意这只是静态的增加,并没有真正意义的为该类增加函数

扩展属性

其实就是扩展了set或者get函数,并不能插入字段。

1
2
val List.lastIndex: Int
get() = size + 1

伴生对象也可以扩展

1
2
3
4
5
6
7
8
9
class MyClass {
companion object { } // 将被称为 "Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }

fun main() {
MyClass.printCompanion()
}

数据类

通常在Java中我们需要创建大量的POJO类,同时也需要书写大量的模板代码,在kotlin中,我们直接将这种POJO类,使用数据类来定义。

1
data class Person(val name: String, val age: Int)

这个一行代码就完成了下面巴拉巴拉一串Java模板代码的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Peson {
private String name;
private int age;

public void setName(String name) {
this.name = name
}
public String getName() {
return this.name
}
public void setAge(String age) {
this.age = age
}
public String getAge() {
return this.age
}
}

即使时数据类,仍然还是Any的子类,所以含有equals()hashCode()toString()·以及一个克隆函数copy()

除了在构造函数申明的属性之外,还可以有在类结构中声明的,但是类结构中声明的不会加入计算toString、equals、hashCode中。

克隆

1
2
3
4
5
data class Name(val n: String)
fun main() {
val name = Name("jack")
val name2 = name.copy()
}

数据类与解构

即将数据类属性解析到量

1
2
3
4
5
6
data class User(val name: String, val age: Int)
fun main() {
val jack = User("jack", 20)
val (name, age) = jack//解构
println("name is $name and age is $age")
}

枚举类

基本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum class PlayMode(private val _value: Int) {
SINGLE(0), LOOP(1), SINGLE_LOOP(2), SHUFFLE(3);

val value: Int get() = _value

companion object {
fun valueOf(value: Int) = when (value) {
0 -> SINGLE
1 -> LOOP
2 -> SINGLE_LOOP
3 -> SHUFFLE
else -> LOOP
}
}
}

匿名

1
2
3
4
5
6
7
8
9
10
11
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},

TALKING {
override fun signal() = WAITING
};

abstract fun signal(): ProtocolState
}

继承

1
2
3
4
5
6
7
8
9
10
enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
PLUS {
override fun apply(t: Int, u: Int): Int = t + u
},
TIMES {
override fun apply(t: Int, u: Int): Int = t * u
};

override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}

嵌套类和内部类

因为在kotlin将面向对象概念深入了一下,即我们看不到所谓的静态方法啊、静态内部类啊等静态打头的名称。

嵌套类

这里的嵌套类其实工作方式相似与Java的静态内部类。不会持有外部引用,即单纯的嵌套的类,相当于在类的内部多了一个类的声明

1
2
3
4
5
6
7
class Outer {


class Nester {

}
}

内部类

这个内部类就相似于Java的内部类,需要加以额外的Inner关键字标志。这个内部类则是可以外部类的对象的引用

1
2
3
4
5
6
7
8
9
10
11
class Outer {

fun test() {
pirntln("Outer test")
}
inner class Inner {
fun test() {
this@Outer.test()
}
}
}

匿名内部类

使用对象表达式创建匿名的类实例。object

1
2
3
4
5
6
7
8
9
10
netMusicDataSource.loadPlaylistDetail(id, object : Callback<PlayListDetailResponse>{
override fun onLoaded(t: PlayListDetailResponse) {
cachePlayListDetail = t.playlist
callback.onLoaded(cachePlayListDetail!!)
}

override fun onFailed(mes: String) {
callback.onFailed(mes)
}
})

如果时函数式接口即只有一个函数。可以继续简写Observer{}这样类似的

1
2
3
4
5
6
7
8
musicDataLoading.observe(homeActivity, Observer { isLoading ->
view?.rv_list?.visibility = if (isLoading) View.INVISIBLE else View.VISIBLE
if (isLoading) {
view?.loading_view?.show()
} else {
view?.loading_view?.hide()
}
})

对象

Kotlin中的真的万物皆对象,静态的概念直接巧妙的转化为了对象。现在我多少有点能理解Js中的面向对象编程的继承方式是原型继承。

对象表达式

使用object进行继承类继而创建对象

1
2
3
4
5
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*……*/ }

override fun mouseEntered(e: MouseEvent) { /*……*/ }
})

有时候不需要继承,只是简单的聚合

1
2
3
4
val xy = object {
val x: Int = 10
val y: Int = 20
}

对象声明

声明式的创建一个有名称的对象。单例,就像声明一个类并且创建一个对象这句话。内容的写法和类相同但是没有构造函数了。而且创建过程是线程安全,延迟初始化

1
2
3
4
5
6
7
8
9
10
object Preference {
private val param: Int = 10
fun test() {
println("test : "+ param)
}
}

fun main() {
Preference.test()
}

伴生对象

在kotlin没有静态,于是出现了一个伴生对象的概念。类拥有伴生对象。使用对象声明描述,可以通过类名直接访问伴生对象成员。功能相似与Java的静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
companion object CompanionObject {
//
fun hello() {

}
}
}

fun main() {
A.hello()
A.CompanionObject.hello()
}

这个伴生对象是可以带名字的,同样也可以省略。

1
2
A.hello()
A.CompanionObject.hello()

两种访问方法是都是可以的

若是没有指定伴生对象的名称,则以Companion表示

1
A.Companion.hello()

倘若要想变成和Java一样的静态方法,需要在成员加上@JvmStatic就可以变成真正的静态成员

对象表达式和对象声明之间的语义差异

对象表达式和对象声明之间有一个重要的语义差别:

  • 对象表达式是在使用他们的地方立即执行(及初始化)的;
  • 对象声明是在第一次被访问到时延迟初始化的;
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。

类型别名

给类型去别名,简写或者区分

1
2
3
typealias NodeSet = Set<Network.Node>

typealias FileTable<K> = MutableMap<K, MutableList<File>>

内联类

委托

委托模式实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Base {
fun print()
}

class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
val b = BaseImpl(10)
Derived(b).print()
}

覆盖优先委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Base {
fun printMessage()
fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}

fun main() {
val b = BaseImpl(10)
Derived(b).printMessage()//abc
Derived(b).printMessageLine()//10
}

委托对象的成员只能访问其自身对接口成员实现

属性委托

将属性交由其他方式来实现,比如

  • 延迟属性:首次访问计算
  • 可观察属性:监听器会收到有关此属性变更的通知;
  • 映射:把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

import kotlin.reflect.KProperty

class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}


class Example {
var p: String by Delegate()
}


fun main() {
val e = Example()
println(e.p)
}
//output

//Example@33a17727, thank you for delegating ‘p’ to me!

而kotlin提供几种委托实现

延迟属性 Lazy

1
2
3
4
5
6
7
8
9
val lazyValue: String by lazy {
println("computed!")
"Hello"
}

fun main() {
println(lazyValue)
println(lazyValue)
}

lazy后仅执行一次,延迟

默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用 LazyThreadSafetyMode.NONE 模式:它不会有任何线程安全的保证以及相关的开销。

可观察属性 Observable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import kotlin.properties.Delegates

class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}

fun main() {
val user = User()
user.name = "first"
user.name = "second"
}

在值变化触发监听

如果你想截获赋值并“否决”它们,那么使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。

把属性储存在映射中

1
2
3
4
5
6
7
8
9
10
11
12
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}

val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))

println(user.name) // Prints "John Doe"
println(user.age) // Prints 25

这也适用于 var 属性,如果把只读的 Map 换成 MutableMap 的话:

1
2
3
4
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}

局部委托