第4章——善用对象

Scala 是一门完全面向对象的编程语言,为类的创建和对象的处理提供了简洁的语法。 Java 中能做的,在 Scala 中都可以做,Scala 还额外提供了一些更强大的特性,以帮助我们进 行面向对象编程。尽管 Scala 是一门纯面向对象的编程语言,但是它也支持一些 Java 中不是 那么纯粹的面向对象概念,如静态方法 ① 。利用伴生对象,Scala 以一种相当有趣的方式处理 了这个问题。伴生对象是伴随一个类的单例,在 Scala 中非常常见。

4.1 创建并使用类

WorkingWithObjects/Car.java

//Java example
public class Car {
  private final int year;
  private int miles;    
  
  public Car(int yearOfMake) { year = yearOfMake; }
  
  public int getYear() { return year; }
  public int getMiles() { return miles; }
  
  public void drive(int distance) {                   
    miles += Math.abs(distance);
  }
}

WorkingWithObjects/UseCar.scala

class Car(val year: Int) {
  private var milesDriven: Int = 0

  def miles: Int = milesDriven

  def drive(distance: Int): Unit = {
    milesDriven += Math.abs(distance)
  }
}

运行结果

Car made in year 2015
Miles driven 0
Drive for 10 miles
Miles driven 10

WorkingWithObjects/CreditCard.scala

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

运行结果

Compiled from "CreditCard.scala"
public class CreditCard {
  private final int number;
  private int creditLimit;
  public int number();
  public int creditLimit();
  public void creditLimit_$eq(int);
  public CreditCard(int, int);
}

WorkingWithObjects/Construct.scala

class Construct(param: String) {
  println(s"Creating an instance of Construct with parameter $param")
}

println("Let's create an instance")
new Construct("sample")

运行结果

Let's create an instance
Creating an instance of Construct with parameter sample

WorkingWithObjects/Person.scala

class Person(val firstName: String, val lastName: String) {
  var position: String = _

  println(s"Creating $toString")

  def this(firstName: String, lastName: String, positionHeld: String) {
    this(firstName, lastName)
    position = positionHeld
  }
  override def toString: String = {
    s"$firstName $lastName holds $position position"
  }
}

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

运行结果

Creating John Smith holds null position
John Smith holds Analyst position
Creating Bill Walker holds null position
Bill Walker holds null position

反编译结果

private java.lang.String position;
public java.lang.String position();
public void position_$eq(java.lang.String);

4.2 遵循 JavaBean 惯例

WorkingWithObjects/Dude.scala

import scala.beans.BeanProperty

class Dude(@BeanProperty val firstName: String, val lastName: String) {
  @BeanProperty var position: String = _
}

反编译结果

Compiled from "Dude.scala"
public class Dude {
  private final java.lang.String firstName;
  private final java.lang.String lastName;
  private java.lang.String position;
  public java.lang.String firstName();
  public java.lang.String lastName();
  public java.lang.String position();
  public void position_$eq(java.lang.String);
  public void setPosition(java.lang.String);
  public java.lang.String getFirstName();
  public java.lang.String getPosition();
  public Dude(java.lang.String, java.lang.String);
}

4.3 类型别名

WorkingWithObjects/PoliceOfficer.scala

class PoliceOfficer(val name: String)

WorkingWithObjects/CopApp.scala

object CopApp extends App {
  type Cop = PoliceOfficer

  val topCop = new Cop("Jack")
  println(topCop.getClass)
}

运行结果

class PoliceOfficer

4.4 扩展一个类

WorkingWithObjects/Vehicle.scala

class Vehicle(val id: Int, val year: Int) {
  override def toString = s"ID: $id Year: $year"
}

class Car(override val id: Int, override val year: Int, var fuelLevel: Int) extends Vehicle(id, year) {
  override def toString = s"${super.toString} Fuel Level: $fuelLevel"
}

val car = new Car(1, 2015, 100)
println(car)

运行结果

ID: 1 Year: 2015 Fuel Level: 100

4.5 参数化类型

WorkingWithObjects/Parameterized.scala

def echo[T](input1: T, input2: T): Unit =
  println(s"got $input1 (${input1.getClass}) $input2 (${input2.getClass})")

WorkingWithObjects/Parameterized.scala

echo("hello", "there")
echo(4, 5)

运行结果

got hello (class java.lang.String) there (class java.lang.String)
got 4 (class java.lang.Integer) 5 (class java.lang.Integer)

WorkingWithObjects/Parameterized.scala

echo("hi", 5)

运行结果

got hi (class java.lang.String) 5 (class java.lang.Integer)

WorkingWithObjects/EchoErr.scala

echo[Int]("hi", 5) //error: type mismatch

WorkingWithObjects/Parameterized.scala

def echo2[T1, T2](input1: T1, input2: T2): Unit =
  println(s"received $input1 and $input2")

echo2("Hi", "5")

WorkingWithObjects/Parameterized.scala

class Message[T](val content: T) {
  override def toString = s"message content is $content"

  def is(value: T): Boolean = value == content
}

WorkingWithObjects/Parameterized.scala

val message1: Message[String] = new Message("howdy")
val message2 = new Message(42)

println(message1)
println(message1.is("howdy"))
println(message1.is("hi"))
println(message2.is(22))

运行结果

message content is howdy
true
false
false

WorkingWithObjects/Message.scala

message1.is(22) //error: type mismatch

WorkingWithObjects/Message.scala

message2.is('A') //No error!

