kotlin 从实现String的‘+’操作到了解操作符重载和函数扩展

1,728 阅读9分钟

前言

         开始工作了,公司大佬建议学学kotlin,能够加快开发速度,可以防止空指针,总之是一堆好处,用起来很爽。but,一开始我是不信的,Java不香吗,kotlin那是啥,听说函数返回类型都在方法名后面,这种奇葩操作的语言。不过,鉴于大佬推荐,貌似了解是有必要的,于是,,,,,,,,,kotlin真香! 说了这么多废话,开始进入正题吧,正文如下

正文

      众所周知,在Java中,对字符串是有特殊处理的,不管什么对象,哪怕它是个null,字符串都能与其相加。

      但是当我学习了kotlin,发现不能愉快的用""+的方式愉快的将对象自动转化成String了(虽然kotlin有个更好的字符串模板功能,能更加愉快的写代码,但是,我就是喜欢直接""+,一个杠精程序员),不仅仅是String无法相加,不同类型的数字之间也不能相加了,这让一个Java程序员很难受的事情(可能只是本人难受,哈哈),本来我是要成为kotlin黑粉了,,但是本着找毛病的原则,我继续学习了kotlin,然后,kotlin又香了!!!

当我在kotlin官网学到了扩展函数和操作符函数这一块,我终于明白kotlin为何如此香了。如何让一个对象可以使用‘+’运算符来与String拼接呢?代码如下:

operator fun String.plus(i:Int)=this+i.toString()
fun main() {
    val i=5
    val re="2222"+i
    println(re)
}

如此,对于Int型变量,我们就能愉快的直接用 ‘+’ 连接了(plus函数的作用将在后文介绍),不过,要注意一点,此时 i这个Int对象只能位置+的左边,如果在右边,则会报错,如下图所示

在这里插入图片描述
这是为啥呢?这里先来科普下kotlin的扩展函数操作符吧。(点击即可进入官网去看了,官网内容总是最全的!) (ps:空指针检测什么的避免了错误,这当然是kotlin中的杀手级功能。但是让我觉得kotlin香气满满的还是kotlin中的函数扩展,操作符重载啊,参数可设置默认值这些功能啊) kotlin官方的扩展函数示例和操作符重载已经很清楚了,在此为了文章的完整性,部分内容是来自官方内容。

kotlin 操作符重载

什么是操作符重载?

       Kotlin 允许我们为自己的类型提供预定义的一组操作符的实现。这些操作符具有固定的符号表示 (如 + 或 *)和固定的优先级。为实现这样的操作符,我们为相应的类型(即二元操作符左侧的类型和一元操作符的参数类型)提供了一个固定名字的成员函数或扩展函数。 重载操作符的函数需要用operator修饰符标记。 另外,我们描述为不同操作符规范操作符重载的约定。 以上就是官方关于运算符的介绍了,对了 一元操作符指代的是如 a++,a-- 这种只对一个表达式执行操作,二元操作符指的是如 a+b,a-b,a*b,a/b 这种将两个表达式合成一个稍复杂的表达式。 在kotlin中,这些操作符其实都是对应一个被operator 修饰的函数。

基本操作符列举

一元操作符如下

表达式 函数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc() +
a-- a.dec() +

二元操作符(这里只列举了基本的算术运算符,其他的如in,== 详见官网)

表达式 函数
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)、 a.mod(b) (已弃用)
a..b a.rangeTo(b)

示例

从以上我们可以看出来了,原来操作符对应的就是一个个函数啊。如此,我们只需要将一个类定义了对应的方法,就可以相加了。这里我们拿个Money类来举例吧(厚脸拿郭神书中的例子了)

class Money(val value: Int) {
    operator fun plus(money: Money): Money {//这是 + 号
        val sum = this.value + money.value
        return Money(sum);
    }

