2014年6月18日 星期三

Scala 與 Java 的差異

前言

以下的內容,討論 Scala 與 Java 在功能與觀念上的不同,不會去比較語法上的不同。

Import

  • Scala 的 import 已有 Java 的 static import 效果
  • Java 的 import org.abc.* 在 Scala 為 import org.abc._ 不過在 Java 及 Scala 都不建議使用;還是詳細寫出 import 那些 class.

Val and Var

使用 val 宣告變數後,就不能再更改其 reference 值;而 var 是允許的,效果等同 Java 宣告。ex:

val a = "abc"
a = "def"

a = "def" 那一行,在 compiler 就會報錯。這項功能有個好處,可以確保變數的 reference 值不會被更動,讓系統的穩定度提高。對工程師在 coding,debug 也有很大的幫助

Getter and Setter in Java Bean on Scala

Scala 在 compile 時,不同的宣告方式,會產生不同 Bean 的 getter 與 setter 的效果。可以使用 scalap -privatejavap -p 來比較 Scala 做了那些事情。

比較 1: 最簡單的 class

class Rational(numer: Int, denom: Int) {

}
  • scalap -private Rational

      class Rational extends scala.AnyRef {
        def this(numer : scala.Int, denom : scala.Int) = { /* compiled code */ }
      }
    
  • javap -p Rational

      public class Rational {
          public Rational(int, int);
      }
    

最簡單的 class,沒有任何功能,也因此 Scala 的 compiler 不會自動產生 member data。

比較 2: 加入 val

class Rational(val numer: Int, val denom: Int) {

}
  • scalap -private Rational

      class Rational extends scala.AnyRef {
        val numer : scala.Int = { /* compiled code */ }
        val denom : scala.Int = { /* compiled code */ }
        def this(numer : scala.Int, denom : scala.Int) = { /* compiled code */ }
      }
    
  • javap -p Rational

      public class Rational {
          private final int numer;
          private final int denom;
          public int numer();
          public int denom();
          public Rational(int, int);
      }
    

很明顯的,加入 val 後, constructor 中的參數,會自動變成 private member data,也產生 getter 的函式:numer()denom()。因為 val 是不可以修改 reference 值,所以 Scala 的 compiler 沒有幫忙產生 setter 的函式

比較 3: 改用 var

class Rational(var numer: Int, var denom: Int) {

}
  • scalap -private Rational

      class Rational extends scala.AnyRef {
        var numer : scala.Int = { /* compiled code */ }
        var denom : scala.Int = { /* compiled code */ }
        def this(numer : scala.Int, denom : scala.Int) = { /* compiled code */ }
      }
    
  • javap -p Rational

      public class Rational {
          private int numer;
          private int denom;
          public int numer();
          public void numer_$eq(int);
          public int denom();
          public void denom_$eq(int);
          public Rational(int, int);
      }
    

由於 var 可以修改 reference,因此 Scala 的 compiler 就自動產生了 setter 的函式:numer_$eq(int)denom_$eq(int)

Add @BeanProperty

Scala 產生的函式,對於給 Java 的使用者,雖然在寫 code 時,是可以使用的,但並不是那麼 friendly。這時候,可以使用 scala 內建的 annotation:@BeanProperty。ex:

import scala.reflect.BeanProperty

class Rational(@BeanProperty var numer: Int, @BeanProperty var denom: Int) {

}
  • scalap -private Rational

      class Rational extends scala.AnyRef {
        var numer : scala.Int = { /* compiled code */ }
        def getNumer() : scala.Int = { /* compiled code */ }
        def setNumer(x$1 : scala.Int) : scala.Unit = { /* compiled code */ }
        var denom : scala.Int = { /* compiled code */ }
        def getDenom() : scala.Int = { /* compiled code */ }
        def setDenom(x$1 : scala.Int) : scala.Unit = { /* compiled code */ }
        def this(numer : scala.Int, denom : scala.Int) = { /* compiled code */ }
      }
    
  • javap -p Rational

      public class Rational {
          private int numer;
          private int denom;
          public int numer();
          public void numer_$eq(int);
          public void setNumer(int);
          public int denom();
          public void denom_$eq(int);
          public void setDenom(int);
          public int getNumer();
          public int getDenom();
          public Rational(int, int);
      }
    

