kotlin 從實現String的‘+’操做到了解操做符重載和函數擴展

前言

         開始工做了,公司大佬建議學學kotlin,可以加快開發速度,能夠防止空指針,總之是一堆好處,用起來很爽。but,一開始我是不信的,Java不香嗎,kotlin那是啥,據說函數返回類型都在方法名後面,這種奇葩操做的語言。不過,鑑於大佬推薦,貌似瞭解是有必要的,因而,,,,,,,,,kotlin真香! 說了這麼多廢話,開始進入正題吧,正文以下html

正文

      衆所周知,在Java中,對字符串是有特殊處理的,無論什麼對象,哪怕它是個null,字符串都能與其相加。java

      可是當我學習了kotlin,發現不能愉快的用""+的方式愉快的將對象自動轉化成String了(雖然kotlin有個更好的字符串模板功能,能更加愉快的寫代碼,可是,我就是喜歡直接""+,一個槓精程序員),不只僅是String沒法相加,不一樣類型的數字之間也不能相加了,這讓一個Java程序員很難受的事情(可能只是本人難受,哈哈),原本我是要成爲kotlin黑粉了,,可是本着找毛病的原則,我繼續學習了kotlin,而後,kotlin又香了!!!程序員

當我在kotlin官網學到了擴展函數和操做符函數這一塊,我終於明白kotlin爲什麼如此香了。如何讓一個對象可使用‘+’運算符來與String拼接呢?代碼以下:shell

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 修飾的函數。bash

基本操做符列舉

一元操做符以下函數

表達式 函數
+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代碼(點擊進入)。ui

示例對應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的‘+’操做到了解操做符重載和函數擴展
相關文章
相關標籤/搜索