    operator fun plus(money: Int): Money {//这是 + 号
        val sum = this.value + money
        return Money(sum);
    }
}
fun main(){
	val money=Moeny(5)
	val num=5
	val balance=money+num
	println("moeny:$balance")
}

此处我们写了Money类的plus函数,实现了 + 操作符的运算,我想这不需要解释啥了吧,记住就ok,当然,我们得弄清kotlin如何实现对+运算符的替代的,让我们来看看其对应的Java代码吧,如何看kotlin对应的Java代码(点击进入)。

示例对应Java源码分析

public final class Money {
   private final int value;

   @NotNull
   public final Money plus(@NotNull Money money) {
      Intrinsics.checkParameterIsNotNull(money, "money");
      int sum = this.value + money.value;
      return new Money(sum);
   }

   @NotNull
   public final Money plus(int money) {
      int sum = this.value + money;
      return new Money(sum);
   }

   public final int getValue() {
      return this.value;
   }

   public Money(int value) {
      this.value = value;
   }
}

public final class TempKt {
   private static final StringBuilder build(@NotNull StringBuilder $this$build, Function1 block) {
      block.invoke($this$build);
      return $this$build;
   }

   public static final void main() {
      Money money = new Money(5);
      int num = 5;
      Money balance = money.plus(num);
      String var3 = "moeny:" + balance;
      boolean var4 = false;
      System.out.println(var3);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
     	 main();
   }
}

从代码中我们可以看出,main函数在java中对应的是一个TempKt类中的java main方法,在入口方法 main中被调用。 Money类没什么好说的,其中方法基本是与kotlin中相对应的,重点在 koltin的val balance=money+num这一行代码,我们可以看到,在Java中,其被翻译成为这样了Money balance = money.plus(num);,很明显了,+被替换成了对应的方法。不过以上只有两个表达式相加,我们再来一个看看结果吧.

Money money = new Money(5);
int num1 = 5;
int num2 = 10;
Money balance = money.plus(num1).plus(num2);

对应Java代码

Money balance = money.plus(num1).plus(num2);

+不断的被替换成了plus

操作符重载小结

就以上代码,我们了解了kotlin中的操作符重载以及对应的Java代码转换了。同时,我相信也明白了开头那段为啥子String+Int不报错,而Int + String却报错了吧??,因为我们只实现了一边的plus,我们实现了左加操作符重载,即String对象在左,Int对象在右。我们再加上左运算符就ok了,代码如下:

package com.example.jetpacklearn.kotlinlearn
operator fun Int.plus(s:String)=this.toString()+s
operator fun String.plus(i:Int)=this+i.toString()
fun main() {
    val i=5
    val re=i+"2222"
    println(re)
}

通过给Int类也加个操作符重载的扩展函数就ok啦。

但是,相信机智的小伙伴们早已发现了,Money类的操作符重载是在其类中定义了方法,显然跟我们开头实现的String类的操作符重载是不同的,so,kotlin的又一个特性来了,扩展函数(官方传送门),让我们接下来探讨下扩展函数(官方传送门)吧。

kotlin 扩展函数

什么是kotlin的扩展函数?

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

官方介绍很清楚了,扩展函数就是方便开发者能够方便的使用某个方法,并且 该方法绑定在了一个类上,在使用上是更加直观的,你能清楚的知道这个方法是是让谁使用的。还有扩展属性,这点本文不介绍了,大家去官网看下吧,扩展传送门在此

示例

在此还是使用Money类作为示例吧,Money类中存储了金钱的数值,但是不同的钱的比例是不同的,因此我们需要增加一个货币计算的功能,比如人民币对应的美元数值。但是我们并不希望改变Money类的结构,如此我们可以用扩展函数,来给Moeny类增加一个名为 convertToDollar的扩展函数

