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

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

前言

作为安卓开发,我们常常碰到这样的场景,需要把以dp为单位的值转化为以px为单位。这时候我们常会写一个Utils类,比如说

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,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 sleep")
 }

}

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

const val currentYear = 2019

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

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

@Metadata(
 mv = {1,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","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();
 }
}

上面我们声明的是一个 val,当然也可以声明一个 var,不过 var 的话需要同时定义其 get 和 set 方法。
由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。

总结

在 Java 中,我们要扩展一个类时,常常是继承该类或者用装饰者模式类似的设计模式来实现,Kotlin 扩展函数和扩展属性为这种需求提供了一种新思路,并且也可以作为 Utils 类的另外一种选择,值得一试。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory;
ImageView的scaleType的属性有好几种,分别是matrix(默认)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY。 |值|说明| |:--:|:--| |center|保持原图的大小,显示在ImageVie
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml<?xml version="1.0" encoding="utf-8"?><RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具
文章浏览阅读2.1k次。初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引擎可供使用,一边自暴自弃的抱怨Android平台游戏开发难度太高,又连个像样的游 戏引擎也没有,甚至误以为使用Java语言开发游戏是一件费力不讨好且没有出路的事情。事实上,这种想法完全是没有必_有素材的游戏引擎
文章浏览阅读3.2k次,点赞2次,收藏2次。2014年12月从csdn专家福利获得的一本书《Android游戏开发技术实战详解》,尘封了一年多的时间,今天才翻开来看。我认识中的Android,提到Android最先浮现在我脑海中的是那可爱的机器人图标:这个Logo是由Ascender公司设计的,诞生于2010年,其设计灵感源于男女厕所门上的图形符号(真的是灵感无处不在),于是布洛克绘制了一个简单的机器人,它的躯干就像锡罐的形状,头上还有两根_智能手机的特点有哪些?
文章浏览阅读8.1k次,点赞9次,收藏11次。首先,Android是不是真的找工作越来越难呢?这个可能是大家最关心的。这个受大的经济环境以及行业发展前景的影响,同时也和个人因素有关。2016-08-26近期一方面是所在的公司招聘Java开发人员很难招到合适的,投简历的人很少;而另一方面,经常听身边的人说Android、iOS方面找工作不好找,特别是没什么经验的,经验比较少的!说是不好找,但在我家所在的吉林省省会长春,会Unity3D+Maya_android 开发和asp.net哪个好 site:blog.csdn.net
文章浏览阅读6.1k次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;而如果没有手机或没有对应型号的手机,又想做对应型号(屏幕尺寸、Android系统版本)的适配,应该怎么办呢?这时Android模拟器就派上用场了。Android模拟器Android SDK自带一个移动模拟器。它是一个可以运行在你电脑上的_安卓移动开发软件怎样预览
文章浏览阅读8.9k次。Google IO 2017 上宣布,将Kotlin语言作为安卓开发的官方语言。Kotlin由JetBrains公司开发,与Java 100%互通,并具备诸多Java尚不支持的新特性。谷歌称还将与JetBrains公司合作,为Kotlin设立一个非盈利基金会。Kotlin 是一个基于 JVM 的静态类型编程语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JV_kotlin为什么被嫌弃
文章浏览阅读9.6w次,点赞17次,收藏35次。有些情况下,不方便使用断点的方式来调试,而是希望在控制台打印输出日志,使用过Eclipse的同学都知道Java可以使用 System.out.println(""); 来在控制台打印输出日志,但是在android studio中却是不行的,还是有差别的,那应该用什么呢?android.util.Log在调试代码的时候我们需要查看调试信息,那我们就需要用Android Log类。android.ut_andirod.studio 为什么不在控制台打印输出
文章浏览阅读8.2k次,点赞2次,收藏8次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;这里讲一下如何为应用添加一个按钮,并为按钮添加Click单击事件处理程序,显示/隐藏另一个按钮。添加按钮在HelloWorld项目的基础上,打开界面布局文件:activity_main.xml切换到Design(设计)模式;在组件But_activity_main.xml按钮隐藏
文章浏览阅读2.9k次,点赞3次,收藏9次。android 开发工具主流的还是Android Studio,当然也有很多人喜欢用Eclipse,也有人喜欢用IntelliJ IDEA ;还有Xamarin这种只需要编写一次代码,可以编译多种平台可运行的强大工具。但是它又真的强大吗?就我看来没有,身边很多人还是在用Android Studio、XCode开发应用,没见谁在用Xamarin之类的工具。系统要求WindowsMicrosoft®_android开发下载安装
文章浏览阅读4.2k次,点赞7次,收藏26次。你知道Hello World程序的由来吗?对于大多数编程语言的学习来说,真正入门的一课就是 Hello World!会而不难,难而不会。虽然很多人写过关于Android开发Hello World的文章,但随着时间的推移,开发工具、技术的进步,可能有些已经过时了。我就记录一下当下我所经历的第一个Android APP HelloWorld。一、准备1、开发环境参考:Android Studio 下载_android helloworld textview 句柄获取
这篇“android轻量级无侵入式管理数据库自动升级组件怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定...
今天小编给大家分享一下Android实现自定义圆形进度条的常用方法有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
这篇文章主要讲解了“Android如何解决字符对齐问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android...