ITKeyword,专注技术干货聚合推荐

注册 | 登录

Kotlin 开发实践 面向对象 类特性

smartbetter 分享于 2017-06-30

推荐:面向对象语言的特性

  面向对象的基本概念 对象 类 消息 抽象 面向对象主要特征(关系) 封装性 继承 多态 转自:http://blog.chinaitlab.com/html/53/1868853-173419.html 1.抽象  

2019阿里云双12.12最低价产品入口(新老用户均可),
地址https://www.aliyun.com/minisite/goods

转载请注明出处:http://blog.csdn.net/smartbetter/article/details/73656551

大家好,上一篇主要从 Kotlin 的数据类型出发,完整的学习了 Kotlin 的数据类型。之后从本篇开始,会真正讲述 Kotlin 语言的神奇之处。与 Java 相同,Kotlin 声明类、抽象类与接口的关键字分别是 class、abstract 与 interface 。类声明由类名、类头和类体构成。其中类头和类体都是可选的; 如果一个类没有类体,那么花括号也是可以省略的。

1.类的构造函数

1.主构造函数

Kotlin 的主构造函数可以写在类头中,跟在类名后面,如果有注解还需要加上关键字 constructor。下面我们为 Person 创建带一个 String 类型参数的主构造函数:

class Person(private var name: String) {
    init {
        name = "zhangsan"
    }

    internal fun printName() {
        println("name $name")
    }
}

在主构造函数中不能有任何代码实现,额外的代码需要放到 init 代码块中执行。

2.次级构造函数

一个类可以有多个构造函数,但是只有主构造函数可以写在类头中,其他的次级构造函数就需要写在类体中了。

class Person(private var name: String) { private var description: String? = null init { name = "zhangsan" }
    // 让次级构造函数调用了主构造函数,完成 name 的赋值
    constructor(name: String, description: String) : this(name) { this.description = description }
    internal fun printName() { println("name $name") }
}

由于次级构造函数不能直接将参数转换为字段,所以需要手动声明一个 description 字段,并为 description 字段赋值。

3.修饰符

修饰符 含义 public 访问权限修饰符,公共的 internal 访问权限修饰符,模块内可见(IDEA 中一个 module 就是一个模块) protected 访问权限修饰符,受到保护的,子类可见 private 访问权限修饰符,私有的 open 确定这个类会被继承时添加本修饰符(在 Kotlin 中默认每个类都是不可被继承的)

2.继承与实现

类只能单继承,接口可以多实现。

abstract class Person(open val age: Int){
    abstract fun printAge()
}
class Student(age: Int): Person(age){ // 继承类时实际上调用了父类构造方法
    override val age: Int    // 覆写参数
        get() = 0
    override fun printAge() { // 覆写父类(接口)成员需要 override 关键字
        println(age)
    }
}
fun main(args: Array<String>) {
    val person: Person = Student(18)
    person.printAge()
    println(person.age)
}

输出结果为:

0
0

在这里我们看到了一个关键字 open,open 关键字可以用来允许一个类被继承,而且同样函数默认也是final的,不能被 override,要想重写父类函数,父类函数必须使用 open 定义。接口、接口方法、抽象类默认为 open 无须 open 定义。不仅如此,在Kotlin中,函数参数默认也都是final的。

3.抽象代理与属性代理

1.接口代理

// 普通讲师
class Teacher: Reader, Writer {
    override fun read() {
    }
    override fun write() {
    }
}

// 资深讲师
class SeniorTeacher(val reader: Reader, val writer: Writer): Reader by reader, Writer by writer // 接口代理

class BookReader: Reader {
    override fun read() {
        println("读书")
    }
}
class BookWriter: Writer {
    override fun write() {
        println("写书")
    }
}

interface Reader{
    fun read()
}
interface Writer{
    fun write()
}

fun main(args: Array<String>) {
    val reader = BookReader()
    val writer = BookWriter()
    val seniorManager = SeniorTeacher(reader, writer)
    seniorManager.read()
    seniorManager.write()
}

