【Scala】Scala之Classes and Properties(二)

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

2.7 防止生成getter和setter方法

  1. 问题描述

  当你定义类的构造函数参数为var时,Scala将会自动生成setter和getter方法,若是val,则生成getter方法,但是你既不需要setter,也不需要getter

  2. 解决方案

  将字段使用private或者private[this]修饰,如下面示例 

class Stock {
    var delayedPrice: Double = _
    private var currentPrice: Double = _
}

使用scalac进行编译,再使用javap进行反编译,结果如下

  40.png

  可以看到delayedPrice字段生成了getter和setter方法,而currentPrice字段则没有生成getter和setter方法

  3. 讨论

  将字段定义为private,这样其只能被同类的所有实例访问,例如,所有的Stock实例可以互相访问currentPrice字段,如下面示例的结果为true

  41.png

  将字段定义为private[this]将会使字段变为对象私有,只有包含它的对象能够访问,同类的实例将无法访问其他实例的字段

  42.png

  可以看到在this对象的方法中无法访问that对象的price字段

  2.8 将代码块或函数赋值给字段

  1. 问题描述

  你想要使用代码块或通过调用函数初始化类中的字段

  2. 解决方案

  设置字段等于代码块或函数,如果算法需要运行一段时间,则可以设置为lazy,下面示例中,text被设置成等于代码块,它要么返回文件中包含的文本,要么返回错误信息,这取决于该文件是否存在和是否能被读取 

class Foo {
    // set 'text' equal to the result of the block of code
    val text = {
        var lines = ""
        try {
            lines = io.Source.fromFile("F:/test.txt").getLines.mkString
        } catch {
            case e: Exception => lines = "Error happened"
        }
        lines
    }
    println(text)
}
object Test extends App {
    val f = new Foo
}

运行结果

This is a test file.

对text的赋值和println都在Foo类构造器中,当新生实例时会被执行,因此,编译运行该示例时要么返回文件中的内容,要么发生错误,在catch中被捕获

  3. 讨论

  当字段被定义成lazy时,只有当被访问时才会被初始化 

class Foo {
    val text =
        io.Source.fromFile("F:/test.txt").getLines.foreach(println)
}
object Test extends App {
    val f = new Foo
}

运行结果

This is a test file.

当字段使用lazy修饰时

class Foo {
    lazy val text =
        io.Source.fromFile("F:/test.txt").getLines.foreach(println)
}
object Test extends App {
    val f = new Foo
}

 无任何结果输出

  2.9 设置未初始化的var字段类型

  1. 问题描述

  你想要设置未初始化的var字段的类型,你可能会这样写,但是不知道如何结尾 

var x =

2. 解决方案

  通常,将字段定义为Option,对于有些类型,如String和numeric字段,你可以指明初始值,例如,你想要用户在社交网中注册,注册使用到的信息只有username和password,因此,你可以有如下类定义

case class Person(var username: String, var password: String)

之后,你也需要用户的其他信息,如age, first name, last name, address,可以有如下定义  

case class Person(var username: String, var password: String) {
    var age = 0
    var firstName = ""
    var lastName = ""
    var address = None: Option[Address]
}
case class Address(city: String, state: String, zip: String)

即使用Option[Address]类型的None作为缺省值,可以使用如下 

val p = Person("leesf", "secret")
p.address = Some(Address("WH", "HB", "43000"))

 当需要访问address的字段时,可以如下 

p.address.foreach { a =>
    println(a.city)
    println(a.state)
    println(a.zip)
}

 当p.address为None时,则会跳过,为Some时,则会打印相应信息

  3. 讨论

  编译器有时会自动给出类型,如下所示  

var i = 0 // Int
var d = 0.0 // Double

当需要自定义类型时,可以如下 

var b: Byte = 0
var c: Char = 0
var f: Float = 0
var l: Long = 0
var s: Short = 0

2.10 处理继承时的构造器参数

  1. 问题描述

  你想要扩展基类,并且需要使用基类中声明的构造函数参数,以及子类中的新参数

  2. 解决方案

  还是使用var和val来定义构造函数参数,当定义子类构造函数时,父类构造函数中的字段不再使用var修饰,在子类构造函数中定义新的字段

class Person (var name: String, var address: Address) {
    override def toString = if (address == null) name else s"$name @ $address"
}

首先定义了Person基类,然后定义Employee子类 

class Employee (name: String, address: Address, var age: Int)
  extends Person (name, address) {
    // rest of the class
}

 Employee继承Person类,不再使用var修饰name和address,但是age在父类中不存在,则使用var修饰,其中Address类如下

case class Address (city: String, state: String)

 可以使用如下方法创建子类  

val lee = new Employee("Leesf", Address("JZ", "HB"), 25)

 3. 讨论

  为了理解构造函数参数在子类中是如何工作的,也可帮助理解Scala是如何翻译你的代码的,由于Person的参数被定义为var 

class Person (var name: String, var address: Address) {
    override def toString = if (address == null) name else s"$name @ $address"
}

Scala编译器将会生成getter和setter

  45.png

  此时,如果Employee继承Person,在Employee构造函数中如何处理name和address字段,加入没有新的参数,那么至少有两种选择

// Option 1: define name and address as 'var'
class Employee (var name: String, var address: Address)
    extends Person (name, address) { ... }
// Option 2: define name and address without var or val
class Employee (name: String, address: Address)
    extends Person (name, address) { ... }

由于Scala已经在Person类中生成了setter和getter函数,一种解决办法就是在Employee的构造函数的参数不用var修饰 

// this is correct
class Employee (name: String, address: Address)
    extends Person (name, address) { ... }

由于Employee的构造函数中并未使用var修饰,因此Scala不会为字段生成getter和setter方法,验证代码如下 

case class Address (city: String, state: String)
class Person (var name: String, var address: Address) {
    override def toString = if (address == null) name else s"$name @ $address"
}
class Employee (name: String, address: Address)
    extends Person (name, address) {
}

 使用javap反编译Employee,结果如下

  46.png

  可以看到Scala并未为Employee类生成任何setter和getter方法

  2.11 调用父类构造函数

  1. 问题描述

  当创建子类构造函数时,你想控制父类构造函数

  2. 解决方案

  你可以通过子类的主构造函数来控制父类构造函数,但是不能通过子类的辅助构造函数来控制父类构造函数,当定义子类时,你可以通过定义extends部分来控制子类所调用的父类构造器,例如,Dog类被定义为调用Animal类,其有name参数 

class Animal (var name: String) {
    // ...
}
class Dog (name: String) extends Animal (name) {
    // ...
}

如果Animal类有多个构造函数,Dog子类可以调用任意的父类构造函数  

// (1) primary constructor
class Animal (var name: String, var age: Int) {
    // (2) auxiliary constructor
    def this (name: String) {
        this(name, 0)
    }
    override def toString = s"$name is $age years old"
}
// calls the Animal one-arg constructor
class Dog (name: String) extends Animal (name) {
    println("Dog constructor called")
}

由于辅助构造函数的第一行必须要调用其他构造函数,因此,辅助构造函数不能调用父类构造函数,下面代码中,Employee类的主构造含可以调用Person类的任意构造函数,但是Employee的辅助构造函数只能调用其他构造函数

case class Address (city: String, state: String)
case class Role (role: String)
class Person (var name: String, var address: Address) {
    // no way for Employee auxiliary constructors to call this constructor
    def this (name: String) {
        this(name, null)
        address = null
    }
    override def toString = if (address == null) name else s"$name @ $address"
}
class Employee (name: String, role: Role, address: Address)
    extends Person (name, address) {
    def this (name: String) {
        this(name, null, null)
    }
    def this (name: String, role: Role) {
        this(name, role, null)
    }
    def this (name: String, address: Address) {
        this(name, null, address)
    }
}

 所以无法通过子类辅助构造函数来控制父类构造函数,因为辅助构造函数必须要调用其他构造函数,所有的辅助构造函数最终会调用主构造函数,最终由主构造函数调用辅助构造函数

  2.12 何时使用抽象类

  1. 问题描述

  Scala有traits特性,并且traits比抽象类更灵活,那么何时使用抽象类呢

  2. 解决方案

  以下两种原因需要使用抽象类

    · 你想要创建一个包含构造函数参数的基类

    · 代码将会被Java代码调用

  考虑第一种情况,traits不允许构造函数参数,如下所示 

trait Animal(name: String) // this won't compile

当基类构造函数必须含有参数时应该使用抽象类 

abstract class Animal(name: String)

考虑第二种情况是你的代码将会被Java调用,traits中实现的方法无法被Java访问

  3. 讨论

  当构造函数有参数时,需要使用抽象类而非traits,但是,一个类只能继承一个抽象类,其与Java类似,当类中抽象方法时,该类就是抽象,方法体未定义的方法就是抽象方法  

def speak // no body makes the method abstract

不需要使用abstract关键字,如下示例 

abstract class BaseController(db: Database) {
    def save { db.save }
    def update { db.update }
    def delete { db.delete }
    // abstract
    def connect
    // an abstract method that returns a String
    def getStatus: String
    // an abstract method that takes a parameter
    def setServerName(serverName: String)
}

