scala学习手记12 - 字段、方法和构造函数

在上一节创建了一个scala类,如果没有更多的方法,scala类的定义还可以更简单一些,看一下下面这个CreditCard类的定义:

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

是的,只用一行就完成了类的定义,连大括号都不需要。

因为scala也是运行在JVM上,可以考虑以java的方式来看看编译后的类文件。查看的方式还是比较灵活的,可以使用JD-GUI,也可以使用javap –private CreditCard命令,还有一个在线反编译的网站ShowMyCode。反编译后的Java代码:

public class CreditCard {

  public int number() {
    return number;
  }

  public int creditLimit() {
    return creditLimit;
  }

  public void creditLimit_$eq(int x$1) {
    creditLimit = x$1;
  }

  public CreditCard (int number, int creditLimit) {
    this.number = number;
    this.creditLimit = creditLimit;
    super ();
  }

  private final int number;
  private int creditLimit;
}

好长的java代码。首先scala默认将CreditCard类转换为了public。因为在CreditCard.scala中将number声明为val,所以在反编译生成的java代码中,number被定义为final。此外在编译后的代码中还可以看到一个构造器以及读写成员变量的方法。可以看到成员变量的getter和setter与我们在java中习惯使用的命名方式有些不一致。此外由于number有final修饰符,因此就没有它的setter方法。如果scala中的成员变量的定义符号既不是var也不是val,那Scala就会为之创建一个private字段以及private getter和setter方法,也因此不能在类外部访问这个成员变量了。

放到类定义中的所有可执行语句或表达式都会被视为类的构造器的组成部分。下面的代码就是一个示例:

class Sample {
  println("You are constructing an instance of Sample")
}

new Sample

在这段代码中先定义了一个类Sample,随后又创建了一个Sample类的实例,执行看一下:

在创建实例的时候输出了类定义中的print语句,因为这段print语句是构造器的一部分。

除了在主构造函数中提供成员变量,我们还可以在类里面定义其他字段、方法、零个或多个副构造函数。在下面这个类中在类里面定义了一个成员变量position、一个副构造函数this()、并且重写了toString()方法。

class Person(val firstName: String, val lastName: String) {

  private var position: String = _

  println("Creating " + toString())

  def this(firstName: String, lastName: String, positionHeld: String) {
    this(firstName, lastName)
    position = positionHeld
  }

  override def toString(): String = {
    firstName + "" + lastName + " holds " + position + " position "
  }
}

val john = new Person("John", "Smith", "Analyst")
println(john)
val bill = new Person("Bill", "Walker")
println(bill)

执行代码的结果如下:

稍稍关注下副构造函数的实现:如果有主构造函数的话,那么副构造函数的第一行必须是主构造函数或者其它副构造函数的调用。这一点倒是和java继承父类时有点相似。

此外还值得注意的就是成员变量position的定义,把这一行单独拎出来看看吧:

private var position: String = _

首先比较有趣的是初始化赋值,赋值是一个“_”——下划线。在这里“_”代表相应类型的默认值。对于Int,它的值是0;对于Double,它的值是0.0;对于String,它的值就是null。通过使用“_”,可以很方便地为var成员变量设置初始默认值。不过不能为val成员使用“_”,因为val成员不允许修改,所以必须显式指定初始值

通过查看Person.scala的字节码反编译出来的java类,可以看到scalac编译器为成员变量position默认设置了getter和setter方法(虽然没有按照我们习惯的JavaBean的方式进行设置)。scala中定义的成员变量的可见性在反编译出来的Java代码中由getter和setter方法的访问权限来控制。

如果更喜欢传统的JavaBean式的注解,可以在成员变量定义时添加注解@BeanProperty:

@BeanProperty  var position: String = _

声明前记得导入注解。不过这也有一点限制:此时成员变量不可再声明为private。而且这样做只是会再额外生成两个JavaBean式的getter和setter,原来的getter和setter也会继续保留。这可以在编译Person类以后再用javap –private验证一下:

就是这样。

#######

时间: 2024-10-16 20:47:20

scala学习手记12 - 字段、方法和构造函数的相关文章

scala学习手记34 - trait方法的延迟绑定

