Kotlin入门(二) —— 类、接口与对象篇

1. 可见性

  • private 只在该类(以及它的成员)中可见
  • protected 和 private 一样但在子类中也可见
  • internal 在本模块的所有可以访问到声明区域的均可以访问该类的所有 internal 成员
  • public 任何地方可见 当没写明可见性,默认为public
  • 特别的:外部类不可以访问内部类的 private 成员。

以上模块是指

  • an IntelliJ IDEA module;
  • a Maven or Gradle project;

2. 构造函数

类可以包含一个主构造函数和多个二级构造函数,完整声明如下。如果构造函数不包括可见性声明和注解,则constructor可以省略

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

主构造函数不能包含任意代码,初始化代码放在init{}代码块内

主构造函数可以直接初始化域变量,也可以(必须)直接带入超类的参数

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

二级构造函数需要constructor前缀
如果存在主构造函数,每一个二级构造函数都要直接调用,或间接通过另一个二级构造函数代理调用主构造函数,用this关键字来指代其他构造函数

class Person(val name: String) {
    constructor (name: String, paret: Person) : this(name) {
        parent.children.add(this)
    }
}

构造函数中的参数可以有默认值,类似于python,当有默认值的参数,new的时候,可以忽略而使用默认值
实际上Kotlin创建对象不使用new 直接使用 Person("Wang")

3. Kotlin的接口

Kotlin的接口允许包含了方法的实现,但和抽象类不同的是,接口不能保存状态。如下,

  • C实现A接口,bar()方法是抽象的,则强制需要重写实现它。
  • D同时实现A和B,对于bar()方法,因为B已经包含了实现,所以会默认选择B的实现;但因为A和B都包含了对foo()这个方法的实现,造成了冲突,所以强制重写此方法。
interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
}

4. 类继承和方法属性复写

  • kotlin 中坚持做明确的事,Kotlin的类默认下都是final的,需要加上open关键字才可被继承。而抽象类和接口则天生是open的。
  • 对于可以复写的方法,Kotlin也需要明确使用open来声明,并且重写他们
  • override的成员也是open的,如果想终结这个open,需要加final
  • 可以用var去覆盖一个val,但反之则不允许
  • 当继承的类和实现的接口包含同名同参方法,并且均有实现时,需要重新复写这个方法(参考接口)

5. 属性

属性可以有get set方法

var stringRepresentation: String
  get() = this.toString()
  set(value) {
    setDataFromString(value) // parses the string and assigns values to other properties
  }

备份域:

var counter = 0 // the initializer value is written directly to the backing field
  set(value) {
    if (value >= 0)
      field = value
  }

备份属性

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
  get() {
    if (_table == null)
      _table = HashMap() // Type parameters are inferred
    return _table ?: throw AssertionError("Set to null by another thread")
  }

对于成员属性,不管是空或非空,都不许再定义的时候初始化。

如果想延迟初始化,有以下方法:

val a : Any by Delegates.notNull()
var b : Any by Delegates.notNull()
lateinit var c : Any
val d : Any? by lazy {  }
val e : Any by lazy {  }

6. 枚举类

  • 实际上每一个枚举都是一个对象实例。
  • 此外,枚举类实现了Comparable接口,比较适使用的是枚举类的自然顺序进行比较
// 1 普通枚举类
enum class Direction {
    NORTH,SOUTH,WEST,EAST
}

// 2 可初始化的枚举类
enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

// 3 可以声明枚举实例自己的匿名类
enum class SwitchState {
    ON {
        override fun switch() = OFF
    },
    OFF{
        override fun switch() = ON
    };
    abstract fun switch(): SwitchState
}

7. 密封类 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 eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // the `else` clause is not required because we've covered all the cases
}

8. 代理属性

有些常用属性,其读写方法可以进行抽象(类似于Gson的属性封装),get set方法可以通过代理类进行封装,以后可以重用该代理。

class Delegate {
    fun get(thisRef: Any?, prop: PropertyMetadata): String {
        return "$thisRef, thank you for delegating '${prop.name}' to me !"
    }
    
    fun set(thisRef: Any?, prop: PropertyMatada, value: String) {
        println("$value has been assigned to '${prop.name} in $thisRef.'")
    }
}

class Example {
    var p: String by Delegate()
}
Kotlin提供的标准代理

kotlin.properties.Delegates 对象是标准库提供的一个工厂方法并提供了很多有用的代理

