第15章——使用Scala创建应用程序

在本章中,我们将会把在本书中学到的许多东西汇集到一起,并学习一些新东西。我们 将逐步构建一个应用程序,用于找到股票市场中的投资的净值。在这个练习中,我们将会看 到一些夺目的特性:简洁性与表现力、模式匹配的力量以及函数值/闭包和并发。此外,我们 还将学习 Scala 对 XML 处理的支持—一个在构建企业级应用时会极大受益的特性。 ①

  1. 从 Scala 2.11.x 版本开始,对 XML 的支持已经被移到了单独的模块中。——译者注

15.1 获取用户输入

UsingScala/ConsoleInput.scala

import scala.io._

print("Please enter a ticker symbol:")
val symbol = StdIn.readLine()
println(s"OK, got it, you own $symbol")

运行结果

Please enter a ticker symbol:OK, got it, you own AAPL

15.2 读写文件

UsingScala/WriteToFile.scala

import java.io._

val writer = new PrintWriter(new File("symbols.txt"))
writer.write("AAPL")
writer.close()
println(scala.io.Source.fromFile("symbols.txt").mkString)

UsingScala/ReadingFile.scala

import scala.io.Source

println("*** The content of the file you read is:")
Source.fromFile("ReadingFile.scala").foreach { print }

运行结果

*** The content of the file you read is:
import scala.io.Source

println("*** The content of the file you read is:")
Source.fromFile("ReadingFile.scala").foreach { print }

UsingScala/ReadingURL.scala

import scala.io.Source
import java.net.URL

val source = Source.fromURL(new URL("http://localhost"))

println(s"What's Source?: $source")
println(s"Raw String: ${source.mkString}")

运行结果

What's Source?: non-empty iterator
Raw String: <html><body><h1>It works!</h1></body></html>

15.3 XML 作为一等公民

UsingScala/UseXML.scala

val xmlFragment =
  <symbols>
    <symbol ticker="AAPL"><units>200</units></symbol>
    <symbol ticker="IBM"><units>215</units></symbol>
  </symbols>

println(xmlFragment)
println(xmlFragment.getClass)

运行结果

<symbols>
  <symbol ticker="AAPL"><units>200</units></symbol>
  <symbol ticker="IBM"><units>215</units></symbol>
</symbols>
class scala.xml.Elem

UsingScala/UseXML.scala

var symbolNodes = xmlFragment \ "symbol"
symbolNodes.foreach(println)
println(symbolNodes.getClass)

运行结果

<symbol ticker="AAPL"><units>200</units></symbol>
<symbol ticker="IBM"><units>215</units></symbol>
class scala.xml.NodeSeq$$anon$1

UsingScala/UseXML.scala

var unitsNodes = xmlFragment \\ "units"
unitsNodes.foreach(println)
println(unitsNodes.getClass)
println(unitsNodes.head.text)

运行结果

<units>200</units>
<units>215</units>
class scala.xml.NodeSeq$$anon$1
200

UsingScala/UseXML.scala

unitsNodes.head match {
  case <units>{numberOfUnits}</units> => println(s"Units: $numberOfUnits")
}

运行结果

Units: 200

UsingScala/UseXML.scala

println("Ticker\tUnits")
xmlFragment match {
  case <symbols>{symbolNodes @ _*}</symbols> =>
    for (symbolNode @ <symbol>{_*}</symbol> <- symbolNodes) {
      println("%-7s %s".format(symbolNode \ "@ticker", (symbolNode \ "units").text))
    }
}

运行结果

Ticker	Units
AAPL    200
IBM     215

15.4 读写 XML

stocks.xml

<symbols>
  <symbol ticker="AAPL"><units>200</units></symbol>
  <symbol ticker="ADBE"><units>125</units></symbol>
  <symbol ticker="ALU"><units>150</units></symbol>
  <symbol ticker="AMD"><units>150</units></symbol>
  <symbol ticker="CSCO"><units>250</units></symbol>
  <symbol ticker="HPQ"><units>225</units></symbol>
  <symbol ticker="IBM"><units>215</units></symbol>
  <symbol ticker="INTC"><units>160</units></symbol>
  <symbol ticker="MSFT"><units>190</units></symbol>
  <symbol ticker="NSM"><units>200</units></symbol>
  <symbol ticker="ORCL"><units>200</units></symbol>
  <symbol ticker="SYMC"><units>230</units></symbol>
  <symbol ticker="TXN"><units>190</units></symbol>
  <symbol ticker="VRSN"><units>200</units></symbol>
  <symbol ticker="XRX"><units>240</units></symbol>
</symbols> 

UsingScala/ReadWriteXML.scala

import scala.xml._

val stocksAndUnits = XML.load("stocks.xml")
println(stocksAndUnits.getClass)
println(s"File has ${(stocksAndUnits \\ "symbol").size} symbol elements")

运行结果

class scala.xml.Elem
File has 15 symbol elements

UsingScala/ReadWriteXML.scala

val stocksAndUnitsMap =
  (Map[String, Int]() /: (stocksAndUnits \ "symbol")) { (map, symbolNode) =>
    val ticker = (symbolNode \ "@ticker").toString
    val units = (symbolNode \ "units").text.toInt
    map + (ticker -> units) //return new map, with one additional entry
  }

println(s"Number of symbol elements found is ${stocksAndUnitsMap.size}")

运行结果