trait的方法的延迟绑定就是先混入的trait的方法会后调用.这一点从上一节的实例中也可以看出来. 下面再来看一个类似的例子: abstract class Writer { def write(message: String): String } trait UpperWriter extends Writer { abstract override def write(message: String): String = super.write(message.toUpperCase) }

scala学习手记20 - 方法返回类型推断

除了推演变量的类型,scala也会推演方法的返回类型.不过这里有一处需要注意:方法返回类型的推演依赖于方法的定义方式.如果用等号"="定义方法,scala就会推演方法返回类型:否则,它就认为方法的返回为void.看一个例子: def printMethodInfo(methodName: String) { println("The return type of " + methodName + " is " + getClass().getDe

scala学习手记13 - 类继承

在scala里,类继承有两点限制: 重写方法需要使用override关键字: 只有主构造函数才能往父类构造函数中传参数. 在java1.5中引入了override注解,但不强制使用.不过在scala中要想重写方法必须使用override关键字.如果确实重写了父类的方法又不使用override关键字的话,则会在编译时报错,提示没有使用override修饰符. scala的副构造函数必须调用主构造函数或是另一个副构造函数.只有在主构造函数中才能向父类的构造函数中传递数据.可以看出来主构造函数如同父类

scala学习手记16 – scala中的static

前面两节学了scala的对象和伴生对象,这两个在使用的时候很有些java的静态成员的意思. scala中没有静态字段和静态方法.静态成员会破坏scala所支持的完整的面向对象模型.不过可以通过伴生对象实现对scala的类一级的操作. 回过头来再看一遍那个Marker的例子,略做了一些调整: class Marker private(val color: String) { println("Creating " + this) override def toString(): Stri

scala学习手记2 - scala中的循环

先来看一段Java中的循环: for (int i = 1; i < 4; i++) { System.out.print(i + ","); } 毫无疑问,scala可以让这个循环更加简洁.根据上一节中的内容,没有必要显示指定变量i的类型,我们甚至不需要声明这个变量.其次输出的语句也可以更加简洁一些,在scala中可以直接使用println()这个方法输出字符串.最后scala的循环结构也是非常的轻量级.好了,可以看一下代码了: for (i <- 1 to 3) { p

scala学习手记38 - 方法命名约定和for表达式

方法命名约定 之前在学习<运算符重载>一节时曾经说过一个方法命名约定:方法的第一个字符决定了方法的优先级.现在再说另一个命名约定:如果方法以冒号(:)结尾,则调用目标是运算符后面的实例. 比如下面这个例子: class Cow { def ^(moon: Moon) = println("Cow jumped over the moon") } class Moon { def ^:(cow: Cow) = println("This cow jumped ove

scala学习手记10 - 访问修饰符

scala的访问修饰符有如下几个特性: 如果不指定访问修饰符,scala默认为public: 较之Java,scala对protected的定义更加严格: scala可以对可见性进行细粒度的控制. scala的默认访问修饰符 如果没有修饰符,scala会默认把类.字段.方法的访问修饰符当做public.如果要将之调整为private或protected,只需在前面添加对应的修饰符关键字即可.就如下面的程序: class Microwave{ def start() = println("star

scala学习手记14 - 单例对象

java中的单例模式都很熟悉了:简单地说就是一个类只能有一个实例.在scala中创建单例对象非常简单,创建类时使用object关键字替换class即可.因为单例类无法初始化,所以不能向它的主构造函数传递参数. 下面是一个单例的示例: class Marker(val color: String) { println("Creating " + this) override def toString(): String = "marker color " + colo

scala学习手记8 - 自适应的默认做法

scala有一些默认做法,会让代码更简洁.更易读写,下面列出了这样几个特性: 1. 支持脚本.scala支持脚本,因此无须将所有的代码都放到类里.如果脚本可以满足需求,就将代码放到一个脚本里,无须再创建一个冗余的类. 2. return是可选的.如果没有写return关键字,方法调用会自动返回最后一个求值的表达式--如果它符合方法声明的返回值类型. 3. 分号":"是可选的.不必在每个语句的后面都写上分号,这样会使代码更简洁.如果语句太长或者包含多行的话可以换行继续写,scala能够识