scala学习笔记 - 类构造器

25 篇文章 0 订阅
15 篇文章 2 订阅

scala类的构造器

辅助构造器

scala的类可以有任意多的辅助构造器。

  • 辅助构造器的名称为this
  • 每一个辅助构造器都必须以一个对先前已定义的其他辅助构造器或主构造器的调用开始。
    如下:
class Demo {
  private var name: String = _
  private var age = 0

  def this(name: String) {
    this() // 调用主构造器
    this.name = name
  }
  def this(name: String, age: Int) {
    this(name) // 调用上一个辅助构造器
    this.age = age
  }
}

val a = new Demo // 主构造器
val b = new Demo("Alice") // 第一个辅助构造器
val c = new Demo("Alice", 42) // 第二个辅助构造器

主构造器

在scala中,每个类都要主构造器。主构造器并不以this方法定义,而是与类定义交织在一起。

  1. 主构造器的参数直接放置在类名之后;
class Demo(val name: String, val age: Int) {
  // (...)中的内容就是主构造器的参数
}

主构造器的参数被编译成字段,其值被初始化成构造时传人的参数。在本例中, name和age成为Demo类的字段。如new Demo("Alice", 30)这样的构造器调用将设置name和age字段。
2. 主构造器会执行类定义中的所有语句。例如:

class Demo(val name: String, val age: Int) {
  println("类定义中的所有语句都会执行") // 对象被创建的时候,就会执行

  def description = s"name: $name, age: $age"
}

println语句是主构造器一部分。每当有对象被构造出来时,上述代码就会被执行。
说明:如果类名之后没有参数 ,则该类具备一个无参主构造器。这样一个构造器仅仅是简单地执行类体中的所有语句而已。

构造参数也可以是普通的方法参数,不带val或var。这样的参数如何处理取决于它们在类中如何被使用。

  • 如果不带val或var的参数至少被一个方法所使用,它将被升格为字段。
class Demo(val name: String, val age: Int) {
  def description = s"name: $name, age: $age"
}

上述代码声明并初始化了不可变字段name和age,而这两个字段都是对象私有的。类似于这样的字段等同于private[this] val字段的效果。

  • 否则,该参数将不被保存为字段,它仅仅是一个可以被主构造器中的代码访问的普通参数。
主构造器参数生成的字段/方法
name:String对象私有字段,如果没有方法使用name,则没有该字段
private val/var name:String私有字段,私有的getter和setter方法
val/var name:String私有字段,公有的getter和setter方法
@BeanProperty val/var name:String私有字段,公有的Scala版和JavaBeans版的getter和setter方法

如果主构造器的表示法让你困惑,则你不需要使用它。你只要按照常规的做法提供一个或多个辅助构造器即可,不过要记得调用this()(如果你不和其他辅助构造器串接的话)。
你要额可以理解为:在Scala中,类也接受参数,就像方法一样。当你把主构造器的参数看作类参数时,不带val和var的参数就变得易于理解了。这样的参数的作用域涵盖了整个类。因此,你可以在方法中使用它们。而一旦你这样做了,编译器就自动帮你将它保存为字段。
如果想让主构造器变成私有的,可以像这样放置private 关键字:

class Demo private(val name: String, val age: Int) {
  ......
}

这样一来类用户就必须通过辅助构造器来构造Demo对象了。

嵌套类

在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。你可以在函数中定义函数,在类中定义类 。以下代码是在类中定义类的一个示例:

import scala.collection.mutable.ArrayBuffer

class Network {

  class Member(val name: String) {
    val contacts = new ArrayBuffer[Member]
  }

  private val members = new ArrayBuffer[Member]

  def join(name: String): Member = {
    val member = new Member(name)
    members += member
    member
  }
}

考虑有如下两个网络:

val chatter = new Network 
val myFace = new Network

在Scala中,每个实例都有它自己的Member类,就和它们有自己的members字段一样。也就是说,chatter.MembermyFace.Member是不同的两个类。
说明:这和Java不同,在Java中的内部类从属于外部类。Scala采用的方式更符合常规。举例来说,要构建一个新的内部对象,你只需要简单地new这个类名:new chatter.Member。而在Java中,你需要使用一个特殊语法:chatter.new Member()
以上面的网络示例来讲,你可以在各自的网络中添加成员,但不能跨网添加成员。

val fred =chatter.join("Fred") 
val wilma = chatter.join("Wilma")
fred.contacts += wilma // OK 
val barney = myFace.join("Barney") // 类型 myFace.Member
fred.contacts += barney // 不可以这样做,不能将一个myFace.Member添加到chatter.Member元素缓冲当中