其中connect、getStatus、setServerName为抽象方法,子类必须要实现或者是为抽象类

  2.13 在抽象基类(或traits)中定义属性

  1. 问题描述

  你想要在抽象类或traits中定义具体的或抽象属性,以便所有的子类都可以引用

  2. 解决方案

  你可以在抽象类或traits中定义var或val字段,该字段可以是抽象的或者是具体定义的,下面示例展示了Pet抽象类中的抽象val和var字段,并且有一个sayHello的具体方法,以及重写的toString方法

abstract class Pet (name: String) {
    val greeting: String
    var age: Int
    def sayHello { println(greeting) }
    override def toString = s"I say $greeting, and I'm $age"
}

Dog和Cat类继承Pet,并且提供了greeting和age的具体实现,其中,字段给再次定义成val或var

class Dog (name: String) extends Pet (name) {
    val greeting = "Woof"
    var age = 2
}
class Cat (name: String) extends Pet (name) {
    val greeting = "Meow"
    var age = 5
}

 可使用如下 

object AbstractFieldsDemo extends App {
    val dog = new Dog("Fido")
    val cat = new Cat("Morris")
    dog.sayHello
    cat.sayHello
    println(dog)
    println(cat)
    // verify that the age can be changed
    cat.age = 10
    println(cat)
}

运行结果如下  

Woof
Meow
I say Woof, and I'm 2
I say Meow, and I'm 5
I say Meow, and I'm 10

 3. 讨论

  你可以在抽象类中定义val或var的抽象字段,抽象字段在抽象类中的工作机制非常有趣

    · 抽象的var字段也会生成getter和setter方法

    · 抽象的val字段会生成getter方法

    · 当在抽象类或traits中定义抽象的var或val字段时,Scala并不会生成该字段,只会生成方法

  当在具体类中重写父类抽象的var或val字段时,一定要再次注明val或var,因为父类中不存在该字段,不需要使用override关键字,当在抽象类中使用def来定义无参数的方法时,可以在子类中定义为val  

abstract class Pet (name: String) {
    def greeting: String
}
class Dog (name: String) extends Pet (name) {
    val greeting = "Woof"
}
object Test extends App {
    val dog = new Dog("Fido")
    println(dog.greeting)
}

 当在抽象类中定义具体的val字段时,你可以提供一个初始值,然后在子类中进行重写

abstract class Animal {
    val greeting = "Hello" // provide an initial value
    def sayHello { println(greeting) }
    def run
}
class Dog extends Animal {
    override val greeting = "Woof" // override the value
    def run { println("Dog is running") }
}

在如下示例中,greeting在父类和子类中都定义了  


abstract class Animal {
    val greeting = { println("Animal"); "Hello" }
}
class Dog extends Animal {
    override val greeting = { println("Dog"); "Woof" }
}
object Test extends App {
    new Dog
}

运行结果

Animal
Dog

为了防止抽象类中的val字段被子类覆盖,可以将其定义为final 

abstract class Animal {
    final val greeting = "Hello" // made the field 'final'
}
class Dog extends Animal {
    val greeting = "Woof" // this line won't compile
}

你也可以在抽象类中定义var变量,并为其赋值,然后在具体子类中引用它们

abstract class Animal {
    var greeting = "Hello"
    var age = 0
    override def toString = s"I say $greeting, and I'm $age years old."
}
class Dog extends Animal {
    greeting = "Woof"
    age = 2
}

 greeting字段和age字段在Animal类中都被定义和初始化了,在Dog类中就没有必要再重新声明为var或val类型了

  在上述讨论中,不应该使用null值,可以使用Option/Some/None来初始化字段 

trait Animal {
    val greeting: Option[String]
    var age: Option[Int] = None
    override def toString = s"I say $greeting, and I'm $age years old."
}
class Dog extends Animal {
    val greeting = Some("Woof")
    age = Some(2)
}
object Test extends App {
    val d = new Dog
    println(d)
}

运行结果 

I say Some(Woof), and I'm Some(2) years old.

2.14 使用样例类生成样板代码

  1. 问题描述

  你需要使用样例类来生成样板代码,如生成setter和getter方法,还有apply, unapply, toString, equals, hashCode等方法

  2. 解决方案

  将类定义成样例类,并且添加相应的构造函数参数

case class Person(name: String, relation: String)

 将类定义为样例类后,会生成很多样板代码,如

    · 生成apply方法,所以不需要是new关键字就可以创建实例

    · 为构造函数参数生成getter方法,因为其默认是val的,当被定义为var时,会生成setter方法

    · 会生成缺省的toString方法

    · 生成unapply方法,在模式匹配中将会很方便

    · 生成equals和hashCode方法

    · 生成copy方法

  3. 讨论

  样例类在模式匹配中很实用,其默认参数为val,当创建样例类时,Scala会为你生成很多样板代码

  2.15 定义equals方法(对象相等)

  1. 讨论

  你想要定义一个equals方法来比较不同对象之间的相等性

  2. 解决方案

  如同Java一样,你在类中定义equals和hashCode来比较两个对象,但与Java不同的是,你使用 == 来比较两个对象的相等性,下面定义了equals方法和hashCode方法 

