文章出處

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


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()