专栏首页Android群英传Kotlin之旅——类特性

Kotlin之旅——类特性

Kotlin 的类特性

写了两篇 PWA 了,今天继续讲回 Kotlin。 Kotlin 中有很多非常好的特性,扩展方法、伴生对象、原生支持动态代理、伪多继承。今天来详细讲讲。

类的扩展

在 Java 开发的时候,经常会写一大堆的 Utils 类,甚至经常写一些common包,比如著名的 apache-commons系列、Guava等等。 如果每个类在想要用这些工具类的时候,他们自己就已经具备了这些工具方法多好,Kotlin的类扩展方法就是这个作用。

扩展方法

在之前的文章中我就讲过扩展方法了,这里就不再多赘述,只回顾一下扩展方法的格式:

fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

首先是一个fun关键字,紧接着是要扩展哪个类的类名,点方法名,然后是方法的声明和返回值以及方法体。

小心有坑

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

open class Animal{

}
class Dog : Animal()

object Main {
    fun Animal.bark() = "animal"

    fun Dog.bark() = "dog"

    fun Animal.printBark(anim: Animal){
        println(anim.bark())
    }

    @JvmStatic fun main(args: Array<String>) {
        Animal().printBark(Dog())
    }
}

最终的输出是 animal,而不是dog。 因为扩展方法是静态解析的,在添加扩展方法的时候类型为Animal,那么即便运行时传入了子类对象,也依旧会执行参数中声明时类型的方法。

强转与智能转换

Kotlin 中,用 is 来判断一个对象是否是某个类的实例,用 as 来做强转。

Kotlin 有一个很好的特性,叫 智能转换(smart cast),在我之前的文章中也提到过。就是当已经确定一个对象的类型后,可以自动识别为这个类的对象,而不用再手动强转。

fun main(args: Array<String>) {
    var animal: Animal? = null
    if (animal is Dog) {
        //在这里你必须手动强转为Dog的对象
       animal.bark()
    }
}

总有例外

如果智能转换的对象是一个全局变量,这个变量可能在别的地方被改变赋值,所以你必须手动判断与转换它的类型。

open class Animal {
}

class Dog : Animal() {
    fun bark() {
        println("animal")
    }
}

var animal: Animal? = null

fun main(args: Array<String>) {
    if (animal is Dog) {
        //在这里你必须手动强转为Dog的对象
       (animal as Dog).bark()
    }
}

伴生对象

在上一篇 Kotlin 与 Java 互转 中 我们提到这样一段工具类代码

class StringUtils {
    companion object {
       fun isEmpty(str: String): Boolean {
            return "" == str
        }
    }
}

由于 Kotlin 没有静态方法。在大多数情况下,官方建议是简单地使用 包级 函数。如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法或单利),你可以把它写成一个用 companion修饰的对象内的方法。我们称companion修饰的对象为伴生对象。

将上面的代码编译后查看,实际上是编译器生成了一个public的内部对象。

public final class StringUtils public constructor() {
    public companion object {
        public final fun isEmpty(str: kotlin.String): kotlin.Boolean { 
        /* compiled code */
        }
    }
}

单例类设计

伴生对象更多的用途是用来创建一个单例类。如果只是简单的写,直接用伴生对象返回一个 val 修饰的外部类对象就可以了,但是更多的时候我们希望在类被调用的时候才去初始化他的对象。以下代码将线程安全问题交给虚拟机在静态内部类加载时处理,是一种推荐的写法:

class Single private constructor() {
    companion object {
        fun get():Single{
            return Holder.instance
        }
    }

    private object Holder {
        val instance = Single()
    }
}

动态代理

写多继承还是要根据场景来,正好今天跟朋友聊到他们项目重构的问题,我当时就说了一句:果然还是Kotlin好,原生支持动态代理。