如果你不希望是这个效果,则有两种解决方式:
首先,你可以将Member类移到别处。一个不错的位置是Network的伴生对象。

object Network { 
   class Member(val name: String) { 
     val contacts = new ArrayBuffer[Member]
   }
}
class Network {
   private val members = new ArrayBuffer[Network.Member]
   ...
}

你也可以使用类型投影( type projection ) Network#Member ,其含义是“任何Network的member”。例如:

class Network { 
   class Member(val name: String) { 
      val contacts = new ArrayBuffer[Network#Member]
   }
}

如果你只想在某些地方,而不是所有地方,利用这个细粒度的“每个对象有自己的内部类”的特性,则可以考虑使用类型投影。
在嵌套类中,你可以通过外部类.this的方式来访问外部类的this引用,就像Java那样。如果你觉得需要,也可以用如下语法建立一个指向该引用的别名:

class Network (val name: String) { outer => 
    class Member (val name: String) {
       ...
       def description = s"$name inside ${outer.name}"
    }
}

class Network { outer =>语法使得outer变量指向Network.this。对这个变量 ,你可以用任何合法的名称。 self这个名称很常见,但用在嵌套类中可能会引发歧义。这样的语法和“自身类型”语法相关。

object(对象)

Scala没有静态方法或静态字段,可以用object这个语法结构来达到同样的目的。
对象的构造器在该对象第一次被使用时调用。对象本质上可以拥有类的所有特性,可以扩展其他类和特质。只有一个例外:不能提供构造器参数。
在Scala中,你可以通过类和与类同名的“伴生(companion)”对象来达到既有实例方法又有静态方法的类;类和它的伴生对象可以相互访问私有特性,它们必须存在于同一个源文件中。
注意1:类的伴生对象的功能特性并不在类的作用域内。
一个object可以扩展类以及一个或多个特质,其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。
注意2:Array(100)和new Array(100)很容易搞混。前一个表达式调用的是apply(100),交出一个单元素(整数100)的Array[Int],而第二个表达式调用的是构造器this(100),结果是Array[Nothing],包含了100个null元素。
每个Scala程序都必须从一个对象的main方法开始,如下:

object Demo {
  def main(args: Array[String]): Unit = {
    println("=======")
  }
}

除了每次都提供自己的main方法,你还可以扩展App特质,然后将程序代码放入构造器方法体内,如下:

object Demo extends App {
  println("=======")
}

如果需要命令行参数,则可以通过args属性得到,如下:

object Demo extends App {
  if (args.length > 0) {
    println(f"args: ${args(0)}")
    println(s"args: ${args(0)}")
  }
}

所有这些涉及一些小小的魔法。App特质扩展自另一个特质DelayedInit ,编译器对该特质有特殊处理。所有带有该特质的类,其初始化方法都会被挪到delayedini法中。App特质的main方法捕获到命令行参数,调用delayedInit方法,并且还可以根据要求打印出逝去的时间(-Dscala.time)。

枚举

Scala并没有枚举类型;不过,标准类库提供了一个Enumeration助手类,可以用于产出枚举。
定义一个扩展Enumeration类的对象并以Value方法调用初始化枚举中的所有可选值。例如:

object Demo extends Enumeration {
  val Red, Yellow, Green = Value
  def main(args: Array[String]): Unit = {
    val Red = Value("stop")
    val Yellow = Value("waiting")
    val Green = Value(7, "go")
    println(s"${Red.id}, ${Yellow.id}, ${Green.id}") // 输出: 3, 4, 7
    println(s"${Demo2.Red.id}, ${Demo2.Yellow.id}, ${Demo2.Green.id}") // 输出: 0, 1, 2
  }
}

每次调用Value方法都返回内部类的新实例,该内部类也叫作Value。或者,你也可以向Value方法传入ID 、名称,或两个参数都传。如果不指定,将在前一个枚举值的基础上加1,从0开; 默认名称为字段名。
记住枚举的类型是Demo.Value而不是Demo,后者是握有这些值的对象。也可以增加一个类型别名:

object Demo extends Enumeration {
  type Demo = Value
  val Red, Yellow, Green = Value
}

现在枚举的类型变成了Demo.Demo,但仅当你使用import语句时这样做才显得有意义。例如:

import Demo._
def doWhat(color: Demo)={
    if(color == Red) "stop"
    else if(color == Yellow) "waitting"
    else "go"
}

枚举值的ID可通过id方法返回,名称通过toString方法返回。

参考:快学scala(第二版)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值