scala 学习笔记(三)

scala进一步学习

1.带类型的参数化数组

scala可以使用new实例化对象或者实例。当你在scala里实例化对象,可以使用值和类型把他参数化。参数化意思就是创建实例时候“设置”它。通过把加在括号里的对象传递给实例的构造器的方式来使用值参数化实例。

如:scala里实例化一个新的java.math.BigInteger并使用值"123456"参数化

val big = new java.math.BigInteger("123456")
通过方括号里设定一个或更多类型来参数化实例。

val greetString = new Arrary[String](3)
greetString(0) = "hello"
greetString(1) = ", "
greetString(2) = "World!\n"
for (i <- 0 to 2)
   print(greetString(i))

Scala 里的数组是通过把索引放在圆括号里面访问的,而不是像 Java 那样放在方括号里。所以数组的第零个元素是 greetStrings(0),不是 greetStrings[0]。 

当你用 val 定义一个变量,那么这个变量就不能重新赋值,但它指向的对象却仍可以暗自改变。所以在本例中,你不能把 greetStrings 重新赋值成不同的数组;greetStrings 将永远指向那个它被初始化时候指向的同一个 Array[String]实例。但是你一遍遍修改那个 Array[String]的元素,因此数组本身是可变的。 

本例中的to实际上是带一个Int参数的方法。代码0 to 2被转换成方法调用(0).to(2)1请注意这个语法仅在你显示指定方法调用的接受者时才起作用。不可以写 pringln 10,但是可以写成“Console println 10”。 

当你在一个或多个值或变量外使用括号时,Scala 会把它转换成对名为 apply 的方法调用。 于是 greetStrings(i)转换成 greetStrings.apply(i)。 

任何对某些在括号中的参数的对象的应用将都被转换为对 apply 方法的调用。当然前提是这个类型实际定义过 apply 方法。所以这不是一个特例,而是一个通则。 

,当对带有括号幷包括一到若干参数的变量赋值时,编译器将把它转化为对带有括号里参数和等号右边的对象的 update 方法的调用。 

greetString(0) = "Hello"
转化为

greetStrings.update(0, "Hello")
我们可以把二中的code写成

val greetString = new Array[String](3)
greetStrings.update(0"Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!\n")
for (i <- 0.to(2))
   print(greetStrings.apply(i))
接下来,Scala 提供了通常可以用在你真实代码里的更简洁的方法创造和初始化数组 ,这行代码创建了长度为 3 的新数组,用传入的字串"zero","one""two"初始化。编译器推断数组的类型是Array[String] ,因为你把字串传给它。 

val numNames = Array("zero","b","c")

这里实际做的就是调用了一个叫做apply的工厂方法,从而创造并返回了新的数组。 

2.使用list

scala创建一个list

把这个函数式编程的哲学应用到对象世界里意味着使对象不可变。 

list是不可变。

val oneTwoThree = List(1,2,3)

List有个叫“:::”的方法实现叠加功能。 

val oneTwo = List(1,2)
val threeFour = List(3,4)
val OneTwoThreeFour = oneTwo ::: threeFour
println(oneTwo + " and" + threeFour + " is a new List.")
println("Thus, " + oneTwoTherrFour + " is a new List.")
执行结果

List(1,2)and List(3,4) were not mutated.
Thus, List(1,2,3,4) is a new List

或许 List 最常用的操作符是发音为“cons”的‘::Cons 把一个新元素组合到已有 List的最前端,然后返回结果 List。例如,若执行这个脚本: 

val twoThree = list(2,3)
val oneTwoThree = 1:: twoThree
println(oneTwoThree)
结果

List(1,2,3)

为什么列表不支持 append

List 没有提供 append 操作,因为随着列表变长 append 的耗时将呈线性增长,而使用::做前缀则仅花费常量时间。如果你想通过添加元素来构造列表,你的选择是把它们前缀进去,当你完成之后再调用 reverse;或使用 ListBuffer,一种提供 append 操作的可变列表,当你完成之后调用 toList。 

方法名 方法作用

List() Nil

List 

