4.2 定义字段、方法和构造函数

Scala把主构造函数放到了类定义中,让定义字段及相应方法变得简单起来。让我们跟着例子看一下Scala是怎么做的:

先看看下面的类定义:

ScalaForTheJavaEyes/CreditCard.scala

  1. class CreditCard(val number: Int, var creditLimit: Int)

这就够了。这就是类的完整定义。如果没有什么要往类定义里加的东西,连大括号({})都不需要。上面这句代码着实给予了我们相当多的东西。先用scalac进行编译,然后运行javap -private CreditCard看看编译器生成了哪些信息:

  1. Compiled from "CreditCard.scala"
  2. public class CreditCard extends java.lang.Object implements scala.
  3. ScalaObject{
  4. private int creditLimit;
  5. private final int number;
  6. public CreditCard(int, int);
  7. public void creditLimit_$eq(int);
  8. public int creditLimit();
  9. public int number();
  10. public int $tag() throws java.rmi.RemoteException;
  11. }

首先,Scala会自动把这个类变成public——在Scala里,任何没有标记为private或者protected的数据都默认是public

我们把number声明为val,所以Scala把它定义为一个private final的字段,给它创建了public方法number(),用以取值。因为creditLimit声明为var,所以Scala将它定义了一个private字段,叫做creditLimit,并同时提供了public的getter和setter方法①。

①默认生成的getter和setter不遵守JavaBean惯用法。稍后会讲到怎么控制这一点。

如果参数既不是val,又不是var,那Scala就会创建一个private字段以及private getter和setter方法。不过,就不能在类外访问这个参数了。

放到类定义中的任何表达式或者可执行语句都会作为主构造函数的一部分执行。我们看个例子:

ScalaForTheJavaEyes/Sample.scala

  1. class Sample {
  2. println("You are constructing an instance of Sample")
  3. }
  4. new Sample

输出结果显示,在创建Sample类实例的时候,因为print语句也是构造器的一部分,所以它也会被执行:

  1. You are constructing an instance of Sample

除了在主构造函数中提供的参数外,我们还可以在类里面定义其他字段、方法、零个或是多个副构造函数。在下面的代码里,this()方法就是副构造函数。我们还定义了position变量,改写了toString()方法:

ScalaForTheJavaEyes/Person.scala

  1. class Person(val firstName: String, val lastName: String) {
  2. private var position: String = _
  3. println("Creating " + toString())
  4. def this (firstName: String, lastName: String, positionHeld: String) {
  5. this (firstName, lastName)
  6. position = positionHeld
  7. }
  8. override def toString() : String = {
  9. firstName + "" + lastName + " holds " + position + " position "
  10. }
  11. }
  12. val john = new Person("John", "Smith", "Analyst")
  13. println(john)
  14. val bill = new Person("Bill", "Walker")
  15. println(bill)

输出结果如下:

  1. Creating John Smith holds null position
  2. John Smith holds Analyst position
  3. Creating Bill Walker holds null position
  4. Bill Walker holds null position

上面的主构造函数②接受两个参数,firstNamelastName。副构造函数接受三个参数,前两个跟主构造函数的一样,最后一个是positionHeld。在副构造函数里,调用主构造函数初始化了名字相关的字段。副构造函数的第一条语句,要么是调用主构造函数,要么是调用另一个副构造函数;二者必选其一。

②想把主构造函数变成private很容易,请参见4.5节,“孤立对象和伴生对象”。

Scala对待字段有些特别。类里定义的var会映射为一个private字段声明,并伴有对应的访问方法——getter和setter。字段上附加的访问权限会在访问方法上体现出来。在上面的例子中,我们声明了private var position: String =_之后,Scala创建出下面的代码:

  1. private java.lang.String position;
  2. private void position_$eq(java.lang.String);
  3. private java.lang.String position();

这样就有了一个特殊的position()方法读取属性,position_=()方法设置属性。

上面position的定义里,可以将其初始值设为null,但我们用的是下划线(_)。在这里,代表的是该类型的默认值——对于Int来说,它是0;对于Double,它是0.0;对于引用类型,它是null,等等。通过\,Scala可以很方便地用默认值初始化var。不过这种便利的初始化方式不适用于val,因为val不能修改,所以必须得在初始化的时候设定值。

如果你更喜欢传统的JavaBean风格的getter和setter,只要给字段加上scala. reflect.BeanProperty注解即可。Scala的注解语法跟Java的很相似。比如,下面的注解让Scala创建了访问方法getAge( )setAge( )

  1. @scala.reflect.BeanProperty var age: Int = _