scala入门
1、使用scala解释器
在命令提示符窗口输入scala,即可进入scala的命令行模式。输入:
1+2
// 输出3
2、定义变量
Scala的变量分为两种:val和var。
val跟Java的final变量类似,一旦初始化就不能被重新复制。但是变量指向的那个对象是有可能发生改变的。(又得看那个对象是否可变的)
而var则不同,类似Java的非final变量,在整个生命周期内var可以被重新赋值。
val msg = "hello, world"
var i = 0
上面两句时类型推断后的结果。
完整的结构:
val msg:String = "hello, world"
var i:Int = 0
4、定义函数
函数定义由def开始,然后是函数名和圆括号中以逗号隔开的参数列表。
每个参数的后面都必须加上以冒号(:)开始的类型标注,因为scala编译器并不会推断函数参数的类型。
在max的参数列表的右括号之后,": Int"定义的是max函数自己的结果类型(result type)。
函数体是等号和用花括号括起来的。
函数体之前的等号也有特别的含义,表示在函数式的世界观里,函数定义的是一个可以获取到结果值的表达式。
scala的if表达式可以返回一个结果,就像Java的三元表达式(ternary operator)。
def max(x: Int, y: Int): Int = {
if(x > y) x else y
}
ps:如果函数是递归的(recursive),就必须显式 的给出函数的结果类型。
5、编写scala脚本
虽然scala被设计为帮助程序员构建大型的软件系统,但同时也适用于脚本编写。
echo 'println("Hello, world, from a script")' >> hello.scala
然后执行:
scala hello.scala
命令行参数可以通过名为args的scala数组获取。scala数组的第一个元素式args(0),而不是Java那样的写法args[0]。
5、用while做循环;用if做判断
将下面的代码录入echoargs.scala文件
var i = 0
while(i < args.length){
if(i != 0)
print(" ")
print(args(i))
i += 1
}
println()
执行命令:
scala echoargs.scala Scala is even more fun
// 输出:Scala is even more fun
6、用foreach和for遍历
函数式编程语言的主要特征之一就是函数是一等的语法单元。
比如,打印每一个命令行参数的另一种方式:
args.foreach(arg => println(arg))
// 根据函数至简原则,也可以写成这样:
args.foreach(println)
ps:函数字面量的语法:用圆括号括起来的一组带名字的参数、一个右箭头和函数体。
对比指令式编程语言中的for循环,scala只提供for表达式。比如
for (arg <- args){
println(arg)
}
// <-符号的左边式"arg",这是一个val变量的名字,而不是var
// 实际情况是:对于args数组中的每一个元素,一个新的名为arg的val会被创建出来,初始化成元素的值,这时for表达式的循环体才被执行。
7、用类型参数化数组
参数化的意思是在创建实例时对实例做“配置”,可以用一个值来参数化一个实例,做法是在构造方法的括号中传入对象参数。例如,实例化一个新的java.math.BigInteger,并用值"123456"对他进行参数化:
val big = new java.math.BigInteger("12345")
初始化一个数组
val arr = new Array[String](3+1)
scala通行规则:
如果一个方法只接收一个参数,在调用它的时候,可以不用英文据点或圆括号。
如(1).+(2)可以简写为 1 + 2。
ps:这种方式仅在显式的给出方法调用的目标对象时才有效。不能"println 10"。
scala从技术上讲并没有操作符重载(operator overloading),因为它实际上并没有传统意义上的操作。类似+、-、*、/这样的字符可以被用作方法名。
scala为什么用括号(而不是方括号)访问数组
数组不过是类的实例,这一点跟其它scala实例没有区别。
当你用一组圆括号将一个或多个值包起来,并将其应用(apply)到某个对象时,scala会将这段代码转换成对这个对象的名为apply的方法调用。
同理,当我们尝试对通过圆括号应用了一个或多个参数的变量进行赋值时,编译器会将代码转换成对update方法的调用,这个update方法接收两个参数:圆括号括起来的值,以及等号右边的对象。
ps:当然,这样的代码仅在对象的类型实际上定义了apply、update等方法时才能通过编译。并且scala在编译代码时,会尽可能使用Java数组、基本类型和原生的算数指令。
apply工厂方法
val names = Array("zero","one","two")
实际调用了名为apply的工厂方法,这个方法创建并返回了新的数组。该方法定义在Array的伴生对象中。
8、使用列表
函数式编程的重要理念之一就是方法不能有副作用。一个方法唯一要做的就是计算并返回一个值。
一个好处是方法不在互相纠缠在一起,因此变得更可靠、更易复用。
另一个好处(作为静态类型的编程语言)是类型检查器会检查方法的入参和出参,因此逻辑通常都是以类型错误的形式出现。
将这个函数式的哲学应用到对象的世界意味着让对象不可变。
列表
scala数组是一个拥有相同类型的对象的可变序列。对于需要拥有相同类型的对象的不可变序列的场景,可以使用scala的List类。
利用List类的apply工厂方法,获得一个List对象。
val ontTwoThree = List(1,2,3)
由于List是不可变的,它们的行为优点类似Java的字符串:当你调用列表的某个方法,而这个方法的名字看上去像是会改变列表时,它实际是创建并返回一个带有新值的新的列表。
比如,“::”方法,读作“cons”,他在一个已有列表的前面添加一个新的元素,并返回这个新的列表。
val oneTwo = List(1,2)
val zeroOneTwo = 0 :: oneTwo
如果方法名的最后一个字符是冒号,该方法的调用会发生在它的有操作元上。
表示空列表的快捷方式是Nil,初始化一个新列表的另一种方式是用::将元素串接起来,并将Nil作为最后一个元素。
9、使用元组
元组也是不可变的,可以容纳不同类型的元素。当你需要从方法返回多个对象时,元组非常有用。
在Java中遇到类似情况通常会创建一个类似JavaBean那样的类来承载多个返回值,而用scala可以简单的返回一个元组。
使用:要实例化一个新的元组,只需要将对象放在圆括号当中,用逗号隔开即可。实例化完成后,可以用英文句点、下画线和从1开始的序号来访问每一个元素。
val pair = (99,"Luftballons")
println(pair._1)
println(pair._2)
scala会自动推断出这个元组的类型时Tuple2[Int, String],并将之作为变量pair的类型。
"."跟用于访问字段调用方法时使用的方式相同。
本例中,访问的是一个名为_1的字段。
访问元组中的元素
apply方法永远只返回同一种类型,但元组里的元素可以是不同类型的。这些_N表示的字段名从1开始而不是从0开始,是由其它通用支持静态类型元组的语言设定的传统,比如Haskell和ML
ps:在一组大小相同位置连续的空间里面,每个元素都可以表示为编号*大小。比如
val arr = Array(1,2,3,4)
// 假设arr数组头节点的开始的内存编号为base+0*4,即元素1的位置
// 元素2的位置为base+1*4
// 元素3的位置为base+2*4
// 元素4的位置为base+3*4
// 基于这种快速索引的思想,大多数数组下标都是从0开始的。
// 元组下标从1开始,出了历史原因之外,也说明它的数据结构不是上面描述的那样。
最多定义22个元素的元组。
10、使用集和映射
由于scala兼有函数式和指令式的编程风格的优势,其集合类库特意对可变和不可变的集合进行了区分。比如数组永远是可变的,列表永远是不可变的。同时还提供了集(set)和映射(map)的可变和不可变的不同选择,但使用同样的简单名字。对于集和映射而言,scala通过不同的类继承关系来区分可变和不可变版本。
Set
通过调用Set伴生对象的名为apply的工厂方法创建集。实际调用了scala.collection.immutable.Set的伴生对象的apply方法,返回一个默认的、不可变的Set的对象。
var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
// jetSet指向一个包含"Boeing"、"Aribus"、"Lear"的新集。
println(jetSet.contains("Cessna"))
如果想要一个可变集,需要做一次引入(import)。
import语句让你在代码中使用简单名字,比如Set,而不是更长的完整名。
比如需要一个不可变的HaseSet:
import scala.collection.immutable.HashSet
val hashSet = HashSet("tomatoes","chilies")
println(hashSet + "coriander")
ps:当集是不可变的,我们并不需要对hashSet重新赋值,所以它是val。与之对应的是,当集是可变
Map
import scala.collection.mutable
val treasureMap = mutable.Map[Int,String]()
// 工厂方法没有传参时,编译器无法推断出映射的类型。
treasureMap += (1 -> "Go to island.")
// scala允许任何对象调用->方法的机制,即隐式转换(impllict conversion)
treasureMap += (2 -> "Find big X on ground.")
treasureMap += (3 -> "Dig.")
println(treasureMap(2))
// apply根据key获取value值
不向工厂方法传入任何内容,创建一个空的映射。通过->和+=方法向映射添加键值对(key/value pair)。scala编译器会将二元的操作,如1 -> "Go to island."转成成标准的方法调用,集(1).->(“Go to island.”)。可以在scala的任何对象上调用这个->方法,它将返回包含键和值两个元素的元组。
ps:一般有是否可变选择的,默认都是不可变的。Array和List被单独拆开了。
11、识别函数式编程风格
val变量
一个显著标志时如果代码包含var变量的,它通常时指令式风格的;而如果代码完全没有var(也就死说代码只包含val),那么它可能时函数式的。
因此,一个向函数式风格转变的方向是尽可能不用var。
输出函数
带有副作用的标志性特征是结果类型为Unit。如果一个函数并不返回任何有意的值,也就是Unit这样结果类型所表达的意思,那么这个函数存在于世上的唯一意义就是产生某种副作用。
函数式编程的做法式顶一个将传入的args作为格式化(用于打印)的方法,但知识返回这个格式化的字符串。没有副作用的函数式代码
def formatArgs(args: Array[String]) = args.mkString("\n")
mkString方法可以被用于任何可被跌打访问的集合(包括数组、列表、集、映射),返回一个包含了对所有元素调用toString的结果的字符串,传入的字符串分割。
然后将它的结果传给println进行输出:
println(formatArgs(args))
// 返回值为Unit
每个有用的程序都会有某种形式的副作用。否则它对于外部世界就没有任何价值。倾向于无副作用的函数鼓励设计出带有副作用最小化的程序。这样做的好处之一式让你的程序更容易测试。比如
val res = formatArgs(Array("zero","one","two"))
assert(res == "zero\none\ntwo")
要测试formatArgs函数变得非常简单,只需要检查它的结果即可。
ps:scala程序员的平衡心态
倾向于使用val、不可变对象和没有副作用的方法,优先选择它们。不过当有特定的需要和理由时,也不要拒绝var、可变对象和带有副作用的方法。
12、从文件读取文本行
import scala.io.Source
// 引入scala.io包的名为Source的类。然后检查是不是命令行至少给出了一个参数。
if(args.length > 0){
for(line <- Source.fromFile(agrs(0)).getLines())
// 表达式Source.fromFile(args(0))尝试打开指定的文件并返回一个Source对象,在这个对象上,继续调用getLines方法。getLines方法返回一个Iterator[String],每次迭代都给出一行内容,去掉最后的换行符。
println(line.length + " " + line)
}
else
Console.err.println("Please enter filename")