输出结果为:

读书
写书

在这里我们看到了一个关键字 by,by 关键字可以用来表示接口代理,除了接口代理,还有属性代理。

3.属性代理

class Delegates{
    val hello by lazy { // 属性代理,只有第一次访问到 hello 的时候才会被初始化为 "hello"
        "Hello"
    }
    val hello2 by X()
    var hello3 by X()
}

class X{ // 自己新建一个属性代理
    private var value: String? = null

    // 代理者需要实现相应的 setValue/getValue 方法
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("getValue: $thisRef -> ${property.name}")
        return value?: ""
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String){
        println("setValue, $thisRef -> ${property.name} = $value")
        this.value = value
    }
}

fun main(args: Array<String>) {
    val delegates = Delegates()
    println(delegates.hello)
    println(delegates.hello2)
    println(delegates.hello3)
    delegates.hello3 = "value of hello3"
    println(delegates.hello3)
}

输出结果为:

Hello
getValue: net.smartbetter.kotlindemo.Delegates@3d075dc0 -> hello2

getValue: net.smartbetter.kotlindemo.Delegates@3d075dc0 -> hello3

setValue, net.smartbetter.kotlindemo.Delegates@3d075dc0 -> hello3 = value of hello3
getValue: net.smartbetter.kotlindemo.Delegates@3d075dc0 -> hello3
value of hello3

2.接口方法冲突

下面再来看一种情况:

abstract class A{
    open fun x(): Int = 3
}
interface B{
    fun x(): Int = 2
}
interface C{
    fun x(): Int = 1
}

class D(var y: Int = 0): A(), B, C{
    override fun x(): Int { // 子类必须覆写冲突方法
        if(y > 100){
            return y
        }else if(y > 10){
            return super<A>.x()
        }else if(y > 1){
            return super<B>.x()
        }else{
            return super<C>.x()
        }
    }
}

fun main(args: Array<String>) {
    println(D(1000).x())
    println(D(100).x())
    println(D(10).x())
    println(D(1).x())
}

输出结果为:

1000
3
2
1

这样我们就解决掉两个签名一致且返回值类型相同的冲突(返回值不属于方法签名)。

4.方法重载与默认参数

此处需要先清楚方法覆写和重载的区别,方法覆写是重写了父类已经存在的方法,而重载是写了一个与父类方法方法名相同,参数不同的方法而已。

class Overloads{
    fun a(): Int{ // 返回值不属于方法签名,方法重载和返回值没有关系
        return 0
    }
    fun a(int: Int): Int{
        return int
    }
}

为了避免定义关系不大的重载,也可以添加默认参数:

class Overloads{
    @JvmOverloads  // 加上这个注解后 Java 中调用 Kotlin 也可传空参数
    fun a(int: Int = 0): Int{ // 当不传参数时候直接默认0
        return int
    }
}

扩展:Jvm 函数签名的概念:函数名、参数列表。

5.一些特殊的类

1.内部类

我们先来看一下在 Kotlin 中静态内部类与非静态内部类的使用(与Java 有一定的区别):

class Outter{
    val a: Int = 0;
    class Inner{ // 静态内部类
    }
    inner class Inner2{ // 非静态内部类
        val a: Int = 5;
        fun hello(){
            print(this.a) // 外部a用this@Outter.a,外部a只能非静态内部类访问到
        }
    }
}

fun main(args: Array<String>) {
    val inner = Outter.Inner()
}

我们再来看一个匿名内部类(实际上有类名,类名编译时生成,类似 Outter$1.class)的例子:

interface OnClickListener{
    fun onClick()
}

class View{
    var onClickListener: OnClickListener? = null
}

fun main(args: Array<String>) {
    val view = View()
    view.onClickListener = object : OnClickListener{ // 可继承父类、实现多个接口,与 Java 注意区别
        override fun onClick() {
        }
    }
}

2.枚举类

在 Kotlin 中,每个枚举常量都是一个对象。

enum class LogLevel(){ VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT }

