类与继承
类的基本结构
一个普通类应当包含属性、函数,构造函数,基本的样子
以下三种都是合规的申明。
1 | class A |
主构造函数
一个对象产生必定经过类的构造函数进行构造。在类名后面使用constructor
进行标志,并且添加构造参数。这就是主构造函数
1 | class A constructor(name: String) { |
同时constructor
前面是可以写入访问控制符和注解的
1 | class A private constructor(name: String) { |
如果没有注解或者是访问修饰符。则就是可以略去constructor
1 | class A(name: String) { |
主构造函数看出来是只有申明的构造参数,那么在哪里进行使用这些构造参数呢,Kotlin为我们准备了一个init初始块。就相当于init就是主构造函数的函数体了。
1 | class A(name: String) { |
我们可以在init
块进行初始参数的初始化操作。或者直接赋值到属性。如果是赋值到属性的话可以继续简写
1 | class A(private val name: String) { |
等同于
1 | class A(name: String) { |
就相当于直接将属性直接写到构造函数里了
次构造函数
次构造函数则相对与在类名主构造函数而言的。同样也是使用constructor
来申明,同时次构造函数必须委托给主构造函数,也即是要调用主构造函数,使用后面加上:this()
来进行委托。同样次构造函数之间存在重载,允许有多个
1 | class A(val name: String) { |
创建实例
1 | val a = A("jack") |
类的成员
- 函数
- 成员函数
- 构造函数
- 属性
- 普通属性
- 对象声明即匿名类对象
- 伴生对象
- 类
- 嵌套类
- 内部类
init
块
继承
kotlin的继承是单继承的
所有的类都是Any的子类,这个和Java的Object很相似,但是只包含了equals()
、hashCode()
、toString()
这三个函数。
默认情况下使用class
进行声明的类都是不可被继承,相对与final类。若想被继承需要加上open
关键字
使用冒号语法在类名后加上继承或者要实现的类或接口,如果父类的有主构造函数,需要显示调用。如下所示
1 | interface I { |
覆盖函数
在继承中就会涉及到覆盖重写的问题。一个方法需要配重写,则也是使用open
进行修饰。而覆盖的函数需要加上override
修饰。而override
本身是继续开放的,需要关闭继承加上final
1 | open class A{ |
覆盖属性
类似覆盖函数,同样使用open和override进行。
1 | open class A { |
注意var可以覆盖val,反之不行
调用超类
使用super
关键字
1 | open class A { |
以及父类或者接口具有同名覆盖方法时
1 | open class A { |
以及当环境不同时,比如在内部类中要访问外部类的超类实现。通过@限定来实现。
1 | super@Outer.func() |
this也有同样的使用方式。调用外部类实现
1 | this@Outer.func() |
接口和抽象类
注意:接口方法是和Java8一样是可以存在默认实现的
1 | abstract class A{ |
属性与字段
其实本来这两者是作为方便使用,可以看成是对应着Java里的字段和get/set方法。只不过这里的话通过声明属性,可以不需要手动写get/set函数了。
字段
首先字段其实就是类中的一个量。通过var
和val
,因为默认下kotlin的非空元素,所以必须要声明时指定值,或设置懒初始化。
1 | class A { |
属性
其实属性和字段的区别就是,属性 = 字段 + get / set 函数。而此时这个属性在内部使用存储标志就是field
关键字标志的幕后字段。
1 | class A { |
同样我们可以不用幕后字段,使用我们自己的字段,即用private
声明的量
1 | class A { |
其实说白了就是将get/set和变量放到了一起,在不自定义时,使用默认的的get/set实现以消除模板代码。对于属性的访问也是通过get/set函数进行访问获取修改的。
如果还是糊涂的可以,将上述的例子进行反编译,看看java,会发现特别简单。就是帮助写模板的get/set函数的。
延迟初始化
val
的量一定要一开始初始化,要么直接赋值,要么在init
块中初始化
var
的量要么一开始初始化赋值,要么在前面加上lateinit
指定随后在某个地方延迟初始化。
1 | class A { |
判断是否初始化,通过获得字段的引用对象判断。
1 | class A { |
可见修饰符
对于类、对象、函数存在可见修饰符。注意,函数同时也包括了,构造函数,get/set函数等函数
public
默认不指定即为public,则随处可见
private
对于文件来说即使当前文件可见,对于类来说就当前类可见
internal
本模块可见,模块即一块儿编译的代码集合。反正android中项目模块对应这个
protected
本类 + 子类可见
扩展
扩展函数
kotlin允许为某个类进行静态增加函数。我们只需要以fun 类名.扩展函数名(){}
进行书写即可,并且函数内部是可以获得到该对象的,即this
1 | fun Int.sayHello() { |
注意这只是静态的增加,并没有真正意义的为该类增加函数
扩展属性
其实就是扩展了set或者get函数,并不能插入字段。
1 | val List.lastIndex: Int |
伴生对象也可以扩展
1 | class MyClass { |
数据类
通常在Java中我们需要创建大量的POJO类,同时也需要书写大量的模板代码,在kotlin中,我们直接将这种POJO类,使用数据类来定义。
1 | data class Person(val name: String, val age: Int) |
这个一行代码就完成了下面巴拉巴拉一串Java模板代码的功能
1 | class Peson { |
即使时数据类,仍然还是Any的子类,所以含有equals()
、hashCode()
、toString()
·以及一个克隆函数copy()
除了在构造函数申明的属性之外,还可以有在类结构中声明的,但是类结构中声明的不会加入计算toString、equals、hashCode中。
克隆
1 | data class Name(val n: String) |
数据类与解构
即将数据类属性解析到量
1 | data class User(val name: String, val age: Int) |
枚举类
基本
1 | enum class PlayMode(private val _value: Int) { |
匿名
1 | enum class ProtocolState { |
继承
1 | enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator { |
嵌套类和内部类
因为在kotlin将面向对象概念深入了一下,即我们看不到所谓的静态方法啊、静态内部类啊等静态打头的名称。
嵌套类
这里的嵌套类其实工作方式相似与Java的静态内部类。不会持有外部引用,即单纯的嵌套的类,相当于在类的内部多了一个类的声明
1 | class Outer { |
内部类
这个内部类就相似于Java的内部类,需要加以额外的Inner
关键字标志。这个内部类则是可以外部类的对象的引用
1 | class Outer { |
匿名内部类
使用对象表达式创建匿名的类实例。object
1 | netMusicDataSource.loadPlaylistDetail(id, object : Callback<PlayListDetailResponse>{ |
如果时函数式接口即只有一个函数。可以继续简写Observer{}
这样类似的
1 | musicDataLoading.observe(homeActivity, Observer { isLoading -> |
对象
Kotlin中的真的万物皆对象,静态的概念直接巧妙的转化为了对象。现在我多少有点能理解Js中的面向对象编程的继承方式是原型继承。
对象表达式
使用object进行继承类继而创建对象
1 | window.addMouseListener(object : MouseAdapter() { |
有时候不需要继承,只是简单的聚合
1 | val xy = object { |
对象声明
声明式的创建一个有名称的对象。单例,就像声明一个类并且创建一个对象这句话。内容的写法和类相同但是没有构造函数了。而且创建过程是线程安全,延迟初始化
1 | object Preference { |
伴生对象
在kotlin没有静态,于是出现了一个伴生对象的概念。类拥有伴生对象。使用对象声明描述,可以通过类名直接访问伴生对象成员。功能相似与Java的静态成员
1 | class A { |
这个伴生对象是可以带名字的,同样也可以省略。
1 | A.hello() |
两种访问方法是都是可以的
若是没有指定伴生对象的名称,则以Companion
表示
1 | A.Companion.hello() |
倘若要想变成和Java一样的静态方法,需要在成员加上@JvmStatic
就可以变成真正的静态成员
对象表达式和对象声明之间的语义差异
对象表达式和对象声明之间有一个重要的语义差别:
- 对象表达式是在使用他们的地方立即执行(及初始化)的;
- 对象声明是在第一次被访问到时延迟初始化的;
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
类型别名
给类型去别名,简写或者区分
1 | typealias NodeSet = Set<Network.Node> |
内联类
委托
委托模式实现继承
1 | interface Base { |
覆盖优先委托
1 | interface Base { |
委托对象的成员只能访问其自身对接口成员实现
属性委托
将属性交由其他方式来实现,比如
- 延迟属性:首次访问计算
- 可观察属性:监听器会收到有关此属性变更的通知;
- 映射:把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
1 |
|
而kotlin提供几种委托实现
延迟属性 Lazy
1 | val lazyValue: String by lazy { |
lazy后仅执行一次,延迟
默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION
作为参数传递给 lazy()
函数。 而如果你确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用 LazyThreadSafetyMode.NONE
模式:它不会有任何线程安全的保证以及相关的开销。
可观察属性 Observable
1 | import kotlin.properties.Delegates |
在值变化触发监听
如果你想截获赋值并“否决”它们,那么使用 vetoable()
取代 observable()
。 在属性被赋新值生效之前会调用传递给 vetoable
的处理程序。
把属性储存在映射中
1 | class User(val map: Map<String, Any?>) { |
这也适用于 var 属性,如果把只读的 Map
换成 MutableMap
的话:
1 | class MutableUser(val map: MutableMap<String, Any?>) { |