类
构造函数
在Kotlin中一个类可以有一个主构造函数和一个或多个的次构造函数,主构造函数是类头的一部分
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的 不带参数的主构造函数,构造函数的可见性是 public
主构造函数
/**
* 主构造函数
*/
class Kotlin constructor(key: String, value: String) {
}
/**
* 省略了constructor关键字的主构造函数
* NOTE:这个主构造函数不能包含任何代码, 初始化代码可以放到init关键字的代码块
*/
class Computer(price: Int) {
var price: Int = 0
init {
this.price = price
}
}
/**
* 如果构造函数有注解修饰或者有可见性(public, private...)修饰符修饰
* 则必须有关键字constructor
*/
class Book public constructor(name: String) {
}
/**
* 没有类体的类,可以省略大括号
* NOTE: 同时声明了mobile为只读属性,brand参数为可变参数
*/
class Phone(val mobile: String, var brand: String)
/**
* 将构造函数私有
*/
class PrivateClass private constructor() {
}
次构造函数
- 次构造函数必须声明前缀
constructor
- NOTE: 如果一个类有主构造函数,则次构造函数必须委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托
/**
* 有主构造函数,也有次构造函数的类
*/
class Glass(val name: String) {
var mName: String = ""
var mBand: String = ""
init {
this.mName = name
}
constructor(name: String, brand: String) : this(name) {
this.mName = name
this.mBand = brand
}
}
创建类实例
- Kotlin没有
new
关键字
var phone: Phone = Phone("Kotlin")
继承
- Kotlin中所有类都有一个超类
Any
(类似于Java的Object
, 但不等同于Object) - Kotlin中所有类默认都是不可以继承的(即所有类都是final), 如果想让一个类可以被继承,可以使用关键字
open
- 如果父类有主构造函数,子类必须使用父类的朱构造函数就地初始化
- 如果父类没有主构造函数,但是有次构造函数,则子类的每个次构造函数必须使用
super
关键字初始化其类型,或者委托给另一个构造函数
/**
* 指明该类可以被继承
*/
open class Human(name: String) {
var mName: String = ""
init {
this.mName = name
}
/**
* 显示的标注该方法可以被覆盖重写
*/
open fun method() {
}
/**
* 该方法不可以被重写
*/
fun method1() {
}
}
/**
* 用父类的主构造函数就地初始化
*/
class Man(name: String) : Human(name) {
/**
* 重写父类的method方法
*/
override fun method() {
super.method()
}
}
open class Woman(name: String) : Human(name) {
/**
* 禁止Woman的子类覆盖重写该方法
*/
final override fun method() {
super.method()
}
}
/**
* super关键字
*/
class MyView : View {
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
}
}
- 如果一个类从它的直接父类继承相同成员的多个实现,它必须覆盖这个成员,并提供自己的实现,可以通过
super<?>
的方式指定采用哪个父类的实现
open class A {
open fun foo() {
// ...
}
}
interface B {
/**
* 有方法体
*/
fun foo() {
// ...
}
}
/**
* A没有提供构造函数,所以有默认的空的主构造函数
* 这里必须使用空构造函数来就地初始化
*/
class C : A(), B {
override fun foo() {
super<A>.foo()
/**
* 这里如果要调用interface的foo, B.foo必须有方法体(即实现了该方法)
* 如果只是简单的声明方法(和Java中interface的用法一样),则这里无法直接调用
* super<B>.foo()
*/
super<B>.foo()
}
}
- 抽象类可以不用
open
关键字修饰
open class Base {
open fun foo() {}
}
abstract class Child : Base() {
override abstract fun foo()
}
密封类
- 类似于
Enum
类,是Enum
类功能的扩展 - 密封类使用关键字
sealed
修饰 - 密封类的所有子类必须嵌套声明在该密封类的内部, 不过子类的子类不用必须声明在密封类内部,可以在任何地方声明
使用密封类的关键好处在于使用
when
表达式 的时候,如果能够 验证语句覆盖了所有情况,就不需要为该语句再添加一个else
子句了
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
/* 对象声明 */
object NotANumber: Expr()
}
fun testExpr(expr: Expr): Double = when (expr) {
is Expr.Const -> expr.number
is Expr.Sum -> testExpr(expr.e1) + testExpr(expr.e2)
Expr.NotANumber -> Double.NaN
}
声明属性
-
val
代表只读属性,不允许setter
-
var
代表可变属性 - 声明一个属性的完整语法是:
var <propertyName>: <propertyType> [= <property_initializer>]
[<getter>]
[<setter>]
class Test(size: Int) {
/**
* 定义getter和setter
*/
var mSize: Int?
get() = this.mSize
set(value) {
this.mSize = value
}
init {
this.mSize = size
}
/**
* 只读变量只有getter
*/
val isEmpty: Boolean
get() = this.mSize == 0
/**
* 改变setter的可见性,但不改变默认的视线
*/
var setterVisibility: String = "abc"
private set
/**
* 可以使用field标识符来访问属性,field标识符只能用在属性的访问器内
*/
var counter = 0
set(value) {
if (value >= 0)
field = value
}
}
编译期常量
编译期常量,可以用const
关键字标识某个属性为编译期常量,一个编译期常量必须具有以下要求:
- 在top-level声明(直接在包内声明)或是一个object的成员
- String或者基础类型,而且有初始值
- 没有自定义的getter方法
const val DEPRECIATED: String = "depreciated"
class Const {
@Deprecated(DEPRECIATED) fun foo(){ }
}
延迟初始化
在Kotlin中,被定义为非空类型的属性,都需要初始化,如果想之后通过某个方法再对该属性进行初始化,可以使用lateinit
修饰符
class LateInit {
lateinit var mName: String
fun setup(name: String) {
mName = name
}
/**
* 通过依赖注入的方式初始化
*/
lateinit var mChild: Child
fun setChild(child: Child) {
this.mChild = child
}
}
NOTE: 对于lateinit
有以下几点限制:
- 只能用在
var
类型的可变属性 - 被修饰的属性不可以有自定义的
getter
和setter
- 属性必须是非空的
- 不可以用在基本类型上
接口
- Kotlin中的接口即可以有方法的声明,也可以包含方法的实现
- 接口的属性要么是抽象的,要么提供
getter
interface MyInterface {
val property: Int //抽象属性
val test: String //提供访问器的属性
get() = "test"
/**
* 只是声明
*/
fun foo()
/**
* 有实现
*/
fun bar() {
}
}
class Impl: MyInterface {
override val property: Int
get() = 10
override fun foo() {
val str = test
Log.d("tag", str)
}
}
Override冲突
如果实现多个接口,接口方法名同名时,可以用super<?>
来解决
interface A1 {
fun foo() {
}
}
interface B1 {
fun foo() {
}
}
class C1: A1, B1 {
override fun foo() {
super<A1>.foo()
super<B1>.foo()
}
}
可见性
Kotlin有四种可见性修饰符:
- public
- private
- protected
- internal
默认的修饰符是public
局部变量不允许有可见性修饰符
顶层声明
直接在包内声明叫做顶层声明,函数,属性,类,对象,接口都可以在顶层声明
protected
不适用于顶层声明
package test
//只在当前文件内可见
private fun test() {}
//随处可见
public var str: String = "str"
//test随处可见, 但是test的setter方法只在当前kt文件内可见
public var test: String = "test"
private set
//对同一模块内的文件都可见
internal var test1: Int = 1
类和接口中可见性
在类和接口中,四个可见性修饰符的意义是:
-
public
随处可见 -
private
只在这个类内部可见 -
protected
只在这个类内部以及其子类中可见 -
internal
对于同属于一个模块的都可见
模块的定义
模块在Kotlin中就是一系列的Kotlin文件编译在一起
- 一个IntelliJ IDEA module
- a Maven or gradle project
- a set of files compiled with one invocation of the Ant task
扩展函数
如果要给一个已经定义好的类新增一个函数,可以使用扩展函数功能
扩展函数并没有给类新增成员,仅仅是通过该类的实例去调用这个新函数
/**
* 给MutableList扩展一个swpa函数
*/
fun <T> MutableList<T>.swap(i1: Int, i2: Int) {
val tmp = this[i1]
this[i1] = this[i2]
this[i2] = tmp
}
/**
* 调用扩展的swap函数
*/
fun test1() {
val list = mutableListOf<Int>(1, 2, 3)
list.swap(0, 2)
}
属性扩展
扩展属性只能通过明确的setter
和getter
进行定义,不可以直接进行初始化
class Test1
class Test2 {
var Test1.name: String
get() = "kotlin"
set(value) {
name = value
}
fun test2() {
var test1 = Test1()
test1.name = "haha"
}
}
嵌套类
在类的内部可以嵌套其他的类:
class Out {
private val outStr: String = "out"
//并不能直接访问Out的属性和方法
class In {
fun foo() = 2
}
/**
* 内部类会带有外部类的引用, 可以访问外部类的属性和方法
*/
inner class Inner {
fun foo() = outStr
}
}
内部类用inner
标记,内部类会带有一个来自外部类的对象的引用