天天看點

scala 學習筆記(03) 參數預設值、不定個數參數、類的屬性(Property)、泛型初步

scala 學習筆記(03) 參數預設值、不定個數參數、類的屬性(Property)、泛型初步

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

作者:菩提樹下的楊過

出處:http://yjmyzz.cnblogs.com

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。