class Money(val value: Double,val type:String){
//省略
}
fun Money.convertToDollar(): Money {
    when(this.type){
        "rmb"->{
            return Money(this.value*6.5,"dollar")
        }
        "dollar"->{return Money(this.value,this.type)}
        else->
            throw Exception("未知类型")
    }
}
fun main(){
    val rmb=Money(5.0)
    println("moeny   value:${rmb.value}  type:${rmb.type}")
    val dollar=rmb.convertToDollar()
    println("moeny   value:${dollar.value}  type:${dollar.type}")
    
}
# 运行结果
moeny   value:5.0  type:rmb
moeny   value:0.7692307692307693  type:dollar

示例生成的Java代码

在示例中,我们实现了扩展函数的编写,并且运行了,那么这一段kotlin代码对应的Java代码是怎么样的呢?反编译的Java代码如下:

public final class TestKt {
   private static final StringBuilder build(@NotNull StringBuilder $this$build, Function1 block) {
      block.invoke($this$build);
      return $this$build;
   }

   @NotNull
   public static final Money convertToDollar(@NotNull Money $this$convertToDollar) {
      Intrinsics.checkParameterIsNotNull($this$convertToDollar, "$this$convertToDollar");
      String var1 = $this$convertToDollar.getType();
      switch(var1.hashCode()) {
      case -1326217028:
         if (var1.equals("dollar")) {
            return new Money($this$convertToDollar.getValue(), $this$convertToDollar.getType());
         }
         break;
      case 113031:
         if (var1.equals("rmb")) {
            return new Money($this$convertToDollar.getValue() / 6.5D, "dollar");
         }
      }

      throw (Throwable)(new Exception("未知类型"));
   }

   public static final void main() {
      Money rmb = new Money(5.0D, (String)null, 2, (DefaultConstructorMarker)null);
      String var1 = "moeny   value:" + rmb.getValue() + "  type:" + rmb.getType();
      boolean var2 = false;
      System.out.println(var1);
      Money dollar = convertToDollar(rmb);
      String var5 = "moeny   value:" + dollar.getValue() + "  type:" + dollar.getType();
      boolean var3 = false;
      System.out.println(var5);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

从以上代码,我们可以发现,扩展函数对应的java中的方法并不是出现在Money类中,其在TestKt这个类中,也就是kotlin main函数所在类中。在convertToDollar这个方法中,传入了一个Money变量,通过这个$this$convertToDollar变量value,type值(有的小伙伴可能会对这个变量产生奇怪,会觉得这是一个特殊的变量,但是实际上,这就是一个再普通不过的一个变量名,以$作为了开始符,这个this也不是真的是对象内部的this,这是个普通的字符,更加形象花的一种命名方式罢了)。同时,我们通过该段代码也发现了when这个kotlin中的关键字是如何转化了Java中的switch。 好了,到此为止,扩展函数也讲完了。 再次回到我们的开头,String 的 ‘+’操作符的实现,发现如此简单,哈哈。

kotlin 泛型

不过!!!!!!!!!!!!!!!! 你会发现,这只是实现了 Int对象与String对象的 ‘+’操作,难道每个类我们都要重写一遍代码,才能实现其与String对象的‘+’操作????,那也太累了吧!!!!!!!!!!! 哈哈,这里当然是有解决方法的了,那就是,泛型了,与java一样,kotlin也是支持泛型的,相信Java的泛型大家都很熟悉了,kotlin的泛型相比Java的泛型使用也没有太大区别。让我们用泛型让 java 中 字符串相加操作真正移植到kotlin中来吧!!!代码如下:

operator fun <T> T.plus(s: String) = this.toString() + s
operator fun String.plus(t:Any)= this+t.toString()

小结

本文从String ‘+’操作符的实现,扩展讲述了kotlin的操作符重载,函数扩展等知识,刚开始写文章,如有问题,还请批评指正

原创声明

作者:陈浩亮

链接:

  1. csdn kotlin 从实现String的‘+’操作到了解操作符重载和函数扩展
  2. 简书 kotlin 从实现String的‘+’操作到了解操作符重载和函数扩展