我學一個東西向來喜歡盡量深入去瞭解它的運作原理,這兩天看了 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 的處理
之後有時間的話會再持續挖些東西出來看。