fun main(args: Array<String>) { println(LogLevel.INFO.ordinal) println(LogLevel.INFO.name) }

如果关注的是代码质量,可以考虑使用枚举,如果你的程序是跑在嵌入式上的,那么更建议用整型来表示,比较省内存。

3.sealed 密封类

sealed 修饰的类称为密封类,用来表示受限的类层次结构。注意和枚举的区分,状态更适合用枚举,而指令则适合用密封类,密封类可以有效的保护我们的指令集,不被继承。

推荐:Kotlin中的面向对象(二)

在Kotlin中的面向对象(一)中,介绍了Kotlin类的相关操作,本文将在上文的基础上,继续介绍属性、接口等同样重要的面向对象的功能。 属性 class AttrDemo{

// 定义一个播放器的密封类,子类可数
sealed class Player {
    // 播放
    class Play(val url: String, val position: Long = 0): Player()
    // 停止
    object Stop: Player()
}

enum class PlayerState{
    IDLE, PLAYING // 空闲状态, 播放状态
}

Kotlin 版本小于 V1.1,子类必须定义为密封类的内部类,V1.1 及以上子类只需要与密封类在同一个文件中即可。

4.data 数据类

我们在 Java 中经常会用到 JavaBean,在 Kotlin 中也有语言级别的支持
——数据类,默认实现 copy、toString 等方法,还会自动将所有成员用 operator 声明(即为这些成员生成 getter/setter 方法):

data class Country(val id: Int, val name: String)

fun main(args: Array<String>) {
    val china = Country(0, "中国")
    println(china)

    println(china.component1())
    println(china.component2())

    val(id, name) = china
    println(id)
    println(name)
}

输出结果为:

Country(id=0, name=中国)
0
中国
0
中国

data class 是存在许多坑的,data class 默认是 final 的,并且默认不存在无参构造函数,如果直接使用 data class 替代 JavaBean,会出现很多莫名其妙的问题,为此官方出了两个插件 allOpen、noArg,解决这些问题,使用步骤如下:

1.打开 build.gradle 文件,编辑文件如下:

group 'net.smartbetter'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.1.2-5'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // allOpen、noArg 插件
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
    }
}

apply plugin: 'java'
apply plugin: 'kotlin'
// allOpen、noArg 插件
apply plugin: 'kotlin-noarg'
apply plugin: 'kotlin-allopen'

// allOpen、noArg 插件配置
// 意思是如果类被PoKo标注了,那么在编译的时候会把final去掉,同时也会生成默认的无参构造方法
noArg{
    annotation("net.smartbetter.kotlindemo.annotations.PoKo") // 名字无所谓
}
allOpen{
    annotation("net.smartbetter.kotlindemo.annotations.PoKo")
}

sourceCompatibility = 1.5

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

PoKo.kt 文件如下:

annotation class PoKo

2.为 data class 添加 @PoKo 注解:

@PoKo
data class Country(val id: Int, val name: String)

此时问题就解决了。

6.类的扩展

在 Java 开发的时候,经常会写一大堆的 Utils 类。如果每个类在想要用这些工具类的时候,他们自己就已经具备了这些工具方法多好,Kotlin的类扩展方法就是这个作用。

1.扩展方法

Kotlin 中使用 args.isEmpty() 判断 args 是否为空,而 isEmpty 方法也并不是定义在 Arrays.kt 中,而是定义在了 _Arrays.kt 中,isEmpty 其实就是一个扩展方法,扩展方法的定义和普通方法的定义非常相似:

public inline fun <T> Array<out T>.isEmpty(): Boolean {
    return size == 0 // this.size,this 指的就是 Array<out T>
}

下面我们自己定义一个扩展方法:

// 定义 String类 的扩展方法
operator fun String.times(int: Int): String{ // operator:运算符重载
    val stringBuilder = StringBuilder()
    for(i in 0 until int){
        stringBuilder.append(this)
    }
    return stringBuilder.toString()
}

// 定义 String类 的扩展成员属性
var String.a: Int
    set(value) {
    }
    get() = 5