// 懒加载
val lazy: String by Delegates.lazy { // 如果你想要线程安全,使用 blockingLazy()
    println("computed!")
    "Hello"
}

// 观察者
var name: String by Delegates.observable("default") {
        d,old,new -> println("$old -> $new")
}
    
// NotNull
// 正常来说 非空的var不允许不初始化,也不允许用null初始化
class Foo {
    var bar: Bat //错误必须初始化
}
// 使用Delegates.notNull()可以作为替代进行null初始化,但如果在set之前get,则会抛出异常
class Foo {
    var bar: Bar by Delegates.notNull()
}

// 在Map中解析属性
class User(val map: Map<String, Any?>) {
    val name: String by Delegates.mapVal(map) // var 则可以用 mapVar
    val age: Int     by Delegates.mapVal(map)
}
9. 代理类

Kotlin提供代理类的方式去快速实现代理模式

interface Base {
    fun print()
}

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

class Derived(b: Base) : Base by b

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

10. 嵌套类

嵌套类可分为

  1. 一般嵌套类:这个跟Java差不多,不展开讲。区别是无法引用外部类的属性和方法
  2. 内部类:用inner修饰,可以引用外部类的属性和方法。但外部类无法使用内部类的private属性或方法
  3. 匿名内部类:使用对象表达式创建,参考下一条“对象声明”
class Outer {
    private val bar: Int = 1
    // 内部类
    inner class Inner {
        fun foo() = bar
    }
}

11. 对象声明

Kotlin中有3种可以进行声明的对象
  1. 匿名内部类
  2. 单例对象
  3. 伴随对象
// 类似于匿名内部类
button.setOnClickListener( object : OnClickListener {
    override fun onClick(v: View) {
        // do something
    }
})

// 单例
object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }
}

// 伴随对象 其中声明的方法和属性相当于是java的类里面声明的静态属性和静态方法,可以使用类名来直接引用
class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}

以上单例类似于java中的

public final class DataProviderManager {
    private DataProviderManager() {}
    public static DataProviderManager INSTANCE = new DataProviderManager();
    
    void registerDataProvider(DataProvider provider) {
        // ...
    }
}

12. 多重声明与data类

针对常用的数据存储类Java bean,Kotlin提供了更便捷的data类。data类的限制如下

  1. 主构造函数至少有一个参数
  2. 所有构造函数参数必须标注为val或var,即不能是局部变量
  3. 数据类不能使abstract open sealed 或 inner
  4. 不能继承,但能实现接口
  5. 如果在 JVM 中如果构造函数是无参的,则所有的属性必须有默认的值
  • data class User(val name: String = "", val age: Int = 0)
data class User(val name: String, val age: Int)
// toString() of the form "User(name=John, age=42)";

// copy() 便于原型模式的使用 
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

// componentN() 便于多重声明和多重返回,对应构造参数的声明顺序
val (name, age) = person
val name = persion.component1()
val age = persion.component2()

data class Result(val result: Int, val status: Status)

fun function(...): Result {
    //...
    return Result(result, status)
}

val (result, status) = function(...)
  • Kotlin标准库提供了 Pair 和 Triple 两种标准data类,但可读性不强不建议使用。其实可以作为多参数返回时使用。

13. 类的扩展

在 java 中,我们通常使用一系列名字为 "Utils" 的类: FileUtils,StringUtils等等。很有名的 java.util.Collections 也是其中一员的,但我们不得不像下面这样使用他们:

Collections.swap(list, 
                Collections.binarySearch(list, Collections.max(otherList)),
                Collections.max(list));

Kotlin类的扩展可以让我们可以使用以下方式去引用这样的工具类静态方法

list.swap(list.binarySearch(otherList.max()), list.max())

如对MutableList<T>使用类的扩展:

fun <T> MutableList<T>.swap(x: Int, y: Int) {
    val temp = this[x] // this 对应 list
    this[x] = this[y]
    this[y] = tmp
}

类的扩展需要注意以下几点:

  1. 类的扩展是被静态解析的,如果有同名同参数的成员函数和扩展函数,调用的时候必然会使用成员函数
  2. 被扩展的类甚至可以为Any?
  3. 可扩展的包括 方法、属性、伴随对象
  4. 扩展的作用域 仅当前包有效。其他包要使用某个扩展需要进行引用,如以上扩展,需要执行如下导入:
import x.x.x.swap
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容