List("Cool", "tools", "rule)

创建带有三个值"Cool","tools""rule"的新List[String] 

val thrill = "Will"::"fill"::"until"::Nil 

创建带有三个值"Will","fill""until"的新 List[String] 

List("a", "b") ::: List("c", "d") 

叠加两个列表(返回带"a","b","c""d"的新List[String]

thrill(2)

返回在 thrill 列表上索引为 2(基于 0)的元素(返回"until"

thrill.count(s => s.length == 4) 

计算长度为4String元素个数(返回2)

thrill.drop(2)

返回去掉前 2 个元素的 thrill 列表(返回 List("until")

thrill.dropRight(2)

返回去掉后 2 个元素的 thrill 列表(返回 List("Will")

thrill.exists(s => s == "until") 

判断是否有值为"until"的字串元素在thrill里(返回true

thrill.filter(s => s.length == 4) 

依次返回所有长度为 4 的元素组成的列表(返回 List("Will","fill")

thrill.forall(s => s.endsWith("1")) 

辨别是否thrill列表里所有元素都以"l"结尾(返回true

thrill.foreach(s => print(s)) 

thrill列表每个字串执行print语句("Willfilluntil"

thrill.foreach(print)

与前相同,不过更简洁(同上)

thrill.head 

返回 thrill 列表的第一个元素(返回"Will")

thrill.init 

返回 thrill 列表除最后一个以外其他元素组成的列表(返回List("Will", "fill"))

thrill.isEmpty 

说明 thrill 列表是否为空(返回 false)

thrill.last 

返回 thrill 列表的最后一个元素(返回"until")

thrill.length 

返回 thrill 列表的元素数量(返回 3

thrill.map(s => s + "y") 

返回由thrill列表里每一个String元素都加了"y"构成的列表(返回List("Willy", "filly", "untily")

thrill.mkString(", ") 

用列表的元素创建字串(返回"will, fill, until"

thrill.remove(s => s.length == 4) 

返回去除了thrill列表中长度为4的元素后依次排列的元素列表(返回 List("until")

thrill.reverse 

返回含有 thrill 列表的逆序元素的列表(返回 List("until","fill", "Will"))

thrill.sort((s, t) =>s.charAt(0).toLowerCase <t.charAt(0).toLowerCase) 

返回包括 thrill 列表所有元素,并且第一个字符小写按照字母顺序排列的列表(返回List("fill", "until", "Will")

thrill.tail 

返回除掉第一个元素的 thrill 列表(返回 List("fill","until")

3.Tuple的应用

与列表一样,元组也是不可变的,但与列表不同,元组可以包含不同类型的元素。而列表应该是 List[Int]List[String]的样子,元组可以同时拥有 Int String。元组很有用,比方说,如果你需要在方法里返回多个对象。 

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

4.使用set和map

因为 Scala 致力于帮助你充分利用函数式和指令式风格两方面的好处,它的集合类型库于是就区分了集合类的可变和不可变。 

例如,数组始终是可变的,而列表始终不可变。 

Scala 于是提供了两个子特质,一个是可变的集,另一个是不可变的集。 

1)例:初始化,和使用不可变集 

var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
println(jetSet.contains("Cessna"))
<div class="page" title="Page 53"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'SimSun'">你用一个包含了</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Boeing"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">,</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Airbus"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">和</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Lear"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">的新集重新赋值了 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">jetSet </span><span style="font-size: 10.000000pt; font-family: 'SimSun'">这个 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">var</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。最终,上面代码</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">的最后一行打印输出了集是否包含字串</span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">"Cessna"</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。(正如你所料到的,输出 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">false</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。) </span></p></div></div></div>

2)如果你需要不可变集,就需要使用一个引用 

import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"
println(movieSet)

它伴生对象的工厂方法即可。例如,如果你需要一个不可变的HashSet,你可以这么做: 

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")


Map Scala 里另一种有用的集合类。和集一样,Scala 采用了类继承机制提供了可变的和不可变的两种版本的 Map。 

1)如果你需要一个不可变的HashSet

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")
PS:scala.collection 包里面有一个基础 Map 特质和两个子特质 Map:可变的 Map scala.collection.mutable 里,不可变的在 scala.collection.immutable 里。 
2)
创造,初始化,和使用可变映射 
import scala.collection.mutable.Map
val treasureMap = Map[Int, String]()
treasureMap += (1 -> "Go to island")
treasureMap += (2 -> "Find big X on ground")
treasureMap += (3 -> "Dig")
println(treasureMap(2))

下面的三行里你使用->+=方法把键/值对添加到Map里。像前面例子里演示的那样,Scala编译器把如 1 -> "Go toisland"这样的二元操作符表达式转换为(1).->("Go to island.")。因此,当你输入 1 ->"Go to island.",你实际上是在值为 1 Int上调用->方法,并传入值为"Go to island."String。这个->方法可以调用Scala程序里的任何对象,并返回一个包含键和值的二元元组。然后你在把这个元组传递给treasureMap指向的Map+=方法。最终,最后一行输出打印了treasureMap中的与键 2 有关的值。 

3)如果你更喜欢不可变映射,就不用引用任何类了,因为不可变映射是缺省的,以下代码展示了这个例子: 

val romanNumeral = Map(
  1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
println(romanNumeral(4))

由于没有引用,当你在代码的第一行里提及 Map 时,你会得到缺省的映射:scala.collection.immutable.Map。传给工厂方法入五个键/值元组,返回包含这些传入的键/值对的不可变 Map。如果你执行代码中的代码,将会打印输出 IV。


5.学习识别函数式风格

Scala 允许一种指令式风格编程,一种更函数式的风格。 

如果代码包含了任何 var 变量,那它大概就是指令式的风格。如果代码根本就没有var——就是说仅仅包含 val——那它大概是函数式的风格。因此向函数式风格推进的一个方式,就是尝试不用任何 var 编程。 

如下例子使用了var指令式风格:
def printArgs(args: Array[String]): Unit = {
  var i = 0
  while (i < args.length) {
    println(args(i))
    i += 1 }
}
通过去掉var变成函数式风格:
def printArgs(args: Array[String]): Unit = {
  for (arg <- args)
    println(arg)
}
或者
def printArgs(args: Array[String]): Unit = {
  args.foreach(println)
}
<div class="page" title="Page 56"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'SimSun'">重构后的 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">printArgs </span><span style="font-size: 10.000000pt; font-family: 'SimSun'">方法并不是</span><span style="font-size: 10.000000pt; font-family: 'FangSong_GB2312'">纯</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">函数式的,因为它有副作用</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">——</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">本例中,其副作用是打印到标准输出流。函数有副作用的马脚就是结果类型为 </span><span style="font-size: 9.000000pt; font-family: 'LuxiMono'">Unit</span><span style="font-size: 10.000000pt; font-family: 'SimSun'">。 </span></p></div></div></div>

为函数式风格可以帮助你写出更易读懂,更不容易犯错的代码。 

更函数式的方式应该是定义对需打印的 arg 进行格式化的方法,但是仅返回格式化之后的字串 

def formatArgs(args: Array[String]) = args.mkString("\n")

现在才是真正函数式风格的了:满眼看不到副作用或者 var。 

args 包含了三个元素,"zero","one""two",formatArgs 将返回"zero\none\ntwo"。 

当然,这个函数并不像 printArgs 方法那样实际打印输出,但可以简单地把它的结果传递给 println 来实现:

println(formatArgs(args))

你可以通过检查结果来测试 formatArgs

val res = formatArgs(Array("zero", "one", "two"))
assert(res == "zero\none\ntwo")

Scala assert 方法检查传入的 Boolean 并且如果是假,抛出 AssertionError。如果传入的 Boolean 是真,assert 只是静静地返回。 

PS:虽如此说,不过请牢记在心:不管是 var 还是副作用都不是天生邪恶的。Scala 不是强迫你用函数式风格编任何东西的纯函数式语言。它是一种指令式/函数式混合的语言。你或许发现在某些情况下指令式风格更符合你手中的问题,在这时候你不应该对使用它犹豫不决。 

6.读取文件信息

建立一个从文件中读行记录,并把行中字符个数前置到每一行,打印输出的脚本 

import scala.io.Source

if (args.length > 0) {
   for (line <- Source.fromFile(args(0)).getLines)
   println(line.length + " " + line)
} 
else Console.err.println("Please enter filename")

调用运行:
$ scala countchars1.scala countchars1.scala

以上代码改写:
<div class="page" title="Page 59"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'SimHei'">对文件的每行记录打印格式化的字符数量 </span></p><p><span style="font-size: 10.000000pt; font-family: 'SimHei'"></span><pre name="code" class="java">import scala.io.Source
def widthOfLength(s: String) = s.length.toString.length
if (args.length > 0) {
  val lines = Source.fromFile(args(0)).getLines.toList
  val longestLine = lines.reduceLeft(
    (a, b) => if (a.length > b.length) a else b
  )
  val maxWidth = widthOfLength(longestLine)
  for (line <- lines) {
    val numSpaces = maxWidth widthOfLength(line)
    val padding = " " * numSpaces
    print(padding + line.length +" | "+ line)
} }
else
Console.err.println("Please enter filename")


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章