朋友的一个 Android 项目,所有网络请求包括回调和参数全部封装在了一个 BaseActivity 中,然后随着项目越来越大,这一些网络请求方法想要抽出来,但又害怕牵连到线上的改动,我就推荐他用个动态代理来做,但是 Java 的动态代理又得要反射,又得要额外多写很多的代码方法,又是一个大改动。

反之看Kotlin的动态代理:

interface Animal{
    fun bark()
}

class Dog :Animal {
    override fun bark() {
        println("Wang Wang")
    }
}

class Cat(animal: Animal) : Animal by animal {
}

fun main(args: Array<String>) {
   Cat(Dog()).bark()
}

这样,我们就很成功的让一只猫的叫声用狗去代理掉了,于是上面的main方法执行完后就变成了 Wang Wang。

伪多继承

Kotlin 的动态代理更多的是用在一种需要多继承的场景。 例如,还是之前我举的我朋友那个项目的例子,他们的问题在于,每个 BaseActivity 的子类,都会要请求不同的网络,可能A需要获取用户信息,B需要获取活动列表,C既需要活动列表也需要获取用户信息,D却只需要获取图片列表。

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

还是用猫狗来举例:

interface Animal{
    fun bark()
}

interface Food{
    fun eat()
}

class Delegate : Animal, Food {
    override fun eat() {
        println("mouse")
    }

    override fun bark() {
        println("Miao")
    }
}

class Cat(animal: Animal, food: Food) : Animal by animal, Food by food {
}

@JvmStatic fun main(args: Array<String>) {
    val delegate: Delegate = Delegate()
    Cat(delegate, delegate).bark()
}

Kotlin 的类就介绍这么多,下一章我们讲:集合与泛型

本文分享自微信公众号 - Android群英传(android_heroes),作者:张涛

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-03-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 他山之石,可以攻玉

    用户1907613
  • Flutter Dojo设计之道——如何打造一个通用的Playground

    Dojo的设计之初,是为了能够演示Flutter中,多如牛毛的Widget,所以,一个通用的Demo演示界面,就显得非常有必要了,一是可以节省很多通用的代码,二...

    用户1907613
  • 基于XDanmuku的Android性能优化实战

    用户1907613
  • 损失函数详解

    在任何深度学习项目中,配置损失函数是确保模型以预期方式工作的最重要步骤之一。损失函数可以为神经网络提供很多实际的灵活性,它将定义网络的输出如何与网络的其他部分连...

    AiTechYun
  • 要做好深度学习任务,不妨先在损失函数上「做好文章」

    损失函数对于机器学习而言,是最基础也最重要的环节之一,因此在损失函数上「做好文章」,是一个机器学习项目顺利进行的前提之一。Deep Learning Demys...

    AI科技评论
  • 《机器学习基础》(第二版)免费下载!纽约大学14年教学精华

    MIT出版社出版的《机器学习基础》(第二版)PDF和HTML资源均已免费开放下载。距离第一版出版已有6年之久。

    新智元
  • 腾讯云中国香港服务器价格

    腾讯云香港云主机价格一年二百多元,是腾讯云的一个优惠活动,仅限新用户购买,老用户不能享受这个价格。

    用户5908769
  • 今日作业 -- 用js控制DIV的显示隐藏

    这个题很简单的,就是用一个按钮切换二个菜单的显示和隐藏。比昨晚的查找重复字符串要简单的多。 参与写作业的同学们基本都写的不错,思路也都OK,用原生JS,jQue...

    web前端教室
  • Elastic认证工程师到底有没有用?

    没过英语四六级,你说没用,多半是心虚,我身边有些同学朋友,考了好多年,最多有10几次不过四六级的,一边考试,一边安慰自己:没什么用,企业不看这个,重在参与!

    铭毅天下
  • 网易云api解析音乐直链

    该方法非常简单 直链通过一个api获取如果需要用于实际网页中就可能出现跨域问题。 为了避免我们可以使用反代。 比如某第三方 api:https://bird.i...

    乔千

扫码关注云+社区

领取腾讯云代金券