jjzjj

Kotlin-扩展(Extension)的能力边界在哪?

大虾啊啊啊 2023-03-28 原文

1、扩展函数

我们对String定义一个扩展函数

//对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
fun String.lastElement(): Char? {
    if (this.isEmpty()) {
        return null
    }
    return this[length - 1]
}
fun main() {
    val a = "Hello World"
    println(a.lastElement())
}

扩展函数我们定义在kotlin文件中,称之为顶层扩展,任何地方都可以使用,转成Java代码看实现


public final class ExtentionKt {
   @Nullable
   public static final Character lastElement(@NotNull String $this$lastElement) {
      Intrinsics.checkNotNullParameter($this$lastElement, "$this$lastElement");
      CharSequence var1 = (CharSequence)$this$lastElement;
      boolean var2 = false;
      return var1.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
   }

   public static final void main() {
      String a = "Hello World";
      Character var1 = lastElement(a);
      boolean var2 = false;
      System.out.println(var1);
   }

}

其实实现很简单,定义了一个在ExtentionKt 类中定义了一个lastElement静态方法并传入String对象。在调用的时候,其实就是传入了我们的字符串a即可。然后再算出最后一个字符。
所以 Kotlin 编译器会将扩展函数转换成对应的静态方法,而扩展函数调用处的代码也会被转换成静态方法的调用。

2、扩展属性

//对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
fun String.lastElement(): Char? {
    if (this.isEmpty()) {
        return null
    }
    return this[length - 1]
}
//定义扩展属性
val String.firstElement: Char?
    get() = if (isEmpty()) null else this[0]
fun main() {
    val a = "Hello World"
    println(a.lastElement())
    println(a.firstElement)

}

转成Java代码看实现

public final class ExtentionKt {
   @Nullable
   public static final Character lastElement(@NotNull String $this$lastElement) {
      Intrinsics.checkNotNullParameter($this$lastElement, "$this$lastElement");
      CharSequence var1 = (CharSequence)$this$lastElement;
      boolean var2 = false;
      return var1.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
   }

   @Nullable
   public static final Character getFirstElement(@NotNull String $this$firstElement) {
      Intrinsics.checkNotNullParameter($this$firstElement, "$this$firstElement");
      CharSequence var1 = (CharSequence)$this$firstElement;
      boolean var2 = false;
      return var1.length() == 0 ? null : $this$firstElement.charAt(0);
   }

   public static final void main() {
      String a = "Hello World";
      Character var1 = lastElement(a);
      boolean var2 = false;
      System.out.println(var1);
      var1 = getFirstElement(a);
      var2 = false;
      System.out.println(var1);
   }
   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

从Java代码中我们可以看到,最终也是转成一个静态的getFirstElement方法,其实我们很容易理解,当我们的属性firstElement 设置成val 非私有的时候,对应的Java代码就会默认持有getter方法,也就是我们的getFirstElement。当我们定义了扩展属性,那么对应的Java代码就会转成相应的静态的getter方法。

3、扩展的能力边界?

从以上我们转成Java便可以知道,kotlin中不管的是扩展函数还是扩展属性对应都是Java中的静态方法,也就是主要用于替代Java 中的各种工具类。例如我们的工具类主要是操作字符串,那么我们就对String类进行扩展

扩展能做什么?

在Kotlin中几乎所有的类都可以扩展、主要用途是取代Java中的各种工具类,StringUtils等等。当我们对某个类进行扩展成员的时候,扩展的成员实际上不是真正的成员,但是我们在编译器中有智能的提示,这样一来就方便开发。

扩展不能做什么?

  • 扩展不是真正的成员,无法被子类重写
  • 扩展属性无法存储状态
    如:
//定义扩展属性
val String.firstElement: Char?
    get() = if (isEmpty()) null else this[0]

其实很容易理解,扩展属性转成Java代码其实就对应了静态的getter方法而已,具体的值由getter方法返回值决定

  • 扩展的访问作用域
    (1)定义处的成员
    (2)接受者类型的公开成员
    如:
private val name = "dog";
//定义扩展属性
val String.firstElement: Char?
    get() = if (isEmpty()) {
        null
    } else {
        println(name)
        println(this.length)
        this[0]
    }

name成员虽然是私有的,但是定义在和扩展属性firstElement同一个文件,因此是可以访问到name 的,否则不能
其次就是访问被扩展类型的公开成员。如以上的例子访问String的length 通过this.length,如果length是私有的则不能访问。
以上我们定义的是顶层扩展,如果我们在某个类中进行扩展呢?

class Pig {
    //对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
    fun String.lastElement(): Char? {
        if (this.isEmpty()) {
            return null
        }
        return this[length - 1]
    }

