我學一個東西向來喜歡盡量深入去瞭解它的運作原理,這兩天看了 Scala 覺得蠻有趣的,也會去想像如果翻譯成 Java 會是怎麼樣的做法。 如果想知道自己的想法跟 Scala 實作的差距,可以直接做些實驗來比對。例如下面就是個求解最大公因數的實驗,我寫了四個版本 (gcd1 .. gcd4):

object GCD {
  def main(args: Array[String]) {
    def gcd1(m: Int, n: Int):Int = {
      if (n == 0) {
        return m
      } else {
        return gcd1(n, m % n)
      }
    }

    def gcd2(m: Int, n: Int):Int = {
      if (n == 0) {
        m
      } else {
        gcd2(n, m % n)
      }
    }

    def gcd3(m: Int, n: Int):Int = if (n == 0) m else gcd3(n, m % n)

    var gcd4: (Int,Int) => Int = null
    gcd4 = (m: Int, n: Int) => if (n == 0) m else gcd4(n, m % n)

    val Array(m, n) = args.map(_.toInt)

    println(gcd1(m, n))
    println(gcd2(m, n))
    println(gcd3(m, n))
    println(gcd4(m, n))
  }
}

執行結果如下:

$ scala GCD 1024 768
256
256
256
256

用 JD 反編譯出來的實作:

import scala.Array.;
import scala.Function2;
import scala.MatchError;
import scala.Option;
import scala.Predef.;
import scala.ScalaObject;
import scala.Serializable;
import scala.Tuple2;
import scala.Tuple2.mcII.sp;
import scala.collection.IndexedSeq;
import scala.collection.SeqLike;
import scala.collection.TraversableLike;
import scala.collection.immutable.StringLike;
import scala.reflect.Manifest.;
import scala.runtime.AbstractFunction1;
import scala.runtime.AbstractFunction2.mcIII.sp;
import scala.runtime.BoxesRunTime;
import scala.runtime.ObjectRef;

public final class GCD$

implements ScalaObject {
    public static final  MODULE$;

    static {
        new ();
    }

    public void main(String[] args) {
        null; ObjectRef gcd4$1 = new ObjectRef(null);
        gcd4$1.elem = new AbstractFunction2.mcIII.sp() {
            public static final long serialVersionUID = 0L;
            private final ObjectRef gcd4$1;

            public final int apply(int m, int n) { return apply$mcIII$sp(m, n); }
            public int apply$mcIII$sp(int v1, int v2) {
                return v2 == 0 ? v1 : ((Function2)this.gcd4$1.elem).apply$mcIII$sp(v2, v1 % v2);
            }
        };

        int[] arrayOfInt = (int[])Predef..MODULE$.refArrayOps((Object[])args).map(
            new AbstractFunction1() {
                public static final long serialVersionUID = 0L;

                public final int apply(String paramString) {
                    return Predef..MODULE$.augmentString(paramString).toInt();
                }
            }
        , Array..MODULE$.canBuildFrom(Manifest..MODULE$.Int()));

        Option localOption = Array..MODULE$.unapplySeq(arrayOfInt);
        if (localOption.isEmpty())
            throw new MatchError(arrayOfInt);

        IndexedSeq localIndexedSeq = (IndexedSeq)localOption.get();

        if ((localIndexedSeq.lengthCompare(2) == 0 ? 1 : localIndexedSeq == null ? 0 : 0) != 0) {
            Tuple2.mcII.sp localsp = new Tuple2.mcII.sp(BoxesRunTime.unboxToInt(localIndexedSeq.apply(0)), BoxesRunTime.unboxToInt(localIndexedSeq.apply(1)));
            int m = localsp._1$mcI$sp();
            int n = localsp._2$mcI$sp();

            Predef..MODULE$.println(BoxesRunTime.boxToInteger(gcd1$1(m, n)));
            Predef..MODULE$.println(BoxesRunTime.boxToInteger(gcd2$1(m, n)));
            Predef..MODULE$.println(BoxesRunTime.boxToInteger(gcd3$1(m, n)));
            Predef..MODULE$.println(BoxesRunTime.boxToInteger(((Function2)gcd4$1.elem).apply$mcIII$sp(m, n)));
            return;
        }

        ObjectRef gcd4$1;
        throw new MatchError(arrayOfInt);
    }

    private final int gcd1$1(int m, int n) {
        while (true)
        {
            if (n == 0) {
                return m;
            }
            n = m % n; m = n;
        }
    }

    private final int gcd2$1(int m, int n) {
        while (true) {
            if (n == 0) {
                return m;
            }
            n = m % n; m = n;
        }
    }

    private final int gcd3$1(int m, int n) {
        while (true) {
            if (n == 0) return m;
            n = m % n; m = n;
        }
    }

    private GCD$() {
        MODULE$ = this;
    }
}

觀察到的幾點:

  • GCD$ 會從 ScalaObject 繼承下來
  • 區域函數都變成了 private final method
  • 前三個 gcd 函數基本上是一樣的
  • var gcd4 會被翻譯成一個 local ObjectRef object
  • gcd4 這個 function literal 會用 Java anonymous function 來實現
  • _.toInt 會翻譯成一個 AbstractFunction1 的實現
  • augmentString 用來實現從 String 到 StringOps 的 implicit conversion
  • unapplySeq 可以對命令列參數提取一個 Option,從其中又可拿到一個 IndexedSeq
  • BoxesRunTime 用來負責 boxing/unboxing 的處理

之後有時間的話會再持續挖些東西出來看。