繼續學習,這一篇主要是通過scala來吐槽java的,同樣是jvm上的語言,差距咋就這么大呢?
作為一個有.NET開發經驗的程序員,當初剛接觸java時,相信很多人對java語言有以下不爽(只列了極小一部分):
1. 一堆的setter/getter方法,沒有c#中的property屬性概念
2. 方法的參數值,不能設置缺省值
3. 不定個數參數的寫法太單一
...
然后java的擁護者講出一堆大道理,說這樣設計是如何如何有道理,各種洗腦,時間長了,也就被迫習慣了。要不是遇到scala,我還真就信了,你看看人家scala同學,2003/2004發布的,早就把這些全實現了,而java同學作為jvm上的元老,這些年一直頑固不化,不思進取,已經被jvm上的其它同學遠遠甩在后面了,java你可長點心吧!進入正題,直接上碼:
一、參數缺省值
/** * 參數缺省值 * @param person * @param msg */ def saySomething(person: String = "somebody", msg: String = "Hello") = { println(person + " say : " + msg); }
調用示例:
saySomething() saySomething("jimmy") saySomething("jimmy", "hi")
當然這里有一個小小的限制,如果要用參數缺省值,建議所有的參數全設置缺省值,如果只給部分參數設置缺省值,函數定義不會有問題,調用時,上面的示例編譯就通不過了(大意是提供的參數不足之類),大家可以把msg參數的缺省值去掉再試試。
那么,最終編譯出來的class,到底是如何實現的呢?可以借助一些反編譯工具,比如JD-GUI還原成java一看究竟:
public void saySomething(String person, String msg) { Predef..MODULE$.println(new StringBuilder().append(person).append(" say : ").append(msg).toString()); } public String saySomething$default$1() { return "somebody"; } public String saySomething$default$2() { return "Hello"; }
也就是說,scala中的def saySomething(person: String = "somebody", msg: String = "Hello") 如果用java實現的話,可以用3個方法來變相實現,每個缺省參數,相當于一個獨立的版本,換言之,在編譯器層面,其實java的編譯器如果想做,是完全可以做到的,為什么不做?懶!頑!
二、class的property
/** * 定義一個帶參主構造器的類 * @param pReadOnly */ class Sample(pReadOnly: String) { /** * 可讀寫的屬性 */ var myProperty: String = _; private val _readOnly: String = pReadOnly; /** * 只讀屬性 */ def readOnly: String = _readOnly; }
調用示例:
val sample = new Sample("test") println(sample.readOnly) sample.myProperty = "a new value" println(sample.myProperty)
沒了setter/getter看起來倍兒清爽!還是反編譯class看看:
public class Sample { private String myProperty; private final String _readOnly; public String myProperty() { return this.myProperty; } public void myProperty_$eq(String x$1) { this.myProperty = x$1; } private String _readOnly() { return this._readOnly; } public String readOnly() { return _readOnly(); } public Sample(String pReadOnly) { this._readOnly = pReadOnly; } }
可以看到,myProperty自動生成了setter/gettter,仍然是在編譯器層面,就可以順手做掉的事情,java編譯器依然不肯做。
三、不定個數參數值
這個問題,java中雖然可以xxx(String[] args)用數組傳遞達到類似的效果,但是就算傳一個空數組,也至少也得寫一個xxx(null)吧,既然此時參數都為空了,為啥不直接xxx()更直接,看看scala:
/** * 不固定個數的參數 * @param x * @return */ def add(x: Int*) = { var i = 0 for (j <- x) i += j i }
調用:
println(add()) println(add(1, 2, 3, 4, 5))
明顯的更高端大氣上檔次,繼續反編譯,這個要略復雜點:
先是生成了這么一個類:
public final class DefHello$$anonfun$add$1 extends AbstractFunction1.mcVI.sp implements Serializable { public static final long serialVersionUID = 0L; private final IntRef i$1; public final void apply(int j) { apply$mcVI$sp(j); } public void apply$mcVI$sp(int j) { this.i$1.elem += j; } public DefHello$$anonfun$add$1(IntRef i$1) { } }
然后是:
public void main(String[] args) { Predef..MODULE$.println(BoxesRunTime.boxToInteger(add(Nil..MODULE$))); Predef..MODULE$.println(BoxesRunTime.boxToInteger(add(Predef..MODULE$.wrapIntArray(new int[] { 1, 2, 3, 4, 5 })))); ... } public int add(Seq<Object> x) { IntRef i = IntRef.create(0); x.foreach(new AbstractFunction1.mcVI.sp() { public static final long serialVersionUID = 0L; public final void apply(int j) { apply$mcVI$sp(j); } public void apply$mcVI$sp(int j) { DefHello..this.elem += j; } }); return i.elem; }
最終調用時,add()這里雖然scala沒有傳任何參數,但從反編譯結果上看,最終還是變成了add(Nil..MODULE$)),編譯器自動加了一個參數,以滿足java的規范。
四、泛型初步
java中的泛型是一個"偽"泛型,其類型擦除機制只是障眼法而已,因此帶來了很多使用上的限制,比如下面這個例子:
public class SampleClass<T> { private T _t; public SampleClass(T t) { this._t = t; } public T getT() { return _t; } }
這里定義了一個泛型類,如果想創建一個該類的數組:
SampleClass<String>[] objs = new SampleClass<String>[10];
編譯器會直接報錯:Error: java: generic array creation,原因是:type erase后,內部已經是SampleClass[],按OOP的原則,可以向上轉型為Object[],這下可好了,Object是萬能類型,如果向這個萬能類型的數組里加入一個不是SampleClass<String>的實例,理論上也是允許的,這就違背了泛型約束的初衷。
但是在scala中,卻是可以這樣做的,看下面的代碼:
class MyClass[T](t1: T) { var t: T = t1; }
然后可以這樣用:
val objs = new Array[MyClass[String]](10) objs(0) = new MyClass[String]("a") for (x <- objs; if x != null) println(x.t)
編譯和運行一切正常,這是什么情況?還是反編譯解密:
MyClass[] objs = new MyClass[10]; objs[0] = new MyClass("a"); Predef..MODULE$.refArrayOps((Object[])objs).withFilter(new DefHello..anonfun.main.1()).foreach(new DefHello..anonfun.main.2());
原來,對于java的偽泛型機制,scala早就看穿了這一切,因此它采用了一種略帶"極端"的做法,直接使用原始類型,無情的對java的泛型機制回應:『不約,我們不約』。
了解以上這些后,我不得不更加佩服堅持使用java語言寫出這么多NB開源框架的達人們,硬是用一個要啥啥沒有的語言為開源世界做出這么大的貢獻,這是一種什么樣的精神,無禁讓我想起了《道士下山》中猿擊術中的精髓:"不離不棄,不嗔不恨!",我只想說:這么多年,你們是怎么忍下來的!
So,Scala既然這么好,就完美無缺了么?當然不是,功能越強大,語法越靈活,自然學習成本也更高。另外,性能方面,它生成的字節碼感覺比java略多,網上有很多關于scala與java的性能討論,包括google也有類似的評測,有人說這二者差不多,但是多數人還是認為在jvm上,scala的性能整體來看要低于java,只能達到java的8成上下(詳情可自行百度,有很多這類文章)
文章列表