Scala数组小结
1.定长数组
定长数组:指长度不可变的数组Array。
第一种方式:
先声明一个数组,后初始化该数组:
scala> val array = new Array[Double](5) array: Array[Double] = Array(0.0, 0.0, 0.0, 0.0, 0.0)
赋值方式:array(index) = value
第二种方式:
scala> val array = Array(1, 2, 3, 4, 5) array: Array[Int] = Array(1, 2, 3, 4, 5)
第三种方式,Array.fill(length)(value):
scala> val array = Array.fill(5)(3.5) array: Array[Double] = Array(3.5, 3.5, 3.5, 3.5, 3.5)
如果fill第二个参数只写一个值的话,那么该数组的所有元素都是该值,但是如果第二个参数是一个iterator或者random,那么数组就会被赋值为它们的值。
scala> val array = Array.fill(2)(math.random) array: Array[Double] = Array(0.2810736748034083, 0.7261142068882558)
第四种方式,ofDim[T](length):
scala> val array = Array.ofDim[Double](5) array: Array[Double] = Array(0.0, 0.0, 0.0, 0.0, 0.0)
赋值方式:array(index) = value
2.变长数组
变长数组:指数组长度可以变化。
声明一个空的数组,此时数组没有任何元素,且不能以下标索引赋值:
val array = new ArrayBuffer[T]()
在数组尾部增加一个类型为T的元素e1:
array += e1
在数组尾部增加一个类型为T的元素e1、e2:
array += (e1, e2)
在数组尾部增加一个类型为T的数组:
array ++= Array(e1, e2)
在第index元素后插入e1:
array.insert(index, e1)
在第index元素后插入e1、e2:
array.insert(index, e1, e2)
移除第index元素后的n个元素:
array.remove(index, n)
3多维数组
3.1定长多维数组
第一种方式:
val array = new Array[Array[Int]](5) scala> val array = new Array[Array[Int]](5) array: Array[Array[Int]] = Array(null, null, null, null, null)
此时,内部的每个数组长度是不一样的,类似于Java中的如下定义:
int[][] array = new int[2][];
第二种方式(推荐):
用ofDim[T](rows, column, height,...)函数定义,但最多可以定义五维数组,不过已经够用了。
scala> val array = Array.ofDim[Int](2,3) array: Array[Array[Int]] = Array(Array(0, 0, 0), Array(0, 0, 0))
val dimArray = Array.tabulate(3, 4)((a, b) => (a + 1, b + 2)) dimArray.foreach(println) for (i <- 0 until 3;j<-0 until 4) println(dimArray(i)(j))
3.2变长多维数组
scala> val arr1 = new ArrayBuffer[ArrayBuffer[Int]]() arr1: scala.collection.mutable.ArrayBuffer[scala.collection.mutable.ArrayBuffer[Int]] = ArrayBuffer()
4.定长数组和变长数组的转换
定长数组imarray转换成变长数组array:
array = imarray.toBuffer
变长数组array转换成定长数组imarray:
imarray = array.toArray
当定/变长多维数组相互转换时,只转换最外面那层数组:
scala> val array = Array(Array(1,2),Array(3,4)) arr: Array[Array[Int]] = Array(Array(1, 2), Array(3, 4)) scala> array.toBuffer res14: scala.collection.mutable.Buffer[Array[Int]] = ArrayBuffer(Array(1, 2), Array(3, 4))
scala for yield 的用法
Scala中的yield的主要作用是记住每次迭代中的有关值,并逐一存入到一个数组中。
用法如下:
for {子句} yield {变量或表达式}*/
case class User(name:String,age:Int,sex:String) object test{ def foo(n:Int,v:Int)={ for(i<-0 until n; j<- i until n if i+j==v) print(s"($i, $j)") } def main(args: Array[String]): Unit = { val users=List(new User("gmy",25,"male"),new User("ggh",22,"female"),new User("wss",25,"female"),new User("zan",23,"female"),new User("wy",23,"female")) val yeildUser=for(user<-users if user.sex.equals("male")||user.age<23) yield user.name //生成列表 yeildUser.foreach(user=>println(user)) //gmy ggh println(foo(10,10)) //(1, 9)(2, 8)(3, 7)(4, 6)(5, 5)() } }
1 case class User(name:String,age:Int,sex:String) 2 object YielldFor { 3 def foo(n:Int,v:Int)={ 4 for(i<-0 until n; 5 j<- i until n if i+j==v) 6 print(s"($i, $j)") 7 8 } 9 10 def foo1(n:Int,v:Int)={ 11 for(i<-0 until n; 12 j<- i until v ) 13 print(s"($i, $j)") 14 15 } 16 17 def foo2(n:Int,v:Int)={ 18 for(i<-0 to n; 19 j<- i to v ) 20 print(s"($i, $j)") 21 22 } 23 def main(args: Array[String]): Unit = { 24 val users=List(new User("gmy",25,"male"),new User("ggh",22,"female"),new User("wss",25,"female"),new User("zan",23,"female"),new User("wy",23,"female")) 25 val yeildUser=for(user<-users if user.sex.equals("male")||user.age<23) 26 yield user.name //生成列表 27 yeildUser.foreach(user=>println(user)) //gmy ggh 28 println(foo(10,10)) //(1, 9)(2, 8)(3, 7)(4, 6)(5, 5)() 29 println(foo1(2,5)) 30 println(foo2(10,5)) 31 } 32 33 }
scala中的apply方法与unapply方法
1.apply方法
当scala中类或者对象有一个主要用途的时候,apply方法就是一个很好地语法糖。
请看下面一个简单的例子:
class Foo(foo: String) {
}
object Foo {
def apply(foo: String) : Foo = {
new Foo(foo)
}
}
定义了一个Foo类,并且在这个类中,有一个伴生对象Foo,里面定义了apply方法。有了这个apply方法以后,我们在调用这个Foo类的时候,用函数的方式来调用:
object Client {
def main(args: Array[String]): Unit = {
val foo = Foo("Hello")
}
}
我们用Foo("Hello")
的方式,就得到了一个Foo类型的对象,这一切就是apply方法的功劳。如果没有apply方法,我们将需要使用new关键字来得到Foo对象。
2.apply方法用来做工厂
apply方法的最佳实践方式之一就是用来做工厂。比如在Scala的标准库中,许多集合类给我们提供了apply方法来创建集合:
object Client {
def main(args: Array[String]): Unit = {
val arr = new Array[Int](3)
arr(0) = 0
arr(1) = 1
arr(2) = 2
arr.foreach(x => print(x + " "))
println()
val array = Array(1,2,3)
array.foreach(x => print(x + " "))
}
}
上面两种方式我们都可以用来创建Array。第一种方式是使用new关键字,这是传统的面向对象的方式。那么第二种方式是什么情况呢?如果我们在IDE里点进去,可以发现IDE会提示我们有一个apply方法。点进去看apply方法的源码:
/** Creates an array of `Int` objects */
// Subject to a compiler optimization in Cleanup, see above.
def apply(x: Int, xs: Int*): Array[Int] = {
val array = new Array[Int](xs.length + 1)
array(0) = x
var i = 1
for (x <- xs.iterator) { array(i) = x; i += 1 }
array
}
3.unapply方法
从上面的例子不难看出,apply方法有点类似于java中的构造函数,接受构造参数变成一个对象。那么unapply方法就刚好相反,他是接受一个对象,从对象中提取出相应的值。
unapply方法主要用于模式匹配中。
看个简单的例子:
class Money(val value: Double, val country: String) {}
object Money {
def apply(value: Double, country: String) : Money = new Money(value, country)
def unapply(money: Money): Option[(Double, String)] = {
if(money == null) {
None
} else {
Some(money.value, money.country)
}
}
}
客户端实现:
def testUnapply() = {
val money = Money(10.1, "RMB")
money match {
case Money(num, "RMB") => println("RMB: " + num)
case _ => println("Not RMB!")
}
}
最后输出为:
RMB: 10.1
Scala中闭包
在Scala中,函数引入传入的参数是再正常不过的事情了,比如
(x: Int) => x > 0
中,唯一在函数体x > 0
中用到的变量是x,即这个函数的唯一参数。
除此之外,Scala还支持引用其他地方定义的变量:
(x: Int) => x + more
,这个函数将more
也作为入参,不过这个参数是哪里来的?从这个函数的角度来看,more是一个自由变量,因为函数字面量本身并没有给more赋予任何含义。相反,x是一个绑定变量,因为它在该函数的上下文里有明确的定义:它被定义为该函数的唯一参数。如果单独使用这个函数字面量,而没有在任何处于作用域内的地方定义more,编译器将报错:
scala> (x: Int) => x + more
<console>:12: error: not found: value more
(x: Int) => x + more
另一方面,只要能找到名为more的变量,同样的函数字面量就能正常工作:
scala> var more = 1
more: Int = 1
scala> val addMore = (x: Int) => x + more
addMore: Int => Int = $$Lambda$1104/583744857@33e4b9c4
scala> addMore(10)
res0: Int = 11
运行时从这个函数字面量创建出来的函数值(对象)被称为闭包。该名称源于“捕获”其自由变量从而“闭合”该函数字面量的动作。没有自由变量的函数字面量,比如(x: Int) => x + 1
,称为闭合语(这里的语指的是一段源代码)。因此,运行时从这个函数字面量创建出来的函数值严格来说并不是一个闭包,因为(x: Int) => x + 1
按照目前这个写法已经是闭合的了。而运行时从任何带有自由变量的函数字面量,比如(x: Int) => x + more
创建的函数,按照定义,要求捕获到它的自由变量more的绑定。相应的函数值结果(包含指向被捕获的more变量的引用)就被称为闭包,因为函数值是通过闭合这个开放语的动作产生的。
这个例子带来一个问题:如果more在闭包创建以后被改变会发生什么?在Scala中,答案是闭包能够看到这个改变,参考下面的例子:
scala> more = 9999
more: Int = 9999
scala> addMore(10)
res1: Int = 10009
很符合直觉的是,Scala的闭包捕获的是变量本身,而不是变量引用的值。正如前面示例所展示的,为(x: Int) => x + more
创建的闭包能够看到闭包外对more的修改。反过来也是成立的:闭包对捕获到的变量的修改也能在闭包外被看到。参考下面的例子:
scala> val someNumbers = List(-11, -10, -5, 0, 5, 10)
someNumbers: List[Int] = List(-11, -10, -5, 0, 5, 10)
scala> var sum = 0
sum: Int = 0
scala> someNumbers.foreach(sum += _)
scala> sum
res3: Int = -11
这个例子通过遍历的方式来对List中的数字求和。sum这个变量位于函数字面量sum += _
的外围作用域,这个函数将数字加给sum。虽然运行时是这个闭包对sum进行的修改,最终的结果-11仍然能被闭包外部看到。
那么,如果一个闭包访问了某个随着程序运行会产生多个副本的变量会如何呢?例如,如果一个闭包使用了某个函数的局部变量,而这个函数又被调用了多次,会怎么样?闭包每次访问到的是这个变量的哪一个实例呢?
答案是:闭包引用的实例是在闭包被创建时活跃的那一个。参考下面的函数,函数创建并返回more闭包的函数
def makeIncreaser(more: Int) = (x: Int) => x + more
该函数每调用一次,就会创建一个新的闭包。每个闭包都会访问那个在它创建时活跃的变量more
scala> val inc1 = makeIncreaser(1)
inc1: Int => Int = $$Lambda$1269/1504482477@1179731c
scala> val inc9999 = makeIncreaser(9999)
inc9999: Int => Int = $$Lambda$1269/1504482477@2dba6013
当调用makeIncreaser(1)
时,一个捕获了more的绑定值为1的闭包就被创建并返回。同理,当调用makeIncreaser(9999)
时,返回的是一个捕获了more的绑定值9999的闭包。当你将这些闭包应用到入参时,其返回结果取决于闭包创建时more的定义
scala> inc1(10)
res4: Int = 11
scala> inc9999(10)
res5: Int = 10009
这里,more是某次方法调用的入参,而方法已经返回了,不过这并没有影响。Scala编译器会重新组织和安排,让被捕获的参数在堆上继续存活。这样的安排都是由编译器自动完成的,使用者并不需要关心
Scala Collection(集合)
scala中:: , +:, :+, :::, +++的区别
package test /** * scala中的:: , +:, :+, :::, +++, 等操作; */ object listTest { def main(args: Array[String]): Unit = { val list = List(1,2,3) // :: 用于的是向队列的头部追加数据,产生新的列表, x::list,x就会添加到list的头部 println(4 :: list) //输出: List(4, 1, 2, 3) // .:: 这个是list的一个方法;作用和上面的一样,把元素添加到头部位置; list.::(x); println( list.:: (5)) //输出: List(5, 1, 2, 3) // :+ 用于在list尾部追加元素; list :+ x; println(list :+ 6) //输出: List(1, 2, 3, 6) // +: 用于在list的头部添加元素; val list2 = "A"+:"B"+:Nil //Nil Nil是一个空的List,定义为List[Nothing] println(list2) //输出: List(A, B) // ::: 用于连接两个List类型的集合 list ::: list2 println(list ::: list2) //输出: List(1, 2, 3, A, B) // ++ 用于连接两个集合,list ++ list2 println(list ++ list2) //输出: List(1, 2, 3, A, B) } }
-
::
该方法被称为cons,意为构造,向队列的头部追加数据,创造新的列表。用法为x::list
,其中x
为加入到头部的元素,无论x
是列表与否,它都只将成为新生成列表的第一个元素,也就是说新生成的列表长度为list的长度+1(btw,x::list
等价于list.::(x)
) -
:+
和+:
两者的区别在于:+
方法用于在尾部追加元素,+:
方法用于在头部追加元素,和::
很类似,但是::
可以用于pattern match ,而+:
则不行. 关于+:
和:+
,只要记住冒号永远靠近集合类型就OK了。 -
++
该方法用于连接两个集合,list1++list2
-
:::
该方法只能用于连接两个List类型的集合
具体示例
-
scala> "A"::"B"::Nil
-
res0: List[String] = List(A, B)
-
scala> "A"+:"B"+:Nil
-
res1: List[String] = List(A, B)
-
scala> Nil:+"A":+"B"
-
res2: List[String] = List(A, B)
-
scala> res0 ++ res1
-
res3: List[String] = List(A, B, A, B)
-
scala> res0 ::: res1
-
res4: List[String] = List(A, B, A, B)
-
scala> res0 :: res1
-
res5: List[java.io.Serializable] = List(List(A, B), A, B)
引用
scala字符串前加s使用$
https://my.oschina.net/u/2000675/blog/1592140
字符串中的变量替换,Scala中基础的字符串插值就是在字符串前加字幕‘s’,然后在字符串中放入变量,每个变量都应以‘$’开头。字符串前加字母‘s’时,其实是在创建一个处理字符串字面量
package demo
object Demo12 {
def main(args:Array[String])={
var name = "zhangsan"
var age = 15
println(s"name=$name,age=$age")
}
}
结果
name=zhangsan,age=15
在字符串字面量中使用表达式,“${}内可嵌入任何表达式”,包括等号表达式。
package demo
object Demo12 {
def main(args:Array[String])={
var name = "zhangsan"
var age = 15
println(s"name=$name,age=$age")
println(s"name=$name,age=${age+1}")
}
}
结果
name=zhangsan,age=16
printf格式化
package demo
object Demo12 {
def main(args:Array[String])={
var name = "zhangsan"
var age = 15
println(s"name=$name,age=$age")
println(s"name=$name,age=${age+1}")
var score = 89.5f
printf(f"name=$name,age=${age+1},score=$score%.2f")
}
}
结果
name=zhangsan,age=15
name=zhangsan,age=16
name=zhangsan,age=16,score=89.50
特殊符号的理解
// src/main/scala/progscala2/rounding/TryCatchArm.scala package progscala2.rounding import scala.language.reflectiveCalls import scala.util.control.NonFatal object manage { def apply[R <: { def close():Unit }, T](resource: => R)(f: R => T) = { var res: Option[R] = None try { res = Some(resource) // 只会引用"resource"一次!! f(res.get) } catch { case NonFatal(ex) => println(s"Non fatal exception! $ex") } finally { if (res != None) { println(s"Closing resource...") res.get.close } } } } object TryCatchARM { /** Usage: scala rounding.TryCatch filename1 filename2 ... */ def main(args: Array[String]) = { args foreach (arg => countLines(arg)) } import scala.io.Source def countLines(fileName: String) = { println() // 打印空白行,以增加可读性 manage(Source.fromFile(fileName)) { source => val size = source.getLines.size println(s"file $fileName has $size lines") if (size > 20) throw new RuntimeException("Big file!") } } }
manage.apply方法
manage.apply 方法声明看上去非常奇怪,为了能够理解该声明,我们将对其进行分解。下
面再次列出了该方法的签名,我们将分行显示方法签名,而每一行也都提供了对应的注释。
def apply[ R <: { def close():Unit }, ➊ T ] ➋ (resource: => R) ➌ (f: R => T) = {...} ➍
➊这行出现了两个新的事物。R 表示我们将要管理的资源类型。而<: 则意味着R 属于
某其他类型的子类。在本例中R 的父类型是一个包含close():Unit 方法的结构类型。
为了能帮助你更直观的理解R 类型,尤其是当你之前没接触过结构化类型时,你可
以认为R <: Closable 表示Closable 接口中定义了close():Unit 方法并且R 实现了
Closable 接口。不过结构化类型允许我们使用反射机制嵌入包含close():Unit 方法的
任意类型(如Source 类型)。反射机制会造成许多系统开销,而结构化类型代价也较为
昂贵, 因此就像后缀表达式那样,Scala 将反射列为可选特性,为了能够让编译器相信
我们知道我们在做什么,需要在代码中添加import 语句。
➋ 我们传入了用于处理资源的匿名函数,而T 表示该匿名函数的返回值。
➌ 尽管看上去像是一个声明体较为奇特的函数,但resource 事实上是一个传名参数(byname
parameter)。我们暂且将其视为一个在调用时应省略括号的函数。
➍ 最后我们将传入第二个参数列表,其中包含了一个输入为resource、返回值类型为T
的匿名函数,该匿名函数将负责处理resource。
我们再回到注释1,假设所有资源均实现了Closable 抽象,那么apply 方法声明看起来会
是下面这个样子:
object manage {
def apply[ R <: Closable, T](resource: => R)(f: R => T) = {...}
...
}
resource 只会在val res = Some(resource) 这行代码中被求值, 这行代码必不可少
的。由于resource 的表现与函数相似,因此就像是一个会被重复调用的函数,每次引
用该变量时便会对其求值。但我们并不希望每次引用resource 时都会执行一次Source.
fromFile(fileName),因为这意味着我们每次都会重新打开一个新的Source 实例。
之后,我们将res 值传入工作函数f 中。