温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何在Kotlin中使用扩展函数和扩展属性

发布时间:2021-05-16 11:10:55 来源:亿速云 阅读:224 作者:Leah 栏目:移动开发

如何在Kotlin中使用扩展函数和扩展属性?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。

public class Utils {

 public static float dp2px(int dpValue) {
  return (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
 }
}

在代码中直接调用 Utils.dp2px(100) 来使用,

val dp2px = Utils.dp2px(100)

如果用kotlin扩展函数的方式来实现,会是怎么调用呢?

val dp2px = 100.dp2px()

是不是很惊讶,100作为一个Int,竟然直接调用了一个dp2px方法,如果你去源码里找找,其实是没有个方法的。我们没有动源码,而是使用拓展函数的方式为Int增加了一个方法。

fun Int.dp2px(): Float {
 return (0.5f + this * Resources.getSystem().displayMetrics.density)
}

扩展函数

我们再来举个?,有一个Person类如下

class Person(val name: String) {

 fun eat() {
  Log.i(name, "I'm going to eat")
 }

 fun sleep() {
  Log.i(name, "I'm going to sleep")
 }

}

它有两个方法,一个是 eat 、一个是 sleep,调用的话就分别打印相应的Log。我们现在不想动Person类,但是又想给他增加一个新的方法,怎么做呢。我们可以新建一个文件 PersonExtensions.kt,再通过一下代码实现,就可以为 Person类新增一个 drink 方法啦。

fun Person.drink() {
 Log.i("Person", "${this.name}: I'm going to drink")
}

声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。上面我们就是以 Person 作为一个扩展函数的接收类型,为其拓展来 drink 方法。我们在其方法中调用了 this ,这个 this 指的就是调用这个拓展方法的当前 Person 对象。

如何在Kotlin中使用扩展函数和扩展属性

扩展函数调用的话也和普通的方法相同。但是你会发现IDE显示的方法颜色有点不一样。

如何在Kotlin中使用扩展函数和扩展属性

由此也可以看出普通的方法和我们的拓展函数是不同的。下面我们来看看扩展函数的实际实现。

在 Android Studio 中,我们可以查看 kotlin 文件的字节码,然后再 Decompile 为 Java 代码。上面我们为 Person 扩展函数转为Java代码后如下。

@Metadata(
 mv = {1, 1, 15},
 bv = {1, 0, 3},
 k = 2,
 d1 = {"\u0000\f\n\u0000\n\u0002\u0010\u0002\n\u0002\u0018\u0002\n\u0000\u001a\n\u0010\u0000\u001a\u00020\u0001*\u00020\u0002¨\u0006\u0003"},
 d2 = {"cook", "", "Lcom/chaochaowu/kotlinextension/Person;", "app_debug"}
)
public final class PersonExtensionsKt {
 public static final void cook(@NotNull Person $this$cook) {
  Intrinsics.checkParameterIsNotNull($this$cook, "$this$cook");
  Log.i("Person", $this$cook.getName() + ": I'm going to cook");
 }
}

妹想到啊,它原来是一个 static final 声明的静态方法,它的入参是一个 Person 类型,也就是我们之前的接收类型。那在Java代码中能不呢调用呢?

PersonExtensionsKt.cook(new Person("Bob"));

竟然也没有报错!由此可见,所谓扩展函数并不是真正的在类中增加了一个方法,而是通过外部文件的静态方法来实现,其实就是和Utils类一个道理。

因为将一个 Person 作为入参传入了方法中,所以我们也就可以在方法内对这个 Person 对象进行操作,这也就是在扩展方法中我们可以使用 this 来访问 Person 属性的原因。

再来看一个特殊的例子。

val s: String? = null
s.isNullOrEmpty()

上面的代码中,s的值为null,我们用null去调用了一个方法,这会不会报错呢?按照以前的经验,一个null去调用一个方法,必然会报空指针的异常,但是上面的代码却是不会崩的。为什么哩?

其实 isNullOrEmpty 是 CharSequence? 的一个扩展方法,我们可以看一下它的源码。

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
 contract {
  returns(false) implies (this@isNullOrEmpty != null)
 }

 return this == null || this.length == 0
}

contract这个契约方法这边我们不需要注意,不影响。主要是看 return this == null || this.length == 0 这句话。它先是判断了 this 是否为空,然后再判断this 的长度。根据我们上面讲的扩展函数的本质,我们可以很好的理解,为什么null可以调用这个方法的原因。因为上面的代码转为 Java 代码后是这样子的。

 public static final boolean isNullOrEmpty(@Nullable CharSequence $this$isNullOrEmpty) {
  int $i$f$isNullOrEmpty = 0;
  return $this$isNullOrEmpty == null || $this$isNullOrEmpty.length() == 0;
 }

我们在用null调用这个扩展方法时,其实是将null作为一个参数传入这个方法中,先判断参数是否为null,再进行下一步判断,这当然不会崩溃。

扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数,并将自身作为参数传入。

扩展属性

扩展属性和扩展函数类似,再举上面Person 的例子,我们对 Person 类稍作修改,为其增加 birthdayYear 字段,表示其出生的年份。

class Person(val name: String, val birthdayYear: Int) {

 fun eat() {
  Log.i(name, "I'm going to eat")
 }

 fun sleep() {
  Log.i(name, "I'm going to sleep")
 }

}

我们现在要为 Person 增加年龄 age 的属性,但是不改 Person 类,怎么实现呢。和扩展函数一样,在其他文件中声明如下。

const val currentYear = 2019

val Person.age: Int
 get() = currentYear - this.birthdayYear

我们通过当前年份减去生日年份计算出 Person 的年龄。可以看到,age 是一个属性,而不是方法。这样我们就为 Person 增加了一个扩展属性。可以看看它转化为 Java 代码后的样子,和扩展函数没啥区别。

@Metadata(
 mv = {1, 1, 15},
 bv = {1, 0, 3},
 k = 2,
 d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\b\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0086T¢\u0006\u0002\n\u0000\"\u0015\u0010\u0002\u001a\u00020\u0001*\u00020\u00038F¢\u0006\u0006\u001a\u0004\b\u0004\u0010\u0005¨\u0006\u0006"},
 d2 = {"currentYear", "", "age", "Lcom/chaochaowu/kotlinextension/Person;", "getAge", "(Lcom/chaochaowu/kotlinextension/Person;)I", "app_debug"}
)
public final class PersonExtensionsKt {
 public static final int currentYear = 2019;

 public static final int getAge(@NotNull Person $this$age) {
  Intrinsics.checkParameterIsNotNull($this$age, "$this$age");
  return 2019 - $this$age.getBirthdayYear();
 }
}

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI