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

运行结果

Please enter a ticker symbol:OK, got it, you own AAPL
Full source at GitHub

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

UsingScala/ReadingFile.scala

import scala.io.Source

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

运行结果

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

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

运行结果

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

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

运行结果

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

UsingScala/UseXML.scala

var symbolNodes = xmlFragment \ "symbol"
symbolNodes foreach println
println(symbolNodes.getClass)
Full source at GitHub

运行结果

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

UsingScala/UseXML.scala

var unitsNodes = xmlFragment \\ "units"
unitsNodes foreach println
println(unitsNodes.getClass)
println(unitsNodes.head.text)
Full source at GitHub

运行结果

<units>200</units>
<units>215</units>
class scala.xml.NodeSeq$$anon$1
200
Full source at GitHub

UsingScala/UseXML.scala

unitsNodes.head match {
  case <units>{ numberOfUnits }</units> ⇒ println(s"Units: $numberOfUnits")
}
Full source at GitHub

运行结果

Units: 200
Full source at GitHub

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

运行结果

Ticker	Units
AAPL    200
IBM     215
Full source at GitHub

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

UsingScala/ReadWriteXML.scala

import scala.xml._

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

运行结果

class scala.xml.Elem
File has 15 symbol elements
Full source at GitHub

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

运行结果

Number of symbol elements found is 15
Full source at GitHub

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

运行结果

Saved file has 15 symbol elements
Full source at GitHub

示例文件

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

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

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

运行结果

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

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

UsingScala/FindTotalWorthConcurrent.scala

val valuesAndWorth = symbolsAndUnits.keys.par.map { symbol ⇒
Full source at GitHub

运行结果

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