第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);
  }
}
Full source at GitHub

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)
  }
}
Full source at GitHub

运行结果

Car made in year 2015
Miles driven 0
Drive for 10 miles
Miles driven 10
Full source at GitHub

WorkingWithObjects/CreditCard.scala

class CreditCard(val number: Int, var creditLimit: Int)
Full source at GitHub

运行结果

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);
}
Full source at GitHub

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")
Full source at GitHub

运行结果

Let's create an instance
Creating an instance of Construct with parameter sample
Full source at GitHub

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)
Full source at GitHub

运行结果

Creating John Smith holds null position
John Smith holds Analyst position
Creating Bill Walker holds null position
Bill Walker holds null position
Full source at GitHub

反编译结果

private java.lang.String position;
public java.lang.String position();
public void position_$eq(java.lang.String);
Full source at GitHub

4.2 遵循 JavaBean 惯例

WorkingWithObjects/Dude.scala

import scala.beans.BeanProperty

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

反编译结果

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);
}
Full source at GitHub

4.3 类型别名

WorkingWithObjects/PoliceOfficer.scala

class PoliceOfficer(val name: String)
Full source at GitHub

WorkingWithObjects/CopApp.scala

object CopApp extends App {
  type Cop = PoliceOfficer

  val topCop = new Cop("Jack")
  println(topCop.getClass)
}
Full source at GitHub

运行结果

class PoliceOfficer
Full source at GitHub

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)
Full source at GitHub

运行结果

ID: 1 Year: 2015 Fuel Level: 100
Full source at GitHub

4.5 参数化类型

WorkingWithObjects/Parameterized.scala

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

WorkingWithObjects/Parameterized.scala

echo("hello", "there")
echo(4, 5)
Full source at GitHub

运行结果

got hello (class java.lang.String) there (class java.lang.String)
got 4 (class java.lang.Integer) 5 (class java.lang.Integer)
Full source at GitHub

WorkingWithObjects/Parameterized.scala

echo("hi", 5)
Full source at GitHub

运行结果

got hi (class java.lang.String) 5 (class java.lang.Integer)
Full source at GitHub

WorkingWithObjects/EchoErr.scala

echo[Int]("hi", 5) //error: type mismatch
Full source at GitHub

WorkingWithObjects/Parameterized.scala

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

echo2("Hi", "5")
Full source at GitHub

WorkingWithObjects/Parameterized.scala

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

  def is(value: T): Boolean = value == content
}
Full source at GitHub

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))
Full source at GitHub

运行结果

message content is howdy
true
false
false
Full source at GitHub

WorkingWithObjects/Message.scala

message1.is(22) //error: type mismatch
Full source at GitHub

WorkingWithObjects/Message.scala

message2.is('A') //No error!
Full source at GitHub

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")
Full source at GitHub

运行结果

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
Full source at GitHub

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")
Full source at GitHub

运行结果

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
Full source at GitHub

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"))
Full source at GitHub

运行结果

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
Full source at GitHub

WorkingWithObjects/Greeter.scala

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

运行结果

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

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
}
Full source at GitHub

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"
}
Full source at GitHub

WorkingWithObjects/UseCurrency.scala

import chapter4.finance1.finance.currencies.Currency

object UseCurrency extends App {
  Currency.values.foreach { currency ⇒ println(currency) }
}
Full source at GitHub

运行结果

CNY
GBP
INR
JPY
NOK
PLN
SEK
USD
Full source at GitHub

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)
  }
}
Full source at GitHub

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}"
  }
}
Full source at GitHub

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))
}
Full source at GitHub

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)
  }
}
Full source at GitHub

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}"
  }
}
Full source at GitHub

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))
}
Full source at GitHub

运行结果

CNY
GBP
INR
JPY
NOK
PLN
SEK
USD
Full source at GitHub