fun main(args: Array<String>) {
    println("abc" * 3) // "abc".times(3)
    println("abc".a)
}

输出结果为:

abcabcabc
5

需要注意的是扩展方法是静态解析的,而并不是真正给类添加了这个方法。

8.伴生对象与静态成员

Kotlin 中也实现类似 Java 中的静态方法,可以使用包级函数、伴生对象、扩展函数和对象声明,需要大家根据不同的情况进行选择。官方推荐是包级函数,也有人说用伴生对象。

包级函数可以在包里面直接声明函数。伴生对象从语义上来讲,伴生函数与 Java 中静态方法最为相近,所以用伴生对象完全可以实现 Java 中静态类的所有内容。

// 伴生对象
class Latitude private constructor(val value: Double) {
    // 只有一个实例,静态方法
    companion object {
        @JvmStatic // 加上这个注解后,在 Java 类中就可以直接 Latitude.ofDouble 调用了
        fun ofDouble(double: Double): Latitude{
            return Latitude(double)
        }
        @JvmStatic
        fun ofLatitude(latitude: Latitude): Latitude{
            return Latitude(latitude.value)
        }
    }
    @JvmField
    val TAG: String = "Latitude" // 静态常量
}

fun main(args: Array<String>) {
    val a = minOf(args[0].toInt(), args[1].toInt()) // minOf 就是一个包级函数
    val latitude = Latitude.ofDouble(3.0)
}

每个类可以对应一个伴生对象,伴生对象的成员全局唯一,伴生对象的成员类似 Java 的静态成员。

静态成员考虑用包级函数、常量替代。

8.单例类设计

Kotlin 中使用 object 关键字声明一个单例对象,单例类不能自定义构造方法,本质上就是单例模式最基本的实现。

class Driver interface OnPlayerListener{ fun onMount(driver: Driver) fun onUnmount(driver: Driver) } abstract class Player object MusicPlayer: Player(), OnPlayerListener{ override fun onMount(driver: Driver) { } override fun onUnmount(driver: Driver) { } val state : Int = 0 fun play(url : String){ } fun stop(){ } }

9.动态代理与伪多继承

1.动态代理

Kotlin 原生支持动态代理,而 Java 的动态代理需要反射,而且需要额外多写很多的代码方法。在动态代理上,Kotlin 比 Java 爽太多。

interface Person{
    fun printName()
}

class Student :Person {
    override fun printName() {
        println("zhangsan")
    }
}

class Teacher(person: Person) : Person by person {
}

fun main(args: Array<String>) {
    Teacher(Student()).printName()
}

输出结果为:

zhangsan

这样,我们就让 Teacher 的 printName() 用 Student 去代理掉了。

2.伪多继承

Kotlin 的动态代理更多的是用在一种需要多继承的场景。使用一个代理类实现所有需要获取信息的接口方法。然后让不同的子类去实现所需的接口,请求统一交给代理类完成。这样不仅维护了请求信息方便,而且每个类不会有额外多的方法,防止新人接触项目时调错请求方法。

interface A{
    fun printA()
}

interface B{
    fun printB()
}

class Person : A, B {
    override fun printA() {
        println("printA")
    }

    override fun printB() {
        println("printB")
    }
}

class Teacher(a: A, b: B) : A by a, B by b {
}

fun main(args: Array<String>) {
    val person: Person = Person()
    Teacher(person, person).printA() // 输出 printA
}

Kotlin 的面向对象与类特性就介绍这么多。喜欢本文的记得顶一下。

推荐:javascript 面向对象特性与编程实现

在 06 年用 javascript 写过上千行的应用,现在的项目中经常用到 javascript ,说不熟悉吧也熟悉了。说熟悉吧, javascript 的面向对象部分还是比较陌生。实际上

转载请注明出处:http://blog.csdn.net/smartbetter/article/details/73656551 大家好,上一篇主要从 Kotlin 的数据类型出发,完整的学习了 Kotlin 的数据类型。之后从本篇开始,会真正讲述 Kot

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。