    private val name = "dog";
    //定义扩展属性
    val String.firstElement: Char?
        get() = if (isEmpty()) {
            null
        } else {
            println(name)
            println(this.length)
            this[0]
        }

    fun testDemo(){
        val a = "Hello World"
        println(a.lastElement())
        println(a.firstElement)
    }
}

转成Java代码


public final class Pig {
   private final String name = "dog";

   @Nullable
   public final Character lastElement(@NotNull String $this$lastElement) {
      Intrinsics.checkParameterIsNotNull($this$lastElement, "$this$lastElement");
      CharSequence var2 = (CharSequence)$this$lastElement;
      boolean var3 = false;
      return var2.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
   }

   @Nullable
   public final Character getFirstElement(@NotNull String $this$firstElement) {
      Intrinsics.checkParameterIsNotNull($this$firstElement, "$this$firstElement");
      CharSequence var2 = (CharSequence)$this$firstElement;
      boolean var3 = false;
      Character var10000;
      if (var2.length() == 0) {
         var10000 = null;
      } else {
         String var4 = this.name;
         var3 = false;
         System.out.println(var4);
         int var5 = $this$firstElement.length();
         var3 = false;
         System.out.println(var5);
         var10000 = $this$firstElement.charAt(0);
      }

      return var10000;
   }

   public final void testDemo() {
      String a = "Hello World";
      Character var2 = this.lastElement(a);
      boolean var3 = false;
      System.out.println(var2);
      var2 = this.getFirstElement(a);
      var3 = false;
      System.out.println(var2);
   }
}

其实非常容易了理解,当扩展成员定义在类中,那么只能在类中访问
因此可知:
如果是顶层扩展,是可以被全局使用的,扩展成员的访问作用域限于所在文件的所有成员,以及被扩展类型的公开成员
如果是在类中扩展,那么只能在类中使用,扩展成员的访问作用域为该类的成员,以及被扩展类型的公开成员

4、实战与思考

以上我们分析了扩展函数常用于替代Java中的各种工具类,仅此而已吗?
我们来看下Kotlin中的String

public class String : Comparable<String>, CharSequence {
    companion object {}
    
    public override fun get(index: Int): Char

    public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence

    public override fun compareTo(other: String): Int
}

我们看到String中只有几个核心方法,而平时我们使用到的这么多API,都在Strings这个顶层扩展文件中,


image.png

由此可知,在优化我们的项目架构的时候,我们可以将核心方法写在类中,非核心方法我们可以通过扩展的方式实现分离。

第二就是对于SDK中的代码,我们在使用的时候,总是要写很多公共的模板代码,如果我们使用Java可以想到把他封装成一个静态工具类,在Kotlin我们便可以通过扩展成员的方式进行封装。虽然说扩展的方式转换成Java代码也是通过静态方法进行封装,但是对于我们开发中却非常方便,开发中编译器就类似把扩展成员当做真正的成员,智能提示。
如下代码:

当我们要设置View的margin的时候,总要写很多模板代码

    val param = textView?.layoutParams as ViewGroup.MarginLayoutParams
    param.bottomMargin = 10
    param.topMargin = 10
    param.marginStart = 10
    param.marginEnd = 10
    textView.layoutParams = param

那么我们便可以通过扩展的方式,使得代码更加方便

fun View.setMargin(left: Int, top: Int, right: Int, bottom: Int) {
    ( layoutParams as ViewGroup.MarginLayoutParams).let { 
        it.bottomMargin = bottom
        it.topMargin = top
        it.marginStart = left
        it.marginEnd = right
    }
}
fun main() {
   val textView: TextView? = null
    textView?.setMargin(10, 20, 30, 50)

    val button: Button? = null
    button?.setMargin(10, 20, 30, 50)
}

这样针对所有的View我们都可以使用。

5、小结

