【Scala】Scala之Classes and Properties(一)

简介: 前面学习了控制结构,下面学习Scala的Class和Properties。

一、前言


  前面学习了控制结构,下面学习Scala的Class和Properties。


二、Class&Properties


  尽管Scala和Java很类似,但是对类的定义、类构造函数、字段可见性控制等则不相同,Java更为冗长,Scala精炼。本章将通过Scala的构造函数工作原理来理解Scala的类和字段,当申明类构造函数参数和使用var、val、private关键字来修饰类的字段时,Scala编译器将会为你生成代码,根据字段修饰符不同,Scala编译器会生成不同的存取函数,本章也会展示如何重写这些函数,另外,Scala将会根据你的值为变量赋予相应的类型。

  2.1 创建主构造函数

  1. 问题描述

  你需要为一个类创建主构造函数,你很快就会发现其和Java大不相同。

  2. 解决方案

  Scala的主构造函数是如下的结合

  · 构造函数参数

  · 类主体中调用的方法

  · 类主体中执行的语句和表达式

  Scala类主体中声明的字段与Java类似,他们在类初始化时被赋值,下面的类演示构造函数的参数、类字段和类中的语句

  5.png

  由于类主体中的方法也是构造函数的一部分,当创建一个Person实例时,你会看到println的输出和printHome和printFullName函数调用的输出

  3 讨论

  如果你是从Java到Scala,你会发现在Scala中声明一个主构造函数时非常不同的,在Java中,在不在主构造函数中都是非常明显的,但是Scala模糊了这个区别,一旦你理解了这个方法,你将会更精确的定义类构造函数,由于构造函数中的firstName和lastName被声明为var类型,因此其是可变的,Scala会为其生成存取函数

  6.png

  HOME字段时private和val的,其在Java中等效于final和private的,其不能被其他对象直接访问,并且其值不能被改变,使用scalac进行编译后,再用javap进行反编译结果如下

  7.png

  可以看到包含了很多_$eq方法,这是var字段的语法糖,对用户透明,对于如下类 

class Person {
    var name = ""
    override def toString = s"name = $name"
}

由于name是var类型的,因此Scala会为其生成存取函数,即将会生成name_$eq函数,有了该函数,你可以修改name字段 

p.name = "Ron Artest"

实际上,你调用的是如下方法  

p.name_$eq("Ron Artest")

可以看到Scala的代码更为精炼,在声明字段时需要注意Scala是否会生成该存取函数

  2.2 控制构造函数字段的可见性

  1. 问题描述

  你想控制Scala中的类中作为构造函数参数的字段的可见性

  2. 解决方案

  构造函数中字段的可见性由该字段是否被声明为val或var或无val、var,是否为字段添加了private决定,解决方案如下

    · 如果字段被声明为var,Scala会为其生成getter和setter方法

    · 如果字段被声明为val,Scala会为只其生成getter方法

    · 如果字段无var或var,Scala将不会生成getter或setter方法

    · var和val字段可以使用private关键字修饰,这样可以防止生成getter或setter方法

  如果字段被声明为var,则表示该字段的值是可变的,所以Scala为其生成getter和setter函数,下面示例中的字段被声明为var,则表示可变

  10.png

  如果字段被声明为val,则表示该字段的值是不可变的,如同Java的final,因此,其不会有setter方法,但是有getter方法

  11.png

  当构造函数中的参数没有使用var或者val声明时,其可见性变得十分严格,Scala不会为其生成setter或getter方法

  12.png

  除了将变量定义为val或var之外,还可以使用private关键字修饰字段,private关键字可以防止Scala生成getter或setter方法,因此该字段只能在类内部访问

  13.png

  在类外访问name字段时,发生异常,而在类函数中访问时正常

  3. 讨论

  当字段定义为val时,则不会产生setter,若定义为var时,则会产生setter和getter,private关键可以提供其他的灵活性。case类中的构造器中的参数与上述情况不太相同,如果你没有使用var或val修饰字段,如 

case class Person(name: String)

此时你也可以访问该字段,就像该字段被定义为val一样

  14.png

  2.3 定义辅助构造器

  1. 问题描述

  你要定义类的一个或多个辅助构造函数,以便用户不同方法来创建对象实例

  2. 解决方案

  将辅助构造函数定义为名字为this的类的方法,你可以定义多个辅助构造函数,但是它们需要有不同的函数签名(参数列表),每个构造函数必须调用前面定义的多个构造函数中的一个。

