Scala学习笔记一
第六步:for和foreach
java中循环数组输出:
for(i=0;i<str.length,;i++){
println(str[i]);
}
scala中循环数组输出:
for(i<-str){
println(str(i))
}
注意:scala的for中的i是val,虽然每次都被赋予了新值,但是每次循环时都是new一个新的对象,只是每次new的对象的名字都是i罢了。下面的7.3节和二十三章将再讲到for
又有如下代码:
//这里用的是to
for(i<- 0 to 2)
print(str(i))
//这里用的是until
for(i<- 0 until 3)
print(str(i))
上面的代码中,to表示包含其后面的数组,即“0 to 2”表示“0,1,2”,而until不包含后面的数组,即“0 until 3”表示“0,1,2”。
另外,上面的代码演示了scala的另一个通用规则:如果方法仅带一个参数,可以不带点或括号调用它。
如本例中的to实际上是带一个Int参数的方法,“0 to 2”实际上应该是”(0).to(2)”。
在scala中没有传统意义上的操作符,其中的“+,-,*,/”都可以用来做方法名。
因此,“1+2”可以理解为“(1).+(2)”。示意图如下所示:
当然,在scala中,for也可以被写成foreach,例如上面的代码可以写成这样:
str.foreach((i:String)=>println(i))
上述代码中我们调用了str的foreach方法,其中i是传入的参数,String为i的类型,“->”右边是方法体。这里的String可以省略不写,编译器会推测为str数组的类型。
如果我们要显示的指定参数类型,那么必须要用小括号括起来,如果不指定类型,且只有一个参数,那么可以不写括号。
如果foreach中执行的函数仅由带一个参数的一句话组成,那么可以这么写:
str.foreach(println)
总结:scala函数文本的语法如下所示:
第七步:Array
数组创建
//没有显示定义greeting的类型
val greeting = new Array[String](3)
//显示定义greeting的类型
val greeting:Array[String] = new Array[String](3)
//更简洁的形式,调用Array伴生对象的apply方法
val greeting = Array("hello",",","world")
数组初始化
greeting(0)="hello"
greeting(1)=","
greeting(2)="world"
scala中数组的访问用(),而不是java中的[]。因为在scala中,数组和其他类一样,都只是类的实现。因此当我们在一个或多个值或变量外使用小括号时,scala会把它转换成对名为“apply”方法的调用。即“greeting(0)”相当于“greeting.apply(0)”。
与之相似的还有当对带有括号并包括一到若干参数的变量赋值时,编译器将把它转换成update方法的调用。即greeting(0) = “Hello”将被转化为greeting.update(0,”Hello”)
对于“伴生对象”和“apply方法”将在4.3详细了解。
另外,从上面可以看出,在scala中,当一个变量被定义为val时,那么这个变量不能被重新赋值,即这个变量永远只能指向它初始化时指向的对象,但是这个被指向的对象本身是可以变化的,例如上例中greeting可以被赋值。
第八步:List
List的创建与初始化
//下面两个方法创建的list类型都为Int
//不用写成new List因为“List.apply()”是被定义在scala.List伴生对象上的工厂方法。
val list = List(1,2,3)
//或者用“::”和Nil来初始化
val list = 1::2::3::Nil
注意:scala中的list和java中的list是不同的,java中的list是可变的,即通过set()方法修改某个节点的值,但是scala中的list是不可变的,它是设计给函数式风格编程用的。
因为函数式编程的哲学应用到对象世界里意味着对象不可变。
数组:共享相同类型的可变对象序列;scala中的list:共享相同类型的不可变对象序列。
list中有两个特别的方法,或者说是操作符:“::”和“:::”。”::”将左边的元素组合到右边list的最前端
,然后返回一个新的list。”:::”将左右两个list叠加,返回一个新的list。
val list1 = List(1,2)
val list2 = List(1,2)
val list12 = list1:::list2
val list11 = list1:::list1
println(list12)//输出结果:List(1,2,1,2)
println(list11)//输出结果:List(1,2,1,2)
val list1 = List(1,2)
val list12 = 1::list1
println(list12)//输出结果:List(1,1,2)
注意:由于list是不可变的,可以类比于java中的String,因此对于它的叠加或元素追加操作,都是将结果赋值给一个新的list并返回。
如果一个方法被用作操作符标注,如a * b,那么方法被左操作数调用,就像a.*(b);除非方法名以冒号结尾,这种情况下,方法被右操作数调用。因此,1 :: list1里,::方法被list1调用,传入1,像这样:list1.::(1)。5.8节中将描述更多操作符关联性的细节。
另外,我们也发现,类List并没有提供append方法,因为随着列表长度增加,append耗时呈线性增长,使用“::”则仅花费常量时间。如果一定想通过添加元素来构造列表,可以把它们前缀进去,完成之后再调用reverse;或使用ListBuffer,一种提供append操作的可变列表,完成之后调用toList。ListBuffer将在22.2节中描述。
List常用的方法如下表所示:
方法吗名 | 方法作用 |
---|---|
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) | 计算长度为4的String元素个数(返回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”)) |
第九步:Tuple
元祖(tuple)是一种非常有用的容器,它是包含不同类型对象的不可变序列。
如果我们需要在方法里返回多个对象。Java里你将经常创建一个JavaBean样子的类去装多个返回值,Scala里你可以简单地返回一个元组。而且这么做的确简单:实例化一个装有一些对象的新元组,只要把这些对象放在括号里,并用逗号分隔即可。一旦你已经实例化了一个元组,你可以用点号,下划线和一个基于1的元素索引访问它。
tuple的创建
//此时pair的类型为Tuple[Int,String,String]
val pair = (1,"left","123")
tupel的使用
println(pair._1)//输出1
println(pair._2)//输出left
println(pair._3)//输出123
注意,元祖的数字是从1开始的,这与list从0开始计数不同。
尽管理论上你可以创建任意长度的元组,然而当前Scala库仅支持到Tupe22。
Array,List,Tuple的比较如下:
名称 | 是否可变 | 元素类型是否相同 |
---|---|---|
Array | 可变 | 必须相同 |
List | 不可变 | 必须相同 |
Tuple | 不可变 | 可以不同 |
第十步:Set和Map
Set
Scala中Set的类继承关系如下图所示:
在scala中,通过继承将set分为可变和不可变两个分支(通过trait实现),它们共享同样的简化名,但是全称不一样,分别放在两个不同的包下面。
set的创建与赋值
//调用Set伴生对象的名为apply的工厂方法
var jetSet = Set("One","Two")
jetSet += "Three"
println(jetSet)//输出结果:Set(One,Two,Three)
以上的代码定义了一个名为jetSet的var变量,Scala编译器推断jetSet的类型为不可变Set[String],于是返回了一个缺省的,不可变Set[String]的实例。
上面的var和Set的不可变需要区分。var表示可以让jetSet指向其他的不可变Set[String]类型的对象,但是由于不可变Set本身是不可变的,因此不能对jetSet指向的原对象进行内容的修改。“jetSet+=Three”操作后返回的实际上是另外一个新对象,并让jetSet指向这个新对象。
因此如果有下面的代码:
//调用Set伴生对象的名为apply的工厂方法
val jetSet = Set("One","Two")
jetSet += "Three"//这里会报错
println(jetSet)
那么编译时会出现错误,因为val规定了jetSet在创建了以后是不能再指向其他对象的,但是“jetSet += “Three”的执行结果是让jetSet指向新的Set对象,所以会报错。修改方法:将val改成var;或者在程序中引入可变的Set包“import scala.collection.mutable.Set”,如下所示:
//引入可变的Set包
import scala.collection.mutable.Set
//调用Set伴生对象的名为apply的工厂方法
val jetSet = Set("One","Two")
jetSet += "Three"
println(jetSet)//输出结果:Set(One,Two,Three)
再看如下代码:
var jetSet = Set("One","Two")
jetSet += "One"
jetSet += "Three"
println(jetSet)//输出结果:Set(One, Two, Three)
从上面看出,对于已经存在的元素,调用“+=”时会被忽略,不存在时才插入。
Map
Scala中Map的类继承关系如下图所示:
Map和Set非常相似,采用了继承的机制提供了可变和不可变两种版本。
Map的创建于赋值
import scala.collection.mutable.Map
//方法一,此时“[Int,String]”是必须的
val map = Map[Int,String]()
map += (1 -> "one")
map += (2 -> "two")
map += (3 -> "three")
println(map)//输出结果:Map(2 -> two, 1 -> one, 3 -> three)
//方法二,此时编译器通过传入的值可以判断map的类型
val map = Map(1->"one",2->"two",3->"three")
println(map)//输出结果:Map(2 -> two, 1 -> one, 3 -> three)
Scala编译器把如1 -> “one”这样的二元操作符表达式转换为(1).->(“one”)。因此,当你输入1 -> “one”,你实际上是在值为1的Int上调用->方法,并传入值为”one”的String。这个->方法可以调用Scala程序里的任何对象,并返回一个包含键和值的二元元组。然后你在把这个元组传递给map指向的Map的+=方法。最终,最后一行输出打印了map中的键值对。
再看下面代码:
val map = Map(1->"One",2->"Two",3->"Three")
map += (1 -> "zero")
map += (4 -> "Four")
println(map)//输出结果:Map(2 -> Two, 4 -> Four, 1 -> zero, 3 -> Three)
从上面的输出结果可以看出:调用“+=”方法时,对于已经存在的key(如上例中的“1”),会对value进行修改,若key不存在,则会插入。
注意:scala之所以区分了可变和不可变,是为了充分利用函数式和指令式风格两方面的好处。