  • 扩展我们可以用于替代Java中的一些工具类
  • 我们可以将核心方法写在类中,非核心成员写在扩展成员中
  • 当我们调用一些外部SDK中的函数总是要写很多模板方法,我们便可以对SDK类进行扩展
  • 扩展成员不是真正的成员,因此不能继承,不能保存数据,其底层最终也是封装成了静态的方式实现
  • 顶层扩展在任何地方都可以使用,类中扩展只能在类中使用,因此我们一般使用底层扩展

有关Kotlin-扩展(Extension)的能力边界在哪?的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. ruby-on-rails - 错误 : Error installing pg: ERROR: Failed to build gem native extension - 2

    我克隆了一个rails仓库,我现在正尝试捆绑安装背景:OSXElCapitanruby2.2.3p173(2015-08-18修订版51636)[x86_64-darwin15]rails-v在您的Gemfile中列出的或native可用的任何gem源中找不到gem'pg(>=0)ruby​​'。运行bundleinstall以安装缺少的gem。bundleinstallFetchinggemmetadatafromhttps://rubygems.org/............Fetchingversionmetadatafromhttps://rubygems.org/...Fe

  3. c - mkmf 在编译 C 扩展时忽略子文件夹中的文件 - 2

    我想这样组织C源代码:+/||___+ext||||___+native_extension||||___+lib||||||___(Sourcefilesarekeptinhere-maycontainsub-folders)||||___native_extension.c||___native_extension.h||___extconf.rb||___+lib||||___(Rubysourcecode)||___Rakefile我无法使此设置与mkmf一起正常工作。native_extension/lib中的文件(包含在native_extension.c中)将被完全忽略。

  4. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

  5. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  6. ruby - 如何在 ruby​​ 中复制目录结构,不包括某些文件扩展名 - 2

    我想编写一个ruby​​脚本来递归复制目录结构,但排除某些文件类型。因此,给定以下目录结构:folder1folder2file1.txtfile2.txtfile3.csfile4.htmlfolder2folder3file4.dll我想复制这个结构,但不包含.txt和.cs文件。因此,生成的目录结构应如下所示:folder1folder2file4.htmlfolder2folder3file4.dll 最佳答案 您可以使用查找模块。这是一个代码片段:require"find"ignored_extensions=[".cs"

  7. ruby - 扩展类和实例 - 2

    这个问题有两个部分。在RubyProgrammingLanguage一书中,有一个使用模块扩展字符串对象和类的示例(第8.1.1节)。第一个问题。为什么如果您使用新方法扩展类,然后创建该类的对象/实例,则无法访问该方法?irb(main):001:0>moduleGreeter;defciao;"Ciao!";end;end=>nilirb(main):002:0>String.extend(Greeter)=>Stringirb(main):003:0>String.ciao=>"Ciao!"irb(main):004:0>x="foobar"=>"foobar"irb(main):

  8. ruby-on-rails - gem install rmagick -v 2.13.1 错误 Failed to build gem native extension on Mac OS 10.9.1 - 2

    我已经通过提供MagickWand.h的路径尝试了一切,我安装了命令工具。谁能帮帮我?$geminstallrmagick-v2.13.1Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingrmagick:ERROR:Failedtobuildgemnativeextension./Users/ghazanfarali/.rvm/rubies/ruby-1.8.7-p357/bin/rubyextconf.rbcheckingforRubyversion>=1.8.5...yescheckingfor/

  9. ruby - 动态扩展现有方法或覆盖 ruby​​ 中的发送方法 - 2

    假设我们有A、B、C类。Adefself.inherited(sub)#metaprogramminggoeshere#takeclassthathasjustinheritedclassA#andforfooclassesinjectprepare_foo()as#firstlineofmethodthenrunrestofthecodeenddefprepare_foo#=>prepare_foo()neededhere#somecodeendendBprepare_foo()neededhere#somecodeendend如您所见,我正在尝试将foo_prepare()调用注入

  10. ruby-on-rails - 如何扩展 Ruby Test::Unit 断言以包含 assert_false? - 2

    显然在Test::Unit中没有assert_false。您将如何通过扩展断言并添加文件config/initializers/assertions_helper.rb来添加它?这是最好的方法吗?我不想修改test/unit/assertions.rb。顺便说一句,我不认为这是多余的。我使用的是assert_equalfalse,something_to_evaluate。这种方法的问题是很容易意外使用assertfalse,something_to_evaluate。这将始终失败,不会引发错误或警告,并且会在测试中引入错误。 最佳答案

随机推荐