class Pizza (var crustSize: Int, var crustType: String) {
    // one-arg auxiliary constructor
    def this(crustSize: Int) {
        this(crustSize, Pizza.DEFAULT_CRUST_TYPE)
    }
    // one-arg auxiliary constructor
    def this(crustType: String) {
        this(Pizza.DEFAULT_CRUST_SIZE, crustType)
    }
    // zero-arg auxiliary constructor
    def this() {
        this(Pizza.DEFAULT_CRUST_SIZE, Pizza.DEFAULT_CRUST_TYPE)
    }
    override def toString = s"A $crustSize inch pizza with a $crustType crust"
}
object Pizza {
    val DEFAULT_CRUST_SIZE = 12
    val DEFAULT_CRUST_TYPE = "THIN"
    def main(args: Array[String]): Unit = {
        val p1 = new Pizza(Pizza.DEFAULT_CRUST_SIZE, Pizza.DEFAULT_CRUST_TYPE)
        val p2 = new Pizza(Pizza.DEFAULT_CRUST_SIZE)
        val p3 = new Pizza(Pizza.DEFAULT_CRUST_TYPE)
        val p4 = new Pizza
        println(p1)
        println(p2)
        println(p3)
        println(p4)
    }
}

运行结果 

A 12 inch pizza with a THIN crust
A 12 inch pizza with a THIN crust
A 12 inch pizza with a THIN crust
A 12 inch pizza with a THIN crust

3. 讨论

  关键点如下

    · 辅助构造函数时以this为名字的类方法

    · 每个辅助构造函数必须以对先前定义的构造函数的调用开始

    · 每个辅助构造函数必须有不同的函数签名

    · 构造函数通过this来调用其他构造函数

  上述示例中的所有辅助构造函数都调用了主构造函数,但是不是只能调用主构造函数,还可调用辅助构造函数,如下所示 

def this(crustType: String) {
    this(Pizza.DEFAULT_CRUST_SIZE)
    this.crustType = Pizza.DEFAULT_CRUST_TYPE
}

同时,crustSize和crustType都是主构造函数中的参数,其可以使得Scala为其生成setter和getter函数,你也可以使用如下方法,其需要更多代码 

class Pizza () {
    var crustSize = 0
    var crustType = ""
    def this(crustSize: Int) {
        this()
        this.crustSize = crustSize
    }
    def this(crustType: String) {
        this()
        this.crustType = crustType
    }
    // more constructors here ...
    override def toString = s"A $crustSize inch pizza with a $crustType crust"
}

case类是一个可以生成很多样板代码的特殊类,为case类添加辅助构造器与常规类的做法不相同,这是因为它们不是真正的构造函数:它们是类的伴生对象中的apply
方法,为了进行验证,先定义一个Person的case类

  15.png

  在val p = new Person("leesf", 25)代码后,由于语法糖的存在,Scala编译器将其转化为了如下代码

val p = Person.apply("leesf", 25)

这是伴生对象中的apply方法,如果你想要为case类添加构造函数,你只需要在伴生对象中添加apply方法即可

// the case class
case class Person (var name: String, var age: Int)
// the companion object
object Person {
    def apply() = new Person("<no name>", 0)
    def apply(name: String) = new Person(name, 0)
}

测试代码如下 

object CaseClassTest extends App {
    val a = Person() // corresponds to apply()
    val b = Person("leesf") // corresponds to apply(name: String)
    val c = Person("dyd", 25)
    println(a)
    println(b)
    println(c)
    // verify the setter methods work
    a.name = "leesf"
    a.age = 25
    println(a)
}

 运行结果 

Person(<no name>,0)
Person(leesf,0)
Person(dyd,25)
Person(leesf,25)

2.4 定义私有主构造函数

  1. 问题描述

  你想要生成类私有的主构造函数,如强制执行单例模式

  2. 解决方案

  为了让主构造器私有,需要在类名和构造函数所接受的任何参数之间插入private关键字 

class Order private { ... }
class Person private (name: String) { ... }

 在REPL中,定义了私有类后,不能创建类实例

  16.png

  3. 讨论

  在Scala中强制单例模式的一种简单方法就是使主构造器变为private,然后在伴生对象中添加getInstance方法 

class Brain private {
    override def toString = "This is the brain."
}
object Brain {
    val brain = new Brain
    def getInstance = brain
}
object SingletonTest extends App {
    val brain = Brain.getInstance
    println(brain)
}

 运行结果: 

This is the brain.

