Kotlin系列之扩展函数

简述: 今天带来的是Kotlin浅谈系列的第五弹,这讲主要是讲利用Kotlin中的扩展函数特性让我们的代码变得更加简单和整洁。扩展函数是Kotlin语言中独有的新特性,利用它可以减少很多的样板代码,大大提高开发的效率;此外扩展函数的使用也是非常简单的。我会从以下几个方面阐述Kotlin中的扩展函数。

  • 1、为什么要使用Kotlin中的扩展函数?
  • 2、怎么去使用扩展函数和扩展属性?
  • 3、什么是扩展函数和属性?
  • 4、扩展函数和成员函数区别
  • 5、扩展函数不可以被重写

一、为什么要使用Kotlin中的扩展函数

我们都知道在Koltin这门语言可以与Java有非常好的互操作性,所以扩展函数这个新特性可以很平滑与现有Java代码集成。甚至纯Kotlin的项目都可以基于Java库,甚至Android中的一些框架库,第三方库来构建。扩展函数非常适合Kotlin和Java语言混合开发模式。在很多公司一些比较稳定良好的库都是Java写,也完全没必要去用Kotlin语言重写。但是想要扩展库的接口和功能,这时候扩展函数可能就会派上用场。使用Kotlin的扩展函数还有一个好处就是没有副作用,不会对原有库代码或功能产生影响。先来看下扩展函数长啥样

  • 给TextView设置加粗简单的例子
//扩展函数定义
fun TextView.isBold() = this.apply { 
	paint.isFakeBoldText = true
}

//扩展函数调用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()

二、怎么去使用扩展函数和扩展属性

  • 1、扩展函数的基本使用

只需要把扩展的类或者接口名称,放到即将要添加的函数名前面。这个类或者名称就叫做接收者类型,类的名称与函数之间用"."调用连接。this指代的就是接收者对象,它可以访问扩展的这个类可访问的方法和属性。

Kotlin系列之扩展函数_第1张图片

注意: 接收者类型是由扩展函数定义的,而接收者对象正是这个接收者类型的对象实例,那么这个对象实例就可以访问这个类中成员方法和属性,所以一般会把扩展函数当做成员函数来用。

  • 2、扩展属性的基本使用
    扩展属性实际上是提供一种方法来访问属性而已,并且这些扩展属性是没有任何的状态的,因为不可能给现有Java库中的对象额外添加属性字段,只是使用简洁语法类似直接操作属性,实际上还是方法的访问。
//扩展属性定义
var TextView.isBolder: Boolean
	get() {//必须定义get()方法,因为不能在现有对象添加字段,也自然就没有了默认的get()实现
		return this.paint.isFakeBoldText
	}
	set(value) {
		this.paint.isFakeBoldText = true
	}
//扩展属性调用
activity.find<TextView>(R.id.course_comment_tv_score).isBolder = true

注意:

  • 扩展属性和扩展函数定义类似,也有接收者类型和接收者对象,接收者对象也是接收者类型的一个实例,一般可以把它当做类中成员属性来使用。

  • 必须定义get()方法,在Kotlin中类中的属性都是默认添加get()方法的,但是由于扩展属性并不是给现有库中的类添加额外的属性,自然就没有默认get()方法实现之说。所以必须手动添加get()方法。

  • 由于重写了set()方法,说明这个属性访问权限是可读和可写,需要使用var

三、什么是扩展函数和属性

我们从上面例子可以看出,kotlin的扩展函数真是强大,可以毫无副作用给原有库的类增加属性和方法,比如例子中TextView,我们根本没有去动TextView源码,但是却给它增加一个扩展属性和函数。具有那么强大功能,到底它背后原理是什么?其实很简单,通过decompile看下反编译后对应的Java代码就一目了然了。

  • 1、扩展函数实质原理

扩展函数实际上就是一个对应Java中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的。

public final class ExtendsionTextViewKt {//这个类名就是顶层文件名+“Kt”后缀,这个知识上篇博客有详细介绍
   @NotNull
   public static final TextView isBold(@NotNull TextView $receiver) {//扩展函数isBold对应实际上是Java中的静态函数,并且传入一个接收者类型对象作为参数
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);//设置加粗
      return $receiver;//最后返回这个接收者对象自身,以致于我们在Kotlin中完全可以使用this替代接收者对象或者直接不写。
   }
}
  • 2、Java中调用Kotlin中定义的扩展函数

分析完Kotlin中扩展函数的原理,我们也就很清楚,如何在Java中去调用Kotlin中定义好的扩展函数了,实际上使用方法就是静态函数调用,和我们之前讲的顶层函数在Java中调用类似,不过唯一不同是需要传入一个接收者对象参数。

ExtendsionTextViewKt.isBold(activity.findViewById(R.id.course_comment_tv_score));//直接调用静态函数
  • 3、扩展属性实质原理

扩展属性实际上就是提供某个属性访问的set,get方法,这两个set,get方法是静态函数,同时都会传入一个接收者类型的对象,然后在其内部用这个对象实例去访问和修改对象所对应的类的属性。

public final class ExtendsionTextViewKt {
   //get()方法所对应生成静态函数,并且传入一个接收者类型对象作为参数
   public static final boolean isBolder(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.getPaint().isFakeBoldText();
   }
   //set()方法所对应生成静态函数,并且传入一个接收者类型对象作为参数和一个需要set的参数
   public static final void setBolder(@NotNull TextView $receiver, boolean value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);
   }
}
  • 4、Java中调用Kotlin中定义的扩展属性