4.6 单例对象和伴生对象

WorkingWithObjects/Singleton.scala

import scala.collection._

class Marker(val color: String) {
  println(s"Creating ${this}")

  override def toString = s"marker color $color"
}

object MarkerFactory {
  private val markers =
    mutable.Map("red" -> new Marker("red"), "blue" -> new Marker("blue"), "yellow" -> new Marker("yellow"))

  def getMarker(color: String): Marker =
    markers.getOrElseUpdate(color, new Marker(color))
}

println(MarkerFactory.getMarker("blue"))
println(MarkerFactory.getMarker("blue"))
println(MarkerFactory.getMarker("red"))
println(MarkerFactory.getMarker("red"))
println(MarkerFactory.getMarker("green"))

运行结果

Creating marker color red
Creating marker color blue
Creating marker color yellow
marker color blue
marker color blue
marker color red
marker color red
Creating marker color green
marker color green

WorkingWithObjects/Marker.scala

import scala.collection._

class Marker private (val color: String) {
  println(s"Creating ${this}")

  override def toString = s"marker color $color"
}

object Marker {
  private val markers =
    mutable.Map("red" -> new Marker("red"), "blue" -> new Marker("blue"), "yellow" -> new Marker("yellow"))

  def getMarker(color: String): Marker =
    markers.getOrElseUpdate(color, new Marker(color))
}

println(Marker.getMarker("blue"))
println(Marker.getMarker("blue"))
println(Marker.getMarker("red"))
println(Marker.getMarker("red"))
println(Marker.getMarker("green"))

运行结果

Creating marker color red
Creating marker color blue
Creating marker color yellow
marker color blue
marker color blue
marker color red
marker color red
Creating marker color green
marker color green

WorkingWithObjects/Static.scala

import scala.collection._

class Marker private (val color: String) {
  override def toString = s"marker color $color"
}
object Marker {
  private val markers =
    mutable.Map("red" -> new Marker("red"), "blue" -> new Marker("blue"), "yellow" -> new Marker("yellow"))

  def supportedColors: Iterable[String] = markers.keys
  def apply(color: String): Marker = markers.getOrElseUpdate(color, op = new Marker(color))
}
println(s"Supported colors are : ${Marker.supportedColors}")
println(Marker("blue"))
println(Marker("red"))

运行结果

Creating marker color red
Creating marker color blue
Creating marker color yellow
marker color blue
marker color blue
marker color red
marker color red
Creating marker color green
marker color green

WorkingWithObjects/Greeter.scala

object Greeter {
  def greet(): Unit = println("Ahoy, me hearties!")
}

运行结果

Compiled from "Greeter.scala"
public final class Greeter {
  public static void greet();
}

4.7 创建枚举类

WorkingWithObjects/Currency.scala

package chapter4.finance1.finance.currencies

object Currency extends Enumeration {
  type Currency = Value
  val CNY, GBP, INR, JPY, NOK, PLN, SEK, USD = Value
}

WorkingWithObjects/finance1/finance/currencies/Money.scala

package chapter4.finance1.finance.currencies

import Currency._

class Money(val amount: Int, val currency: Currency) {
  override def toString = s"$amount $currency"
}

WorkingWithObjects/UseCurrency.scala

import chapter4.finance1.finance.currencies.Currency

object UseCurrency extends App {
  Currency.values.foreach { currency =>
    println(currency)
  }
}

运行结果

CNY
GBP
INR
JPY
NOK
PLN
SEK
USD

4.8 包对象

WorkingWithObjects/finance1/finance/currencies/Converter.scala

package chapter4.finance1.finance.currencies

import Currency._

object Converter {
  def convert(money: Money, to: Currency): Money = {
    //fetch current market rate... using mocked value here
    val conversionRate = 2
    new Money(money.amount * conversionRate, to)
  }
}

WorkingWithObjects/finance1/finance/currencies/Charge.scala

package chapter4.finance1.finance.currencies

object Charge {
  def chargeInUSD(money: Money): String = {
    def moneyInUSD = Converter.convert(money, Currency.USD)
    s"charged $$${moneyInUSD.amount}"
  }
}

WorkingWithObjects/finance1/CurrencyApp.scala

import chapter4.finance1.finance.currencies._

object CurrencyApp extends App {
  var moneyInGBP = new Money(10, Currency.GBP)

  println(Charge.chargeInUSD(moneyInGBP))

  println(Converter.convert(moneyInGBP, Currency.USD))
}

WorkingWithObjects/finance2/finance/currencies/package.scala

package chapter4.finance2.finance

package object currencies {
  import Currency._

  def convert(money: Money, to: Currency): Money = {
    //fetch current market rate... using mocked value here
    val conversionRate = 2
    new Money(money.amount * conversionRate, to)
  }
}

WorkingWithObjects/finance2/finance/currencies/Charge.scala

package chapter4.finance2.finance.currencies

object Charge {
  def chargeInUSD(money: Money): String = {
    def moneyInUSD = convert(money, Currency.USD)
    s"charged $$${moneyInUSD.amount}"
  }
}

WorkingWithObjects/finance2/CurrencyApp.scala

package chapter4.finance2

import chapter4.finance2.finance.currencies._

object CurrencyApp extends App {
  var moneyInGBP = new Money(10, Currency.GBP)

  println(Charge.chargeInUSD(moneyInGBP))

  println(convert(moneyInGBP, Currency.USD))
}

运行结果

CNY
GBP
INR
JPY
NOK
PLN
SEK
USD