加了 @BeanProperty 後,就產生了常用的 getter 及 setter 了。因此使用 Scala 來產生 JavaBean, 可以讓工程師少寫幾行程式。

Argument

Default Arguement Values

目前 Java 的參數,並沒有預設值功能。也就是說,沒有辦法寫類似 public void func(int a = 0) { } 的程式,而 Scala 卻有這樣功能,因此在程式設計上,就有很大的發揮空間,而且也可以少寫很多程式碼。ex:

class Argu {

  def func(a: Int = 10): Int = a + 1
}
  • scalap -private Argu

      class Argu extends scala.AnyRef {
        def this() = { /* compiled code */ }
        def func(a : scala.Int) : scala.Int = { /* compiled code */ }
      }
    
  • javap -p Argu

      public class Argu {
          public int func(int);
          public int func$default$1();
          public Argu();
      }
    

Scala 在有預設值參數的地方,自行新增一個 function public int func$default$1();,當 func 被呼叫,且不帶入參數時,Scala 的 compile 就會在 bytecode 偷呼叫 func$default$1() 來達到有預設值的效果。

雖然 Scala 允許預設值的參數不一定要放在最後。ex:

def func(a: Int, b:Int = 10, c:Int) { }

但如果不使用 keyword argument 的方式使用函式,就一定都要傳入參數,ex: func()(1, 10, 3) 不能是 func(1,3)。因此在設計程式時,建議還是將 default value 的 argument 放在最後。

Named Arguements

Named arguments 使用跟 python 相同,對於 Java 使用者來說,是一個非常陌生的功能。簡單來說,就是在使用函式時,使用 key-value 方式來傳入參數。 ex:

def func(a: Int, b: Int=10, c:Int) { }

func(a = 10, c = 20)

這項功能,會讓程式有更高的可讀性,也當然自己在寫程式時,參數的命名也不能隨便。如果函式有比較多的參數要傳入時,也可以透過用 key-value 的方式,讓 coding 的速度可以加快。

Lazy Load

Lazy 這項功能,Java 的使用者也是非常陌生的功能,簡單來說,你可以先定義變數的來源,等到真的要使用時,再執行產生變數值。ex:

scala> def func(a: Int): Int = { 
     | println("call func")
     | a + 1
     | }
func: (a: Int)Int

scala> val x = func(10)
call func
x: Int = 11

scala> lazy val y = func(20)
y: Int = <lazy>

scala> val z = y + 10
call func
z: Int = 31

要使用 lazy 的功能,只要在變數前加 lazy 這個 keyword 即可,上述的 sample code 中的 y 就是一個 lazy 變數,在宣告時,並不會真的執行 func(20),要一直到 val z = y + 10 才會真正執行 func(20)

其實 lazy 善用的話,也可以幫工程師少寫一些 code。 ex: 以下是連線資料庫的 sample,用來比較 Java 及 Scala 版的差別。

Java 版

java.sql.Connection conn = null;
java.sql.PreparedStatement stmt = null;
java.sql.ResultSet rs = null;

try {
    conn = getConnection()
    stmt = conn.prepareStatement("...........")
    stmt.setInt(1, 10)
    rs = stmt.executeQuery()

    while(rs.next()) {

    }
}
catch (Exception ex) {
    ex.printStackTrace();
}
finally {
    close(rs);
    close(stmt);
    close(conn);

}

Scala 版

lazy val conn = getConnection()
lazy val stmt = conn.prepareStatement(".......")
lazy val rs = stmt.executeQuery()

try {
  stmt.setInt(1, 10)
  rs = stmt.executeQuery()

  while(rs.next()) {

  }
}
catch {
  case ex: Exception => ex.printStackTrace()
}
finally {
  close(rs)
  close(stmt)
  close(conn)
}
張貼留言