
繼續學習,這一篇主要是通過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成上下(詳情可自行百度,有很多這類文章)
作者:菩提樹下的楊過