Scala 有两种变量, val 和 var 其中val不可变,var可变
val msg: String = "Hello yet again, world!"
或者类型推断
val msg = "Hello, world!"
如果函数仅由一个句子组成,你可以可选地不写大括号。
def max2(x: Int, y: Int) = if (x > y) x else y
函数式编程风格
打印每一个命令行参数的方法是:
args.foreach(arg => println(arg)) 对于每一个变量,怎么怎么样
如果函数文本由带一个参数的一句话组成,
args.foreach(println)
Scala 里只有一个指令式 for的函数式近似。
for (arg <- args)
println(arg)
<- 的左边是变量,右边是数组。
再比如带类型的参数化数组
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
for (i <- 0 to 2)
print(greetStrings(i))
注意这里的数组定义,只要new的时候带类型Array[String]就行了,val后面自动推断类型。
注意这里的数组索引用的是()而不是java里面的[]。
因为scala里面根本没有传统意义上的操作符,取而代之的是他们都可以转换为方法。例如greetStrings(i)可以转换成 greetStrings.apply(i),
greetStrings(0) = "Hello" 将被转化为 greetStrings.update(0, "Hello")
尽管实例化之后你无法改变 Array 的长度,它的元素值却是可变的。因此,Array 是可变的对象。
Scala 的 List 是设计给函数式风格的编程用的。创建一个 List 很简单。 List里面元素不可变。
val oneTwoThree = List(1, 2, 3)
List有个叫“ :::”的方法实现叠加功能。
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour 结果是List(1, 2, 3, 4)
Cons 把一个新元素组合到已有 List的最前端,然后返回结果 List。 例如,若执行这个脚本:
val twoThree = list(2, 3)
val oneTwoThree = 1 :: twoThree
println(oneTwoThree)
你会看到:
List(1, 2, 3)
一个简单的需记住的规则:如果一个方法被用作操作符标注,如 a* b,那么方法被左操作数调用,就像 a.*(b)——除非方法名以冒号结尾。这种情况下,方法被右操作数调用。因此, 1 :: twoThree 里, ::方法被 twoThree 调用,传入 1,像这样: twoThree.::(1)。
类 List 没有提供 append 操作,因为随着列表变长 append 的耗时将呈线性增长,而使用::做前缀则仅花费常量时间。如果你想通过添加元素来构造列表,你的选择是把它们前缀进去,当你完成之后再调用 reverse;
与列表一样,元组也是不可变的,但与列表不同,元组可以包含不同类型的元素。
val pair = (99, "Luftballons", 55)
println(pair._1)
println(pair._2)
println(pair._3)
注意这里第一个元素是从_1开始而不像List那样从0开始。
使用 Set 和 Map
var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear" 默认set或者HashSet可变。即jetSet = jetSet + "Lear"
如果要用不可变的set或者HashSet,要import
import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander") 这里就不能再赋值给hashSet了
如果代码根本就没有var——就是说仅仅包含 val——那它大概是函数式的风格:
传统的指令式函数先定义var变量i再来for循环或者while循环
def printArgs(args: Array[String]): Unit = {
var i = 0
while (i < args.length) {
println(args(i))
i += 1
}
}
而函数式则像下面这样:
def printArgs(args: Array[String]): Unit = {
for (arg <- args)
println(arg)
\\或者 args.foreach(println)
}
Public 是 Scala 的缺省访问级别。C++中struct默认是public,class默认是private。java类中的变量默认是default类型,只允许在同一个包内访问,一般用的时候跟private差不多。所以java用的时候要想用public必须要指出。
class ChecksumAccumulator {
private var sum = 0
...
}
Scala 比 Java 更面向对象的一个方面是 Scala 没有静态成员。替代品是, Scala 有单例对象: singleton object。除了用 object 关键字替换了 class 关键字以外,单例对象的定义看上去就像是类定义。
import scala.collection.mutable.Map
object ChecksumAccumulator {
private val cache = Map[String, Int]()
def calculate(s: String): Int =
if (cache.contains(s))
cache(s)
else {
.....
}
可以如下方式调用 ChecksumAccumulator单例对象的calculate方法:
ChecksumAccumulator.calculate("Every value is an object.")
也不用在实例化了。
类和单例对象间的一个差别是,单例对象不带参数,而类可以。因为你不能用new关键字实例化一个单例对象, 你没机会传递给它参数。
当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象: companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类: companion class。类和它的伴生对象可以互相访问其私有成员。
不与伴生类共享名称的单例对象被称为孤立对象: standalone object。 由于很多种原因你会用到它
要执行Scala程序,你一定要提供一个有main方法(仅带一个参数, Array[String],且结果类型为 Unit的孤立单例对象名。比如下面这个例子;
import ChecksumAccumulator.calculate //
object Summer {
def main(args: Array[String]) { //对比java里面的public static void main(String[] args){}
for (arg <- args)
println(arg + ": " + calculate(arg))
}
}
Application 特质:在单例对象名后面写上“ extends Application” 。然后取代main 方法
import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends Application {
for (season <- List("fall", "winter", "spring"))
println(season +": "+ calculate(season))
}
效果和main函数一样。不过它也有些缺点。首先, 如果想访问命令行参数的话就不能用它,因为args数组不可访问。第二,如果你的程序是多线程的就需要显式的 main 方法。最后,某些JVM的实现没有优化被 Application 特质执行的对象的初始化代码。因此只有当你的程序相对简单和单线程情况下你才可以继承 Application 特质。
Scala 原始字串 """字符串""":与python中的r'字符串'功能类似。
富包装器:每个基本类型,都有一个“富包装器”可以提供许多额外的方法
0 max 5 5
0 min 5 0
-2.7 abs 2.7
4 to 6 Range(4, 5, 6)
"bob" capitalize "Bob"
"robert" drop 2 "bert"
Java 类具有可以带参数的构造器,而 Scala 类可以直接带参数。 Scala 的写法更简洁——类参数可以直接在类的主体中使用;没必要定义字段然后写赋值函数把构造器的参数复制到字段里。(不需要构造函数)
例如以下分数构造器:
class Rational(n: Int, d: Int) {
require(d != 0)
override def toString = n +"/"+ d
val numer: Int = n
val denom: Int = d
def add(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
}
require方法带一个布尔型参数。如果传入的值为真,require将正常返回。反之,require将通过抛出IllegalArgumentException来阻止对象被构造。
这里使用了重载。重载了类自带的toString函数。新建如下:
scala> val x = new Rational(1, 3)
x: Rational = 1/3
这时,不能引用x.n或者x.d
如果真的要这样做,可以另外在类里面定义两个变量进行保存。
val numer: Int = n
val denom: Int = d
这样就能使用x.numer或者x.denom
同理,在类里面就可以定义上面那个add函数的时候,不能像下面这样定义:
def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d)
这里that也就是随便起了个名字的变量,不是关键字。this是关键字。比如下面这个函数:
def lessThan(that: Rational) =
this.numer * that.denom < that.numer * this.denom
这里的this可以省略。但下面这个就不能省略了:
def max(that: Rational) =
if (this.lessThan(that)) that else this
从构造器:
def this(n: Int) = this(n, 1)
这样一来,val y = new Rational(3)就是val y = new Rational(3,1) 即3/1就是3
Scala 里的每一个从构造器的第一个动作都是调用同一个类里面其他的构造器。换句话说
就是,每个 Scala 类里的每个从构造器都是以“ this(...)”形式开头的。调用终将结束于对类的
主构造器的调用。因此主构造器是类的唯一入口点。
Scala 的 if 可以产生值(是能返回值的表达式)。于是 Scala 持续了这种趋势让 for, try 和 match 也产生值。while不产生值,所以用得少。如果实在想用while,在纯函数式编程的时候可以考虑递归。
指令式风格:
var filename = "default.txt"
if (!args.isEmpty)
filename = args(0)
函数式风格:
val filename =
if (!args.isEmpty) args(0)
else "default.txt"
使用 val 而不是 var 的第二点好处是他能更好地支持等效推论。无论何时都可以用表达式替代变量名。
如,要替代 println(filename),你可以这么写:println(if (!args.isEmpty) args(0) else "default.txt")
scala里面也没有break以及continue。如果想要用到他们的功能,可以使用增加布尔变量控制到循环语句判断中,类似:
var
foundIt = false
while
(i < args.length && !foundIt) { }
在for里面还支持过滤器:
val filesHere = (new java.io.File(".")).listFiles //路径名的目录中的文件的数组。
for (file <- filesHere if file.getName.endsWith(".scala"))
println(file)
甚至多个过滤器:
for (
file <- filesHere
if file.isFile;
if file.getName.endsWith(".scala")
) println(file)
for {子句} yield {循环体} 制造新集合
例如
def scalaFiles =
for {
file <- filesHere
if file.getName.endsWith(".scala")
} yield file
这样每一步就不是打印一个file,而是将file存储起来,最终产生一个Array[File]
try catch finally:
try {
val f = new FileReader("input.txt")
openFile(file)
// Use and close file
} catch {
case ex: FileNotFoundException => new FileReader("input.txt")
//注意这里依然有返回值。使用=>符号
case ex: IOException => // Handle other I/O error
}
finally {
file.close() // 确保关闭文件。
}
这里catch {
case ex: 。。。
case ex: 。。。
}
对比java,catch是这样用的
catch (Exception e) { 。。。。} 而且经常在catch里面throw。scala一般不使用throw。
还有,finally里最好只做一些关闭或打印之类的操作,不要有副作用的表达式,这样会有无谓的返值。
match语句就像java里的switch语句。
val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
case "salt" => println("pepper")
case "chips" => println("salsa")
case "eggs" => println("bacon")
case _ => println("huh?")
}
差别:
1、Java 的 case 语句里面的整数类型和枚举常量。而这里可以是任意类型,甚至正则匹配
2、在每个可选项的最后并没有 break。取而代之, break是隐含的。
3、match 表达式也能产生值:例如可以这样:
val friend =
firstArg match {
case "salt" => "pepper"
case "chips" => "salsa"
case _ => "huh?"
}
println(friend)
函数:函数式编程风格的一个重要设计原则:程序应该被解构成若干小的函数, 每个完成一个定义良好的任务。在java中通常这样做:
def processFile(filename: String, width: Int) {
...
for (line <- source.getLines)
processLine(filename, width, line)
}
private def processLine(filename:String, width:Int, line:String) {
....
}
Scala 提供了另一种方式:你可以把函数定义在另一个函数中。就好象本地变量那样,这种本地函数仅在包含它的代码块中可见。
def processFile(filename: String, width: Int) {
def processLine(filename:String, width:Int, line:String) {
...
}
val source = Source.fromFile(filename)
for (line <- source.getLines) {
processLine(filename, width, line)
}
}
Scala 拥有第一类函数: first-class function。你不仅可以定义函数和调用它们,还可以把函数写
成没有名字的文本: (跟python里面的lambda函数差不多)
scala> var increase = (x: Int) => x + 1
scala> increase(10)
res0: Int = 11
如果你想在函数文本中包括超过一个语句,用大括号包住函数体,当函数值被调用时,所有的语句将被执行,而函数的返回值就是最后一行产生的那个表达式。
scala> increase = (x: Int) => {
println("We")
println("are")
println("here!")
x + 1
}
再例如所有的集合类都能用到foreach方法,在foreach就能使用函数文本:
scala> val someNumbers = List(-11, -10, -5, 0, 5, 10)
scala> someNumbers.foreach((x: Int) => println(x))
再比如:集合类型还有 filter 方法
scala> someNumbers.filter((x: Int) => x > 0)
Scala 提供了许多方法去除冗余信息并把函数文本写得更简短。比如去除参数类型以及被推断的参数之外的括号:
scala> someNumbers.filter(x => x > 0)
如果想让函数文本更简洁,可以把下划线当做一个或更多参数的占位符,只要每个参数在函数文
本内仅出现一次。 :
scala> someNumbers.filter(_ > 0)
还可以使用一个下划线替换整个参数列表。叫偏应用函数
scala> def sum(a: Int, b: Int, c: Int) = a + b + c
一般调用可以这样:
scala> sum(1, 2, 3)
用偏函数取而代之:
scala> val a = sum _ //请记住要在函数名和下划线之间留一个空格
scala> a(1, 2, 3)
再比如
someNumbers.foreach(println _)
闭包:是指可以包含自由(未绑定到特定对象)变量的代码块
任何带有自由变量的函数文本,如(x: Int) => x + more,都是开放术语:由于函数值是关闭这个开放术语(x: Int) => x + more 的行动的最终产物, 得到的函数值将包含一个指向捕获的 more 变量的参考, 因此被称为闭包。
scala> var more = 1
scala> val addMore = (x: Int) => x + more
scala> addMore(10)
res19: Int = 11
每次函数被调用时都会创建一个新闭包。每个闭包都会访问闭包创建时活跃的 more 变量。闭包对捕获变量
作出的改变在闭包之外也可见。
想要标注一个重复参数,在参数的类型之后放一个星号。例如:
scala> def echo(args: String*) =
for (arg <- args) println(arg)
这样定义, echo 可以被零个至多个 String 参数调用:
scala> echo()
scala> echo("one")
scala> echo("hello", "world!")
但是如果有一个数组变量
scala> val arr = Array("What's", "up", "doc?"),则不能像scala> echo(arr)这样调用。
你需要在数组参数后添加一个冒号和一个_*符号,像这样:
scala> echo(arr: _*)
Scala 允许你创建新的“感觉像是原生语言支持”的控制抽象:scala提供curry 化:
Scala里的Curry化可以把函数从接收多个参数转换成多个参数列表。如果要用同样的一组实参多次调用一个函数,可以用curry化来减少噪音,让代码更有味道。我们要编写的方法不是接收一个参数列表,里面有多个参数,而是有多个参数列表,每个里面可以有一个或多个参数。也就是说,写的不是def foo(a: Int, b: Int, c: Int){},而是 def foo(a: Int)(b: Int)(c: Int){}。可以这样调用这个方法,比如:foo(1)(2)(3)、foo(1){2}{3},甚至这样foo{1}{2}{3}。
例如,传统函数如下:
scala> def plainOldSum(x: Int, y: Int) = x + y
scala> plainOldSum(1, 2) //res5: Int = 3
scala允许你使用curry化的新型函数:
scala> def curriedSum(x: Int)(y: Int) = x + y
scala> curriedSum(1)(2) //res5: Int = 3
结果一样。
高阶函数: higher-order function——带其它函数做参数的函数
def filesMatching(query: String,
matcher: (String, String) => Boolean) = {
for (file <- filesHere; if matcher(file.getName, query))
yield file
}
这里matcher其实是一个函数,这里做了filesMatching函数的参数。
(String, String) => Boolean)表示matcher函数的参数是(String, String)类型,而返回值是Boolean 类型
你可以通过让多个搜索方法调用它,并传入合适的函数:
def filesEnding(query: String) =
filesMatching(query, _.endsWith(_))
这就相当于
def filesEnding(query: String) =
for (file <- filesHere; if file.getName.endsWith(query))
yield file
因为上面matcher: (String, String)里面两个参数, matcher(file.getName, query)
所以 _.endsWith(_)里面的第一个_对应于字符串file.getName,第二个_对应于字符串query,
连在一起就是file.getName.endsWith(query)
类似的
def filesContaining(query: String) =
filesMatching(query, _.contains(_))
就相当于
def filesContaining(query: String) =
for (file <- filesHere; if file.getName.contains(query))
yield file
//这一段估计暂时用不到,用到的时候再看/
传名参数(叫名参数)
Scala中允许无参数的函数作为另一函数的参数传递进去,也就是传名参数(call-by-name)
定义一个函数 myAssert ,而其参数则为传名参数。在这里,我们想要实现的是断言,可以将传名参数写成函数文本的格式:(…) => Type ,即参数列表 => 类型。
def myAssert(check: () => Boolean) =
if(!check()){
println("OK ...")
throw new AssertionError
}
上面的函数定义了一个当客户代码传入的函数值(这里我们用()指明,代表省略了该函数的参数列表。调用方式如下:
scala> myAssert(() => 5 < 3)
客户端代码中的 () => 5 < 3 似乎有点繁琐,如果能够直接传入 5 < 3 之类的布尔表达式就更好了。这是可以实现的。只需要将函数定义 def 中空参数列表即小括号对()去掉,直接用 => 而不是()=> 就可以了。此外,if 判断中的 check 后面的()也要同时去掉。修改后的代码如下:
def myAssert(check: => Boolean) =
if(!check){
println("OK ...")
throw new AssertionError
}
myAssert(5 < 3)
//这一段估计暂时用不到,用到的时候再看/
case class
我们可以通过一个简单的例子来说明一些基本概念。我们设计一个函数库,这个函数库可以用来计算算术表达式,为简单起见,我们设计的算术表达式只侧重于变量,数字,单操作符,和双操作符。我们可以采用如下的Scala类定义:
abstract class Expr
case class Var(name:String) extends Expr
case class Number(num:Double) extends Expr
case class UnOp(operator:String, arg:Expr) extends Expr
case class BinOp(operator:String,left:Expr,right:Expr) extends Expr
这里我们定义了一个抽象类Expr和四个子类(分别代表变量,数值,单操作符,双操作符),Scala允许我们不定义类的实现,实际我们是class C 和 class C {}是等价的。
case classes
我们可以看到上面的四个子类定义前面我们使用了case关键字,使用了case关键字的类定义就是case classes。使用这个关键字,Scala编译器会自动为你定义的类生成一些成员。
首先,编译器为case class生成一个同名的对象构造器(Factory Method),也就是你可以使用 Var(“x”) 来创建一个类的实例,而无需使用new Var(“x”).
scala> val x = Var("x")
x: Var = Var(x)
这个构造器在嵌套使用时显得非常简洁明了,比如 我们构建如下的表达式,这种写法避免了很多new 的使用。
scala> val op=BinOp("+",Number(1),x)
op: BinOp = BinOp(+,Number(1.0),Var(x))
其次,Scala编译器为case class的构造函数的参数创建以参数名为名称的属性,比如Val的类的参数name:String 可以直接通过 .name访问,比如:
scala> x.name
res1: String = x
第三,编译器为case class 构造了更自然的toString,hashCode和equals实现,它们会递归打印,比较case class的参数属性。比如:
scala> println(op)
BinOp(+,Number(1.0),Var(x))
scala> op.right == Var("x")
res3: Boolean = true
其实感觉case class最重要的特性应该就是支持模式匹配。