其中伴生类对象中的getInstance是静态方法

  在Java中,你可以在类中定义静态方法来创建文件工具类,但在Scala中,你只需要将这些方法放在对象中即可

object FileUtils {
    def readFile(filename: String) = {
        // code here ...
    }
    def writeToFile(filename: String, contents: String) {
        // code here ...
    }
}

说明:可以通过FileUtils.readFile和FileUtils.writeToFile来调用相应的方法

  2.5 为构造函数参数提供默认值

  1. 问题描述

  你想要为构造器参数提供默认值,给其他类调用构造函数时可决定是否指定该参数

  2. 解决方案

  在构造函数声明中给予参数默认值,这里有一个简单的声明,Socket类有一个构造函数参数名为timeout,默认值为10000 

class Socket (val timeout: Int = 10000)

 由于构造函数提供了默认参数,因此在调用时可以不指定timeout的值

  19.png

  也可以指定timeout的值

  20.png

  如果你更喜欢调用构造函数时使用命名参数的方法,你也可以用如下方法调用构造函数

  21.png

  3. 讨论

  默认参数可以有效消除辅助构造函数,如下的构造函数相当于两个构造函数(不指定参数值和指定参数值)

class Socket (val timeout: Int = 10000)

 若没有默认参数这一特性,则需要定义一个辅助构造函数 

class Socket(val timeout: Int) {
    def this() = this(10000)
    override def toString = s"timeout: $timeout"
}

你可以为多参数的构造函数提供多个默认值 

class Socket(val timeout: Int = 1000, val linger: Int = 2000) {
    override def toString = s"timeout: $timeout, linger: $linger"
}

 尽管你只定义了一个构造函数,但实际上有三个构造函数

  25.png

  在创建对象时你也可以提供参数名

  26.png

  2.6 覆盖默认的访问器和修改器

  1. 问题描述

  你想要覆盖Scala为你生成的访问器和修改器

  2. 解决方案

  这是一个技巧的问题,如果你想坚持用Scala的命名约定时,你不能重写Scala为你生成的getter和setter方法,例如Person类有name参数,以下代码将不会被编译

  27.png

  为了解决这个问题,可以构造函数的字段名,如将name改为_name

  28.png

  name方法为访问器,name_=方法为修改器

  29.png

  如果你不想使用Scala为你定义访问器和修改器的名字,你可以自由更改,如改为getName、setName。

  3. 讨论

  如果你将类构造器的字段设置为var,则Scala会自动生成getter和setter方法,对于如下类 

class Stock (var symbol: String)

首先使用scalac进行编译,然后使用javap Stock进行反编译,结果如下

  30.png

  可以看到Scala生成了symbol方法和symbol_$eq方法,可以直接通过方法名调用,对于symbol_$eq而言,还可以使用symbol_=来调用

  31.png

  如果本想对字段加private,然而没有加,如下类 

class Stock (var _symbol: String) {
    def symbol = _symbol
    def symbol_= (s: String) {
        this.symbol = s
        println(s"symbol was updated, new value is $symbol")
    }
}

同样经过编译和反编译,结果如下

  35.png

  可以看到姿势的_symbol函数和_symbol_$eq函数都变为public了,而加private修饰时,其结果如下

  36.png

目录
相关文章
|
算法 Java 编译器
【Scala】Scala之Classes and Properties(二)
前面学习了控制结构,下面学习Scala的Class和Properties。
105 0
【Scala】Scala之Classes and Properties(二)
|
2月前
|
分布式计算 大数据 Java
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
69 5
|
2月前
|
分布式计算 关系型数据库 MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
55 3
|
2月前
|
消息中间件 分布式计算 NoSQL
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
45 0
|
2月前
|
消息中间件 存储 分布式计算
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
101 0
|
2月前
|
分布式计算 大数据 Java
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
40 1
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
|
2月前
|
SQL 分布式计算 Java
大数据-96 Spark 集群 SparkSQL Scala编写SQL操作SparkSQL的数据源:JSON、CSV、JDBC、Hive
大数据-96 Spark 集群 SparkSQL Scala编写SQL操作SparkSQL的数据源:JSON、CSV、JDBC、Hive
59 0
|
2月前
|
缓存 分布式计算 大数据
大数据-90 Spark 集群 RDD 编程-高阶 RDD容错机制、RDD的分区、自定义分区器(Scala编写)、RDD创建方式(一)
大数据-90 Spark 集群 RDD 编程-高阶 RDD容错机制、RDD的分区、自定义分区器(Scala编写)、RDD创建方式(一)
60 0