一篇文章弄懂kotlin的扩展方法

Usage

扩展函数是 kotlin 的又一杀手锏功能,能够在不修改源码的基础上,扩展某些类的能力,方便开发。

例如这里演示了给 String 添加一个获取第一个元素的方法。

fun String.first(): Char {
  if (isEmpty()) {
    throw NoSuchElementException("String is empty")
  }
  return this[0]
}

fun main(args: Array<String>) {
  println("Hello,World".first())
}

这里需要额外注意的地方在于扩展函数的方法体中,是能够直接访问扩展对象 public 的变量的。例如上面的方法里面,我们也可以这么写:

fun String.first(): Char {
  if (length < 1) {
    throw NoSuchElementException("String is empty")
  }
  return this[0]
}

通过 this 可以在方法内,访问扩展对象,这里就是通过 this[0] 拿到第一个字符的。

Under in hood

看上去很厉害哈,但他的原理却非常简单。我们要时刻记住,kotlin JVM 是基于 JVM 开发的,kotlin 源码最后会变成字节码而后被运行。当遇到语法上不太懂的地方,直接反编译字节码,或者 Decompile 成 Java 方法,就能洞察里面的玄机。

我们将上述代码,Decompile 成 Java 后,就能发现里面的秘密。

public static final char first(@NotNull String $this$first){
  Intrinsics.checkParameterIsNotNull($this$first, "$this$first");
  if ($this$first.length() < 1) {
   throw (Throwable)(new NoSuchElementException("String is empty"));
  } else {
   return $this$first.charAt(0);
  }
}

原来是生成了一个 public static final 的方法呀,不过这个生成是 kotlin 提供的语法糖,帮我们完成的。看到这个代码,也解释了为什么在扩展对象方法内部,能够访问到扩展对象的 public 成员。

重载与多态

扩展方法能否被继承呢,或者重载呢?我们来看看例子

open class Animal
class Dog : Animal()

fun Animal.desc() = "Animal"
fun Dog.desc() = "Dog"

fun main(args: Array<String>) {
  println(Dog().desc())
  var animal: Animal = Dog()
  println(animal.desc())
}

// output:
// Dog
// Animal

如果扩展方法能够被重载,那么两次都应该输出 Dog,我们还是和前面方法一样,来看看真相。

@NotNull
public static final String desc(@NotNull Animal $this$desc) {
  Intrinsics.checkParameterIsNotNull($this$desc, "$this$desc");
  return "Animal";
}

@NotNull
public static final String desc(@NotNull Dog $this$desc) {
  Intrinsics.checkParameterIsNotNull($this$desc, "$this$desc");
  return "Dog";
}

可以看到实际生成了两个 desc 方法,里面的参数不动,所以这个方法的调用,只与扩展对象本身有关系,在编译时已经确定,不存在多态。

扩展属性

这是一个很神奇的设定,kotlin 并不能真的给扩展对象添加一个属性,而只是提供了一个语法糖,什么意思呢?我们具体看看下面这个例子。

var String.first: Char
  get() {
    if (isEmpty()) {
      throw NoSuchElementException(“String is empty”)
    }
    return this[0]
  }
  set(value) {
    println(“set value to $value”)
  }

fun main() {
  “Hello, World”.first = ‘G'
  println(“Hello,World”.first)
}

我们扩展了 kotlin 的属性,添加了一个 first。我们可以分别给这个所谓的 first 属性,注意是所谓的,添加 get 和 set 方法。然后我们可以通过 = 和 . 来调用 set 和 get 方法,就像 main 方法中那样。但实际上,最后并没有生成 first 属性,我们来看看反编译过后的代码。

public static final Char getFirst(@NotNull String $this$first) {
  Intrinsics.checkParameterIsNotNull($this$first, "$this$first");
  CharSequence var1 = (CharSequence)$this$first;
  boolean var2 = false;
  if (var1.length() == 0) {
   throw (Throwable)(new NoSuchElementException("String is empty"));
  } else {
   return $this$first.charAt(0);
  }
}

public static final void setFirst(@NotNull String $this$first, char value) {
  Intrinsics.checkParameterIsNotNull($this$first, "$this$first");
  String var2 = "set value to " + value;
  boolean var3 = false;
  System.out.println(var2);
}

看到没有,实际上只是添加了 setFirst 和 getFirst 两个方法,并没有实际的属性添加上去。这也是 kotlin 提供给我们的语法糖之一,糖要吃,但也要小心蛀牙哦!

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

时间: 2019-09-08

Kotlin中常见内联扩展函数的使用方法教程

前言 Kotlin一个强大之处就在于它的扩展函数,巧妙的运用这些扩展函数可以让你写出的代码更加优雅,阅读起来更加流畅,下面总结了在开发中经常用到的一些内联扩展函数.经常有小伙伴搞不懂with,run,apply等等这些函数该怎么用,在哪里用,我的建议是先记住每个函数的功能(无非就是它需要什么参数?返回值是什么?)记住这两点再根据实际开发中的场景慢慢的就能熟练运用了.其实这些函数极其类似,不同的函数可以完成同样的功能,通过下面的实例也能看出.而在我以往的开发经验中这些函数主要的使用场景有两个,一是

Kotlin中的扩展函数与属性示例详解