Number of symbol elements found is 15

UsingScala/ReadWriteXML.scala

val updatedStocksAndUnitsXML =
  <symbols>
    {stocksAndUnitsMap.map(updateUnitsAndCreateXML)}
  </symbols>

def updateUnitsAndCreateXML(element: (String, Int)) = {
  val (ticker, units) = element
  <symbol ticker={ticker}>
    <units>{units + 1}</units>
  </symbol>
}

XML.save("stocks2.xml", updatedStocksAndUnitsXML)

val elementsCount = (XML.load("stocks2.xml") \\ "symbol").size
println(s"Saved file has $elementsCount symbol elements")

运行结果

Saved file has 15 symbol elements

示例文件

Date,Open,High,Low,Close,Volume,Adj Close
2015-03-20,561.65,561.72,559.05,560.36,2585800,560.36
2015-03-19,559.39,560.80,556.15,557.99,1191100,557.99
2015-03-18,552.50,559.78,547.00,559.50,2124400,559.50
...

15.5 从 Web 获取股票价格

UsingScala/StockPriceFinder.scala


object StockPriceFinder { import scala.io.Source case class Record(year: Int, month: Int, date: Int, closePrice: BigDecimal) def getLatestClosingPrice(symbol: String): BigDecimal = { val url = s"https://raw.githubusercontent.com/ReactivePlatform/" + s"Pragmatic-Scala-StaticResources/master/src/main/resources/" + s"stocks/daily/daily_$symbol.csv" val data = Source.fromURL(url).mkString val latestClosePrize = data .split("\n") .slice(1, 2) .map(record => { val Array(timestamp, open, high, low, close, volume) = record.split(",") val Array(year, month, date) = timestamp.split("-") Record(year.toInt, month.toInt, date.toInt, BigDecimal(close.trim)) }) .map(_.closePrice) .head latestClosePrize } def getTickersAndUnits: Map[String, Int] = { val classLoader = this.getClass.getClassLoader val stocksXMLInputStream = classLoader.getResourceAsStream("stocks.xml") //或者来自于文件 val stocksAndUnitsXML = scala.xml.XML.load(stocksXMLInputStream) (Map[String, Int]() /: (stocksAndUnitsXML \ "symbol")) { (map, symbolNode) => val ticker = (symbolNode \ "@ticker").toString val units = (symbolNode \ "units").text.toInt map + (ticker -> units) } } }

UsingScala/FindTotalWorthSequential.scala

object FindTotalWorthSequential extends App {

  val symbolsAndUnits = StockPriceFinder.getTickersAndUnits

  println("Ticker  Units  Closing Price($) Total Value($)")

  val startTime = System.nanoTime()
  val valuesAndWorth = symbolsAndUnits.keys.map { symbol =>
    val units = symbolsAndUnits(symbol)
    val latestClosingPrice = StockPriceFinder.getLatestClosingPrice(symbol)
    val value = units * latestClosingPrice

    (symbol, units, latestClosingPrice, value)
  }

  val netWorth = (BigDecimal(0.0d) /: valuesAndWorth) { (worth, valueAndWorth) =>
    val (_, _, _, value) = valueAndWorth
    worth + value
  }
  val endTime = System.nanoTime()

  valuesAndWorth.toList.sortBy(_._1).foreach { valueAndWorth =>
    val (symbol, units, latestClosingPrice, value) = valueAndWorth
    println(f"$symbol%7s  $units%5d  $latestClosingPrice%15.2f  $value%.2f")
  }

  println(f"The total value of your investments is $$$netWorth%.2f")
  println(f"Took ${(endTime - startTime) / 1000000000.0}%.2f  seconds")
}

运行结果

Ticker  Units  Closing Price($) Total Value($)
   AAPL    200           125.90  25180.00
   ADBE    125            77.36  9670.00
    ALU    150             3.84  576.00
    AMD    150             2.80  420.00
   CSCO    250            28.44  7110.00
    HPQ    225            33.28  7488.00
    IBM    215           162.88  35019.20
   INTC    160            31.31  5009.60
   MSFT    190            42.88  8147.20
    NSM    200            29.94  5988.00
   ORCL    200            44.41  8882.00
   SYMC    230            24.38  5607.40
    TXN    190            59.28  11263.20
   VRSN    200            64.75  12950.00
    XRX    240            13.18  3163.20
The total value of your investments is $146473.80
Took 11.13  seconds

15.6 编写并发的资产净值应用程序

UsingScala/FindTotalWorthConcurrent.scala

import scala.collection.parallel.CollectionConverters._
val valuesAndWorth = symbolsAndUnits.keys.par.map { symbol =>

运行结果

Ticker  Units  Closing Price($) Total Value($)
   AAPL    200           125.90  25180.00
   ADBE    125            77.36  9670.00
    ALU    150             3.84  576.00
    AMD    150             2.80  420.00
   CSCO    250            28.44  7110.00
    HPQ    225            33.28  7488.00
    IBM    215           162.88  35019.20
   INTC    160            31.31  5009.60
   MSFT    190            42.88  8147.20
    NSM    200            29.94  5988.00
   ORCL    200            44.41  8882.00
   SYMC    230            24.38  5607.40
    TXN    190            59.28  11263.20
   VRSN    200            64.75  12950.00
    XRX    240            13.18  3163.20
The total value of your investments is $146473.80
Took 1.98  seconds