class Person (name: String, age: Int) {
    def canEqual(a: Any) = a.isInstanceOf[Person]
    override def equals(that: Any): Boolean =
    that match {
        case that: Person => that.canEqual(this) && this.hashCode == that.hashCode
        case _ => false
    }
    override def hashCode:Int = {
        val prime = 31
        var result = 1
        result = prime * result + age;
        result = prime * result + (if (name == null) 0 else name.hashCode)
        return result
    }
}

接着可以使用 == 来比较两个Person对象是否相等

  3. 讨论

  在Java中,==是比较两个对象的引用是否相等,而在Scala中,其是根据用户定义逻辑进行比较,其调用equals方法,在涉及继承时,也可以使用如上方法 

class Employee(name: String, age: Int, var role: String)
    extends Person(name, age) {
    override def canEqual(a: Any) = a.isInstanceOf[Employee]
    override def equals(that: Any): Boolean =
        that match {
        case that: Employee =>
            that.canEqual(this) && this.hashCode == that.hashCode
        case _ => false
    }
    override def hashCode:Int = {
        val ourHash = if (role == null) 0 else role.hashCode
        super.hashCode + ourHash
    }
}

 2.16 创建内部类

  1. 问题描述

  你希望创建一个内部类,以将类排除在公共API之外,或者封装您的代码

  2. 解决方案

  将类声明在另一个类中,如下示例 

class PandorasBox {
    case class Thing (name: String)
    var things = new collection.mutable.ArrayBuffer[Thing]()
    things += Thing("Evil Thing #1")
    things += Thing("Evil Thing #2")
    def addThing(name: String) { things += new Thing(name) }
}

可以在PandorasBox内部使用things集合

object ClassInAClassExample extends App {
    val p = new PandorasBox
    p.things.foreach(println)
}

 运行结果  

Evil Thing #1
Evil Thing #2

 并且可以调用addThing方法 

p.addThing("Evil Thing #3")
p.addThing("Evil Thing #4")

 3. 讨论

  与Java的内部类属于外部类不同,Scala中的内部类绑定到外部类的对象上

object ClassInObject extends App {
    // inner classes are bound to the object
    val oc1 = new OuterClass
    val oc2 = new OuterClass
    val ic1 = new oc1.InnerClass
    val ic2 = new oc2.InnerClass
    ic1.x = 10
    ic2.x = 20
    println(s"ic1.x = ${ic1.x}")
    println(s"ic2.x = ${ic2.x}")
}
class OuterClass {
    class InnerClass {
        var x = 1
    }
}

 运行结果  

ic1.x = 10
ic2.x = 20


三、总结


  本篇博文学习了Scala中类的相关知识点,对比Java学习还是相对简单,也谢谢各位园友的观看~


目录
相关文章
|
Java 编译器 Scala
【Scala】Scala之Classes and Properties(一)
前面学习了控制结构,下面学习Scala的Class和Properties。
141 0
【Scala】Scala之Classes and Properties(一)
|
SQL 消息中间件 分布式计算
如何查看spark与hadoop、kafka、Scala、flume、hive等兼容版本【适用于任何版本】
如何查看spark与hadoop、kafka、Scala、flume、hive等兼容版本【适用于任何版本】
673 0
如何查看spark与hadoop、kafka、Scala、flume、hive等兼容版本【适用于任何版本】
|
3月前
|
分布式计算 Java Scala
spark 与 scala 的对应版本查看、在idea中maven版本不要选择17,弄了好久,换成11就可以啦
spark 与 scala 的对应版本查看、.在idea中maven版本不要选择17,弄了好久,换成11就可以啦
112 2
|
3月前
|
分布式计算 数据处理 Scala
Spark 集群和 Scala 编程语言的关系
Spark 集群和 Scala 编程语言的关系
29 0
|
4月前
|
分布式计算 Java Scala
Spark编程语言选择:Scala、Java和Python
Spark编程语言选择:Scala、Java和Python
Spark编程语言选择:Scala、Java和Python
|
12月前
|
存储 分布式计算 Scala
Spark-RDD 键值对的操作(Scala版)
Spark-RDD 键值对的操作(Scala版)
|
12月前
|
SQL 存储 JSON
人人都懂Spark-SQL基础操作(Scala版)
人人都懂Spark-SQL基础操作(Scala版)