前言 Kotlin 中类的扩展方法并不是在原类的内部进行拓展,通过反编译为Java代码,可以发现,其原理是使用装饰模式,对源类实例的操作和包装,其实际相当于我们在 Java中定义的工具类方法,并且该工具类方法是使用调用者为第一个参数的,然后在工具方法中操作该调用者: 理论上来说,扩展函数很简单,它就是一个类的成员函数,不过定义在类的外面.让我们来添加一个方法,来计算一个字符串的最后一个字符: package strings /** * @author :Reginer in 2018/5/22

Kotlin基础教程之dataclass,objectclass,use函数,类扩展,socket

Kotlin基础教程之dataclass,objectclass,use函数,类扩展,socket Kotlin提供了一些机制来扩展已有的类,如下: 还记得我们之前写过的Point3D类吗?(将其略作修改,将成员变量改为Double类型) 让我们为其扩展一个length函数 扩展的方法很简单,只要在函数名前面加上类名就行了. 这样Point3D的对象就有了一个名为length的方法. 运行的结果不出所料: 除此之外,在Kotlin中还有一些特殊的类,比如Data Class: 有些类只包含数据,

Kotlin语法学习-变量定义、函数扩展、Parcelable序列化等简单总结

Kotlin语法学习-变量定义.函数扩展.Parcelable序列化等简单总结 今年 Google I/O 2017 开发者大会中,Google 宣布正式把 Kotlin 纳入 Android 程序的官方一级开发语言(First-class language),作为Android开发者,当然要逐步熟悉这门语言,第一步就要从语法开始学习. 在这之前,我们需要了解怎么使用Kotlin编写一个Android应用.对于Android Studio 3.0版本,我们在创建工程的时候直接勾选 Include

Kotlin扩展函数及实现机制的深入探索

前言 2017年Google IO大会宣布使用Kotlin作为Android的官方开发语言,相比较与典型的面相对象的JAVA语言,Kotlin作为一种新式的函数式编程语言,也有人称之为Android平台的Swift语言. 先让我们看下实现同样的功能,Java和Kotiln的对比: // JAVA,20多行代码,充斥着findViewById,类型转换,匿名内部类这样的无意义代码 public class MainJavaActivity extends Activity { @Override

Kotlin 扩展函数和扩展属性的使用方法

Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式. 这通过叫做 扩展 的特殊声明完成. 例如,你可以为一个你不能修改的.来自第三方库中的类编写一个新的函数. 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用. 这种机制称为 扩展函数 .此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性. 前言 作为安卓开发,我们常常碰到这样的场景,需要把以dp为单位的值转化为以px为单位.这时候我们常会写一个Utils类,比如说 public cl

Kotlin中的反射机制深入讲解

前言 Java中的反射机制,使得我们可以在运行期获取Java类的字节码文件中的构造函数,成员变量,成员函数等信息.这一特性使得反射机制被常常用在框架中,想要比较系统的了解Kotlin中的反射,先从Java的反射说起. Java中的反射 通常我们写好的.java源码文件,经过javac的编译,最终生成了.class字节码文件.这些字节码文件是与平台无关的,使用时通过Classloader去加载这些.class字节码文件,从而让程序按照我们编写好的业务逻辑运行.Java的反射主要是从这些.class

Kotlin中的Checked Exception机制浅析

前言 现在使用Kotlin的Android开发者已经越来越多了. 这门语言从一开始的无人问津,到后来成为Android开发的一级语言,再到后来Google官宣的Kotlin First.Kotlin正在被越来越多的开发者接受和认可. 许多学习Kotlin的开发者之前都是学习过Java的,并且本身Kotlin就是一款基于JVM语言,因此不可避免地需要经常和Java进行比较. Kotlin的诸多特性,在熟悉Java的开发者看来,有些人很喜欢,有些人不喜欢.但即使是不喜欢的那些人,一旦用熟了Kotli

Android在Kotlin中更好地使用LitePal

Kotlin 是一个用于现代多平台应用的静态编程语言,由 JetBrains 开发. Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行. Kotlin已正式成为Android官方支持开发语言. 自从LitePal在2.0.0版本中全面支持了Kotlin之后,我也一直在思考如何让LitePal更好地融入和适配Kotlin语言,而不仅仅停留在简单的支持层面. Kotlin确实是一门非常出色的语言,里面有许多优秀的特性是在Java中无法实现的.因此,

利用Kotlin的方式如何处理网络异常详解

一. 前言 之前的文章 RxJava处理业务异常的几种方式 曾经介绍过 Retrofit 的异常可以有多种处理方式. 其中,可以使用 RxJava 的错误处理操作符,它们是专门用来处理异常的. 随便例举两个操作符: onErrorReturn 操作符,表示当发生错误的时候,发射一个默认值然后结束数据流.所以 Subscriber 看不到异常信息,看到的是正常的数据流结束状态. onErrorResumeNext 操作符,表示当错误发生的时候,使用另外一个数据流继续发射数据.在返回的被观察者中是看

Android 中 MD5 的几种生成方式(小结)

Java 提供了 MessageDigest 类用来处理消息摘要算法,如 MD5 , SHA-1 和 SHA-256 等. 1. Java 的标准实现 使用位运算进行进制转换. import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Hash { private static final char[] hexCode = "0123456789ABCDEF&qu