本文為系列第二篇,聚焦 Scala 類別定義與使用特性。以實作有理數(Rational)類別為主線,展示建構子鏈、不可變設計、運算子方法、隱式轉換與 Java 互通測試等核心技術。
Scala 類別參數直接置於類別標籤,即為主建構子:
class Rational(n: Int, d: Int) {
private def gcd(x: Int, y: Int): Int = if (x == 0) y else gcd(y % x, x)
private val g = gcd(n, d)
val numer: Int = n / g
val denom: Int = d / g
// 運算子方法...
override def toString = s"Rational: [$numer / $denom]"
}
建構子邏輯直接寫在類別主體,執行順序自上而下。private val g 為建構子期間計算的中介值,外部不可見。
class Rational(n: Int, d: Int) {
def this(d: Int) = this(0, d) // 單參數建構子委派給主建構子
}
所有輔助建構子最終必須委派給主建構子,確保初始化邏輯單一入口。
numer、denom 為 val(唯讀),無 setter。操作回傳新實例:
def +(that: Rational) = new Rational(numer * that.denom + that.numer * denom, denom * that.denom)
def -(that: Rational) = new Rational(numer * that.denom - that.numer * denom, denom * that.denom)
def *(that: Rational) = new Rational(numer * that.numer, denom * that.denom)
def /(that: Rational) = new Rational(numer * that.denom, denom * that.numer)
此設計天然線程安全,同 java.lang.String。
關鍵洞見:Scala 無運算子重載。+、-、*、/ 皆為合法方法名稱。r1 + r2 實為 r1.+(r2) 語法糖。同理可自訂一元運算子:
def unary_~ : Rational = new Rational(denom, numer) // 求倒數
val r6 = ~r1 // 呼叫 unary_~
unary_ 前綴標識一元運算子(+、-、~、! 四種)。
未顯式定義 += 也能用:
var r5 = new Rational(3, 4)
r5 += r1 // 編譯器自動轉為 r5 = r5.+(r1)
任何 op= 皆自動展開為 op 呼叫加賦值。
public class Rational extends java.lang.Object implements scala.ScalaObject {
public Rational(int, int);
public Rational unary_$tilde(); // ~ -> $tilde
public Rational $plus(Rational); // + -> $plus
public Rational $minus(Rational); // - -> $minus
public Rational $times(Rational); // * -> $times
public Rational $div(Rational); // / -> $div
public int numer();
public int denom();
...
}
運算子方法編譯為 $op 形式,因 JVM 方法名允許 $ 但 Java 原始碼不允許。Java 調用需用反射或 Groovy/JRuby。
@Test public void testAddRationals() {
Rational r1 = new Rational(2, 5);
Rational r2 = new Rational(1, 3);
Rational r3 = (Rational) reflectInvoke(r1, "$plus", r2); // 反射調用 $plus
assertEquals(11, r3.numer());
assertEquals(15, r3.denom());
}
既可驗證正確性,又證明 Scala 類別可無縫納入 Java 建置流程。
傳統 Java POJO 需大量 getter/setter:
// Java 版:~30 行
public class JavaPerson { private String firstName; public String getFirstName() {...} public void setFirstName(...) {...} ... }
Scala 版(唯讀,線程安全):
class Person(firstName: String, lastName: String, age: Int) {
override def toString = s"[Person firstName:$firstName lastName:$lastName age:$age]"
}
// 編譯器自動產生 firstName(), lastName(), age() 存取器
需可變欄位只改 val 為 var,編譯器同步產生 firstName_$eq(String) 修改器:
class Person(var firstName: String, var lastName: String, var age: Int) { ... }
若需 JavaBeans 相容,加註解 @scala.reflect.BeanProperty 即可生成標準 get/set。
Scala 以函數式簡潔性強化物件模型:主建構子、不可變預設、運算子方法統一、隱式推論、Java 互通。可直接用於生成 Spring/Hibernate 所需 POJO。下一篇將進入函數式核心領域。