Java调用Kotlin中定义的扩展属性也很简单,就相当于直接调用生成的set(),get()方法一样。

    ExtendsionTextViewKt.setBolder(activity.findViewById(R.id.course_comment_tv_score), true);

四、扩展函数和成员函数区别

说到扩展函数和成员函数的区别,通过上面例子我们已经很清楚了,这里做个归纳总结:

  • 1、扩展函数和成员函数使用方式类似,可以直接访问被扩展类的方法和属性。(原理: 传入了一个扩展类的对象,内部实际上是用实例对象去访问扩展类的方法和属性)
  • 2、扩展函数不能打破扩展类的封装性,不能像成员函数一样直接访问内部私有函数和属性。(原理: 原理很简单,扩展函数访问实际是类的对象访问,由于类的对象实例不能访问内部私有函数和属性,自然扩展函数也就不能访问内部私有函数和属性了)
  • 3、扩展函数实际上是一个静态函数是处于类的外部,而成员函数则是类的内部函数。
  • 4、父类成员函数可以被子类重写,而扩展函数则不行

五、扩展函数不可以被重写

在Kotlin和Java中我们都知道类的成员函数是可以被重写的,子类是可以重写父类的成员函数,但是子类是不可以重写父类的扩展函数。

open class Animal {
    open fun shout() = println("animal is shout")//定义成员函数
}

class Cat: Animal() {
    override fun shout() {
        println("Cat is shout")//子类重写父类成员函数
    }
}

//定义子类和父类扩展函数
fun Animal.eat() = println("Animal eat something")

fun Cat.eat()= println("Cat eat fish")

//测试
fun main(args: Array<String>) {
    val animal: Animal = Cat()
    println("成员函数测试: ${animal.shout()}")
    println("扩展函数测试: ${animal.eat()}")
}

运行结果:

Kotlin系列之扩展函数_第2张图片

以上运行结果再次说明了扩展函数并不是类的一部分,它是声明与类外部的,尽管子类和父类拥有了相同的扩展函数,但是实际上扩展函数是静态函数。从编译内部来看,子类和父类拥有了相同的扩展函数,实际上就是定义两个同名的静态扩展函数分别传入父类对象和子类对象,那么调用的方法肯定也是父类中的方法和子类中的方法,所以输出肯定是父类的。

1635c3fb0ba21ec1?w=430&h=430&f=jpeg&s=39536

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

Kotlin系列文章,欢迎查看:

Kotlin邂逅设计模式系列:

  • 当Kotlin完美邂逅设计模式之单例模式(一)

数据结构与算法系列:

  • 每周一算法之二分查找(Kotlin描述)

翻译系列:

  • [译] Kotlin中关于Companion Object的那些事
  • [译]记一次Kotlin官方文档翻译的PR(内联类)
  • [译]Kotlin中内联类的自动装箱和高性能探索(二)
  • [译]Kotlin中内联类(inline class)完全解析(一)
  • [译]Kotlin的独门秘籍Reified实化类型参数(上篇)
  • [译]Kotlin泛型中何时该用类型形参约束?
  • [译] 一个简单方式教你记住Kotlin的形参和实参
  • [译]Kotlin中是应该定义函数还是定义属性?
  • [译]如何在你的Kotlin代码中移除所有的!!(非空断言)
  • [译]掌握Kotlin中的标准库函数: run、with、let、also和apply
  • [译]有关Kotlin类型别名(typealias)你需要知道的一切
  • [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?
  • [译]Kotlin中的龟(List)兔(Sequence)赛跑

原创系列:

  • 教你如何完全解析Kotlin中的类型系统
  • 如何让你的回调更具Kotlin风味
  • Jetbrains开发者日见闻(三)之Kotlin1.3新特性(inline class篇)
  • JetBrains开发者日见闻(二)之Kotlin1.3的新特性(Contract契约与协程篇)
  • JetBrains开发者日见闻(一)之Kotlin/Native 尝鲜篇
  • 教你如何攻克Kotlin中泛型型变的难点(实践篇)
  • 教你如何攻克Kotlin中泛型型变的难点(下篇)
  • 教你如何攻克Kotlin中泛型型变的难点(上篇)
  • Kotlin的独门秘籍Reified实化类型参数(下篇)
  • 有关Kotlin属性代理你需要知道的一切
  • 浅谈Kotlin中的Sequences源码解析
  • 浅谈Kotlin中集合和函数式API完全解析-上篇
  • 浅谈Kotlin语法篇之lambda编译成字节码过程完全解析
  • 浅谈Kotlin语法篇之Lambda表达式完全解析
  • 浅谈Kotlin语法篇之扩展函数
  • 浅谈Kotlin语法篇之顶层函数、中缀调用、解构声明
  • 浅谈Kotlin语法篇之如何让函数更好地调用
  • 浅谈Kotlin语法篇之变量和常量
  • 浅谈Kotlin语法篇之基础语法

Effective Kotlin翻译系列

  • [译]Effective Kotlin系列之考虑使用原始类型的数组优化性能(五)
  • [译]Effective Kotlin系列之使用Sequence来优化集合的操作(四)
  • [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)
  • [译]Effective Kotlin系列之遇到多个构造器参数要考虑使用构建器(二)
  • [译]Effective Kotlin系列之考虑使用静态工厂方法替代构造器(一)

实战系列:

  • 用Kotlin撸一个图片压缩插件ImageSlimming-导学篇(一)
  • 用Kotlin撸一个图片压缩插件-插件基础篇(二)
  • 用Kotlin撸一个图片压缩插件-实战篇(三)
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用

你可能感兴趣的:(Kotlin,Kotlin,扩展函数)