一文学会Scala
整体介绍
- Scala 是一门多范式(multi-paradigm)的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性。
- 联邦理工学院洛桑(EPFL)的Martin Odersky于2001年基于Funnel的工作开始设计Scala。Java平台的Scala于2003年底/2004年初发布。.NET平台的Scala发布于2004年6月。该语言第二个版本,v2.0,发布于2006年3月。
- Scala 运行在Java虚拟机上,并兼容现有的Java程序。
- Scala 源代码被编译成Java字节码,所以它可以运行于JVM之上,并可以调用现有的Java类库。
Scala 特性
面向对象特性
Scala可以是一种面向对象的语言,每个值都可以是对象。
函数式编程
Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。
静态类型
Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性。
扩展性
Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构:
- 任何方法可用作前缀或后缀操作符
- 可以根据预期类型自动构造闭包
并发性
Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。Actor可以复用线程,因此在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现。
Scala 包
定义包
Scala 使用 package 关键字定义包,在Scala将代码定义到某个包中有两种方式:
第一种方法
和 Java 一样,在文件的头定义包名,这种方法就后续所有代码都放在该包中。 比如:
package com.runoob
class HelloWorld{
}
第二种方法
有些类似 C#,如:
package com.runoob {
class HelloWorld{
}
}
第二种方法,可以在一个文件中定义多个包。
引用包
Scala 使用 import 关键字引用包。
import java.awt.Color // 引入Color
import java.awt._ // 引入包内所有成员
def handler(evt: event.ActionEvent) { // java.awt.event.ActionEvent
... // 因为引入了java.awt,所以可以省去前面的部分
}
import语句可以出现在任何地方,而不是只能在文件顶部。import的效果从开始延伸到语句块的结束。这可以大幅减少名称冲突的可能性。
如果想要引入包中的几个成员,可以使用selector语法糖(选取器):
import java.awt.{Color, Font}
// 重命名成员
import java.util.{HashMap => JavaHashMap}
// 隐藏成员
import java.util.{HashMap => _, _} // 引入了util包的所有成员,但是HashMap被隐藏了
注意
:默认情况下,Scala 总会引入 java.lang._ 、 scala._ 和 Predef._,这里也就解释了,为什么以scala开头的包,在使用时都是省去scala.的。
数据类型
Scala的数据类型和Java差不多,以下是和Java不同之处.
数据类型 | 描述 |
---|---|
java... | 和java一样,8种基本类型... |
Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null | null 或空引用 |
Nothing | Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。 |
Any | Any是所有其他类的超类 |
AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 |
上表中列出的数据类型都是对象,也就是说scala没有java中的原生类型。在scala是可以对数字等基础类型调用方法的。
类型的层级关系
关于Scala的类型关系,可以看我的另一篇文章Scala的类层级讲解
符号字面量
符号字面量 'x 是表达式 scala.Symbol("x") 的简写;
多行字符串的表示方法
val foo = """菜鸟教程
www.runoob.com
www.w3cschool.cc
www.runnoob.com
以上三个地址都能访问"""
Scala 变量
变量是一种使用方便的占位符,用于引用计算机内存地址,变量创建后会占用一定的内存空间。
变量声明
在 Scala 中,使用关键词 "var" 声明变量,使用关键词 "val" 声明常量。
var myVar : String = "Foo"
val myVal : String = "Foo"
在 Scala 中声明变量和常量不一定要指明数据类型,在没有指明数据类型的情况下,其数据类型是通过变量或常量的初始值推断出来的。
++所以,如果在没有指明数据类型的情况下声明变量或常量必须要给出其初始值,否则将会报错。++
Scala 支持多个变量同时的声明赋值:
val xmax, ymax = 100 // xmax, ymax都声明为100
访问修饰符
分别有:private,protected,public(默认)。
私有(Private)
Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。
class Outer{
class Inner{
private def f(){
println("f")
}
class InnerMost{
f() // 正确
}
}
(new Inner).f() //错误
}
保护(Protected)
对保护(Protected)成员的访问比 java 更严格一些。
因为它只允许保护成员在定义了该成员的的类的子类中被访问,同包不可访问。
package p{
class Super{
protected def f() {
println("f")
}
}
class Sub extends Super{
f()
}
class Other{
(new Super).f() //错误
}
}
公共(Public)
如果没有指定任何的修饰符,则默认为 public。这样的成员在任何地方都可以被访问。
作用域保护
Scala中,访问修饰符可以通过使用限定词强调。格式为:
private[x]
或
protected[x]
这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作"这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对象可见外,对其它所有类都是private。
这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。
package bobsrocckets{
package navigation{
private[bobsrockets] class Navigator{
protected[navigation] def useStarChart(){}
class LegOfJourney{
private[Navigator] val distance = 100
}
private[this] var speed = 200
}
}
package launch{
import navigation._
object Vehicle{
private[launch] val guide = new Navigator
}
}
}
上述例子中,类Navigator被标记为private[bobsrockets]就是说这个类对包含在bobsrockets包里的所有的类和对象可见。
比如说,从Vehicle对象里对Navigator的访问是被允许的,因为对象Vehicle包含在包launch中,而launch包在bobsrockets中,相反,所有在包bobsrockets之外的代码都不能访问类Navigator。
Scala 运算符
算术运算符
和java一样+ , - , * , / , %
关系运算符
和java一样 == , != , > , < , >= , <=
逻辑运算符
和java一样&& , || , !
位运算符
和java一样& , | , ^ , ~ , << , >> , >>>
~ 按位取反
无符号右移
赋值运算符
和java一样
流程语句
if判断语句
和java一样,支持嵌套同时有返回值.
循环语句
和java完全一样,支持嵌套.
Scala 函数
Scala 有函数和方法,二者在语义上的区别很小。
Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。
我们可以在任何地方定义函数,甚至可以在函数内定义函数(内嵌函数)。更重要的一点是 Scala 函数名可以有以下特殊字符:+, ++, ~, &,-, -- , \, /, :
等。
函数声明
def functionName ([参数列表]) : [return type]
如果你不写等于号和方法主体,那么方法会被隐式声明为"抽象(abstract)",包含它的类也变成一个抽象类。
函数定义
def functionName ([参数列表]) : [return type] = {
function body
return [expr]
}
如下,一个求加法的函数:
object add{
def addInt( a:Int, b:Int ) : Int = {
var sum:Int = 0
sum = a + b
return sum
}
}
如果函数没有返回值,可以返回为 Unit,这个类似于 Java 的 void.
Scala的灵活性,使的函数定义的方法多种多样:
1、规规矩矩的写法,带有等号、大括号和返回值类型的形式
def myFunc(p1: Int) : Int = {
//something
}
def myFunc(p1: Int) : Unit = {
//something
}
2、非unit返回值的情形下,省略返回值,让程序根据代码块,自行判断。注意
,这里等号还是要的
def myFunc(p1: Int) = {
//something
}
3、unit返回值的情况下,直接省略返回值类型和等号
def myFunc(var p1 : Int) {
//something
// return unit
}
4、函数只有一行的情形下省略返回值和大括号
def max2(x: Int, y: Int) = if (x > y) x else y
def greet() = println("Hello, world!")
函数调用
Scala 提供了多种不同的函数调用方式:
以下是调用方法的标准格式:
functionName( 参数列表 )
如果函数使用了实例的对象来调用,我们可以使用类似java的格式 (使用 . 号):
[instance.]functionName( 参数列表 )
函数传名调用(call-by-name)
Scala的解释器在解析函数参数(function arguments)时有两种方式:
- 传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
- 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部;
在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。
这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。
object Test {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("获取时间,单位为纳秒")
System.nanoTime
}
def delayed( t: => Long ) = {
println("在 delayed 方法内")
println("参数: " + t)
t
}
}
以上实例中我们声明了 delayed 方法, 该方法在变量名和变量类型使用 =>
符号来设置传名调用。执行以上代码,输出结果如下:
在 delayed 方法内
获取时间,单位为纳秒
参数: 241550840475831
获取时间,单位为纳秒
分析可得,delayed()中的time()参数先是没有调用,在打印t时再计算调用的.
我们把t: => Long
改成t: Long
,打印结果就变为:
获取时间,单位为纳秒
在 delayed 方法内
参数: 241550840475831
可能现在大家有些乱,如果把这里的t: => Long
改成t: () => Long
我想大家就清楚了,方便记忆,可以认为这里的t
并不是一个值类型参数,而是一个函数类型的参数.
指定函数参数名
只是调用的时候,使用=
指定参数而已,比如:
object Test {
def main(args: Array[String]) {
printInt(b=5, a=7);
}
def printInt( a:Int, b:Int ) = {
println("Value of a : " + a );
println("Value of b : " + b );
}
}
可变参数
Scala 允许你指明函数的最后一个参数可以是重复的,即我们不需要指定函数参数的个数,可以向函数传入可变长度参数列表。
Scala 通过在参数的类型之后放一个星号来设置可变参数(可重复的参数)。例如:
object Test {
def main(args: Array[String]) {
printStrings("Runoob", "Scala", "Python");
}
def printStrings( args:String* ) = {
var i : Int = 0;
for( arg <- args ){
println("Arg value[" + i + "] = " + arg );
i = i + 1;
}
}
}
递归函数
递归函数意味着函数可以调用它本身。常用的比如计算阶乘.
默认参数值
在定义函数的时候使用=
设置默认值,如:
def addInt( a:Int=5, b:Int=7 ) : Int = { }
高阶函数(Higher-Order Function)
Scala 中允许使用高阶函数,高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。
内嵌函数
我么可以在 Scala 函数内定义函数,定义在函数内的函数称之为局部函数。
匿名函数
Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体。
使用匿名函数后,我们的代码变得更简洁了。
var mul = (x: Int, y: Int) => x*y
偏应用函数
Scala 偏应用函数是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。
如下实例,我们打印日志信息:
import java.util.Date
object Test {
def main(args: Array[String]) {
val date = new Date
log(date, "message1" )
Thread.sleep(1000)
log(date, "message2" )
Thread.sleep(1000)
log(date, "message3" )
}
def log(date: Date, message: String) = {
println(date + "----" + message)
}
}
输出结果为:
Mon Dec 02 12:52:41 CST 2013----message1
Mon Dec 02 12:52:41 CST 2013----message2
Mon Dec 02 12:52:41 CST 2013----message3
我们可以使用偏应用函数优化以上方法,绑定第一个 date 参数,第二个参数使用下划线(_)替换缺失的参数列表,并把这个新的函数赋给变量。
如下:
import java.util.Date
object Test {
def main(args: Array[String]) {
val date = new Date
val logWithDateBound = log(date, _ : String)
logWithDateBound("message1" )
Thread.sleep(1000)
logWithDateBound("message2" )
Thread.sleep(1000)
logWithDateBound("message3" )
}
def log(date: Date, message: String) = {
println(date + "----" + message)
}
}
函数柯里化(Function Currying)
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受两次一个参数的函数的过程。
如下,首先我们定义一个函数:
def add(x:Int,y:Int)=x+y
那么我们应用的时候,应该是这样用:add(1,2)
调用方法.
现在我们把这个函数变一下形:
def add(x:Int)(y:Int) = x + y
我们使用add(1)(2)
,最后结果都一样是3,这种方式(过程)就叫柯里化。
Scala 闭包
闭包是一个函数实现方式,返回值依赖于声明在函数外部的一个或多个变量。
完整实例:
object Test {
def main(args: Array[String]) {
println( "muliplier(1) value = " + multiplier(1) )
println( "muliplier(2) value = " + multiplier(2) )
}
var factor = 3
val multiplier = (i:Int) => i * factor
}
是其实和js的闭包一样.
Scala 字符串
实例:
object Test {
val greeting: String = "Hello,World!"
def main(args: Array[String]) {
println( greeting )
}
}
以上实例定义了变量 greeting,为字符串常量,它的类型为 String (java.lang.String)。
在 Scala 中,字符串的类型实际上是 Java String,Scala本身没有 String 类。
在 Scala 中,String 是一个不可变的对象
,所以该对象不可被修改。这就意味着你如果修改字符串就会产生一个新的字符串对象。
创建字符串
创建字符串实例如下:
var greeting = "Hello World!";
或
var greeting:String = "Hello World!";
字符串连接
String 类中使用 concat() 方法来连接两个字符串:string1.concat(string2)
同样你也可以使用加号(+)来连接.string1 + string2
StringBuilder 类
object Test {
def main(args: Array[String]) {
val buf = new StringBuilder;
buf += 'a' //+= 对字符
buf ++= "bcdef" //++= 对字符串
println( "buf is : " + buf.toString );
}
}
StringBuffer 类
StringBuffer 类似,就不说了.
StringBuilder和StringBuffer区别是历代面试重点考点,主要区别如下.
StringBuilder | StringBuffer |
---|---|
非线程安全的 | 线程安全的 |
效率高 | 效率低 |
上面只是基础问题,但实际使用中,很少用到StringBuffer,同时JVM对于(+)拼接也做了优化,在非循环情况下自动转为StringBuilder.本段引自
创建格式化字符串
- String 类中你可以使用
printf()
方法来格式化字符串并输出; - String
format()
方法可以返回 String 对象而不是 PrintStream 对象。
以下实例演示了 printf() 方法的使用:
object Test {
def main(args: Array[String]) {
var floatVar = 12.456
var intVar = 2000
var stringVar = "菜鸟教程!"
var fs = printf("浮点型变量为 " +
"%f, 整型变量为 %d, 字符串为 " +
" %s", floatVar, intVar, stringVar)
println(fs)
}
}
字符串方法
和java基本一致!
Scala 数组
Scala 语言中提供的数组是用来存储固定大小
的同类型
元素,数组对于每一门编程语言来说都是重要的数据结构之一。
声明数组
以下是 Scala 数组声明的语法格式:
var z:Array[String] = new Array[String](3)
或
var z = new Array[String](3)
然后就可以通过索引来访问每个元素:
z(0) = "Runoob"
z(1) = "Baidu"
z(4/2) = "Google"
我们也可以使用以下方式来定义一个数组:即定义时直接赋值...
var z = Array("Runoob", "Baidu", "Google")
处理数组
数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本的 for 循环。
object Test {
def main(args: Array[String]) {
var myList = Array(1.9, 2.9, 3.4, 3.5)
// 输出所有数组元素
for ( x <- myList ) {
println( x )
}
// 计算数组所有元素的总和
var total = 0.0;
for ( i <- 0 to (myList.length - 1)) {
total += myList(i);
}
println("总和为 " + total);
// 查找数组中的最大元素
var max = myList(0);
for ( i <- 1 to (myList.length - 1) ) {
if (myList(i) > max) max = myList(i);
}
println("最大值为 " + max);
}
}
多维数组
不多介绍
合并数组
以下实例中,我们使用 concat()
方法来合并两个数组,concat() 方法中接受多个数组参数:
import Array._
object Test {
def main(args: Array[String]) {
var myList1 = Array(1.9, 2.9, 3.4, 3.5)
var myList2 = Array(8.9, 7.9, 0.4, 1.5)
var myList3 = concat(myList1, myList2)
// 输出所有数组元素
for ( x <- myList3 ) {
println( x )
}
}
}
创建区间数组
以下实例中,我们使用了 range()
方法来生成一个区间范围内的数组。range() 方法最后一个参数为步长,默认为 1:
import Array._
object Test {
def main(args: Array[String]) {
var myList1 = Array.range(10, 20, 2)
var myList2 = Array.range(10,20)
// 输出所有数组元素
for ( x <- myList1 ) {
print( " " + x )
}
println()
for ( x <- myList2 ) {
print( " " + x )
}
}
}
执行以上代码,输出结果为:
10 12 14 16 18
10 11 12 13 14 15 16 17 18 19
Scala Collection
Scala 集合分为可变的和不可变的集合。
可变集合可以在适当的地方被更新或扩展。这意味着你可以修改,添加,移除一个集合的元素。
而不可变集合类,相比之下,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变。
集合 | 描述 |
---|---|
List(列表) | 其元素以线性方式存储,集合中可以存放重复对象。 |
Set(集合) | 最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。 |
Map(映射) | 把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 |
元组 | 元组是不同类型的值的集合 |
Option | Option[T] 表示有可能包含值的容器,也可能不包含值。 |
Iterator(迭代器) | 迭代器不是一个容器,更确切的说是逐一访问容器内元素的方法。 |
下面是各个集合的简单实现:
// 定义整型 List
val x = List(1,2,3,4)
// 定义 Set
var x = Set(1,3,5,7)
// 定义 Map
val x = Map("one" -> 1, "two" -> 2, "three" -> 3)
// 创建两个不同类型元素的元组
val x = (10, "Runoob")
// 定义 Option
val x:Option[Int] = Some(5)
Iterator(迭代器)
迭代器 it 的两个基本操作是 next 和 hasNext。
- 调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。
- 调用 it.hasNext() 用于检测集合中是否还有元素。
让迭代器 it 逐个返回所有元素最简单的方法是使用 while 循环:
object Test {
def main(args: Array[String]) {
val it = Iterator("Baidu", "Google", "Runoob", "Taobao")
while (it.hasNext){
println(it.next())
}
}
}
- it.min 和 it.max 方法从迭代器中查找最大与最小元素;
- it.size 或 it.length 方法来查看迭代器中的元素个数;
Scala 类和对象
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
实例如下:
object StudyClass {
def main(args: Array[String]) {
val pt = new Point(10, 20);
// 移到一个新的位置
pt.move(10, 10);
}
}
class Point(xc: Int, yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点: " + x);
println ("y 的坐标点: " + y);
}
}
Scala 继承
Scala继承一个基类跟Java很相似, 但我们需要注意一下几点:
- 重写一个非抽象方法必须使用override修饰符。
- 只有主构造函数才可以往基类的构造函数里写参数。
- 在子类中重写超类的抽象方法时,你不需要使用override关键字。
实例演示:
class Point(xc: Int, yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点: " + x);
println ("y 的坐标点: " + y);
}
}
class Location(override val xc: Int, override val yc: Int,
val zc :Int) extends Point(xc, yc){
var z: Int = zc
def move(dx: Int, dy: Int, dz: Int) {
x = x + dx
y = y + dy
z = z + dz
println ("x 的坐标点 : " + x);
println ("y 的坐标点 : " + y);
println ("z 的坐标点 : " + z);
}
}
Scala 单例对象
在 Scala 中,是没有 static 这个东西的,但是它也为我们提供了单例模式的实现方法,那就是使用关键字 object。
Scala 中使用单例模式时,除了定义的类之外,还要定义一个同名的 object 对象,它和类的区别是,object对象不能带参数。
当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。
类和它的伴生对象可以互相访问其私有成员。
单例对象实例
class singleObject(val xc: Int, val yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
}
}
object Test {
def main(args: Array[String]) {
val point = new singleObject(10, 20)
printPoint()
def printPoint(){
println ("x 的坐标点 : " + point.x);
println ("y 的坐标点 : " + point.y);
}
}
}
单例对象实例
// 私有构造方法
class CompanionObject private(val color:String) {
println("创建" + this)
override def toString(): String = "颜色标记:"+ color
}
// 伴生对象,与类共享名字,可以访问类的私有属性和方法
object CompanionObject{
private val markers: Map[String, CompanionObject] = Map(
"red" -> new CompanionObject("red"),
"blue" -> new CompanionObject("blue"),
"green" -> new CompanionObject("green")
)
def apply(color:String) = {
if(markers.contains(color))
Some(markers(color))
else
None
}
def getCompanionObject(color:String) = {
if(markers.contains(color))
Some(markers(color))
else
None
}
def main(args: Array[String]) {
println(CompanionObject("red"))
// 单例函数调用,省略了.(点)符号
println(CompanionObject getCompanionObject "blue")
// println(CompanionObject.getCompanionObject("blue"))
}
}
Scala Trait(特征)
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。
简单实例:
trait Equal {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}
以上Trait(特征)由两个方法组成:isEqual 和 isNotEqual。isEqual 方法没有定义方法的实现,isNotEqual定义了方法的实现。子类继承特征可以实现未被实现的方法。所以其实 Scala Trait(特征)更像 Java 的抽象类。
以下展示Trait的完整实例:
trait StudyTrait {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}
class Point(xc: Int, yc: Int) extends StudyTrait {
var x: Int = xc
var y: Int = yc
def isEqual(obj: Any) =
obj.isInstanceOf[Point] &&
obj.asInstanceOf[Point].x == x
}
object Test2 {
def main(args: Array[String]) {
val p1 = new Point(2, 3)
val p2 = new Point(2, 3)
val p3 = new Point(3, 3)
println(p1.isNotEqual(p2))
println(p1.isNotEqual(p3))
println(p1.isNotEqual(2))
}
}
特征构造顺序
特征也可以有构造器,由字段的初始化和其他特征体中的语句构成。
构造器的执行顺序:
- 调用基类的构造器;
- 特征构造器在基类构造器之后、类构造器之前执行;
- 特征由左到右被构造;
- 每个特征当中,父特征先被构造;
- 如果多个特征共有一个父特征,父特征不会被重复构造
- 所有特征被构造完毕,子类被构造。
++构造器的顺序是类的线性化的反向。线性化是描述某个类型的所有超类型的一种技术规格。++
上面那句话说人话就是,最右边的特质最先实例化,当其中有
super
字样调用方法时,调用的是它左侧的类方法,如果左侧与它不是同一组,则真正调用父类方法.也就是super
是动态调用的.
Scala 模式匹配
模式匹配是FP语言的一大利器,务必学好,本篇仅是入门.
一个模式匹配包含了一系列备选项,每个都开始于关键字 case。每个备选项都包含了一个模式及一到多个表达式。箭头符号 => 隔开了模式和表达式。
以下是一个简单的整型值模式匹配实例:
object Test {
def main(args: Array[String]) {
println(matchTest(3))
}
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
}
match 对应 Java 里的 switch,但是写在选择器表达式之后。即:选择器 match {备选项}。
我们还可以匹配不同类型,像这样:
object Test {
def main(args: Array[String]) {
println(matchTest("two"))
println(matchTest("test"))
println(matchTest(1))
println(matchTest(6))
}
def matchTest(x: Any): Any = x match {
case 1 => "one"
case "two" => 2
case y: Int => "scala.Int"
case _ => "many"
}
}
使用样例类
使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配。
以下是样例类的简单实例:
object Test {
def main(args: Array[String]) {
val alice = new Person("Alice", 25)
val bob = new Person("Bob", 32)
val charlie = new Person("Charlie", 32)
for (person <- List(alice, bob, charlie)) {
person match {
case Person("Alice", 25) => println("Hi Alice!")
case Person("Bob", 32) => println("Hi Bob!")
case Person(name, age) =>
println("Age: " + age + " year, name: " + name + "?")
}
}
}
// 样例类
case class Person(name: String, age: Int)
}
在声明样例类时,下面的过程自动发生了:
- 构造器的每个参数都成为
private[this] val
,除非显式被声明为var
,但是并不推荐这么做; - 在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象,apply就像Object的构造函数一样,使用对象名会被自动调用;
- 提供unapply方法使模式匹配可以工作;
- 生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
Scala 正则表达式
Scala 通过 scala.util.matching 包中的 Regex 类来支持正则表达式。
以下实例演示了使用正则表达式查找单词 Scala
:
import scala.util.matching.Regex
object StudyRegex {
def main(args: Array[String]) {
val pattern = "Scala".r()
val str = "Scala is Scalable and cool"
println(pattern.findFirstIn(str))
}
}
执行以上代码,输出结果为:
Some(Scala)
实例中使用 String 类的 r() 方法构造了一个Regex对象。
然后使用 findFirstIn 方法找到首个匹配项。
如果需要查看所有的匹配项可以使用 findAllIn 方法。
你可以使用 mkString( ) 方法来连接正则表达式匹配结果的字符串,并可以使用管道(|)来设置不同的模式:
import scala.util.matching.Regex
object Test {
def main(args: Array[String]) {
val pattern = new Regex("(S|s)cala") // 首字母可以是大写 S 或小写 s
val str = "Scala is scalable and cool"
println((pattern findAllIn str).mkString(",")) // 使用逗号 , 连接返回结果
}
}
执行以上代码,输出结果为:
Scala,scala
如果你需要将匹配的文本替换为指定的关键词,可以使用 replaceFirstIn( ) 方法来替换第一个匹配项,使用 replaceAllIn( ) 方法替换所有匹配项,实例如下:
object Test {
def main(args: Array[String]) {
val pattern = "(S|s)cala".r
val str = "Scala is scalable and cool"
println(pattern replaceFirstIn(str, "Java"))
}
}
执行以上代码,输出结果为:
Java is scalable and cool
Scala 异常处理
抛出异常
Scala 抛出异常的方法和 Java一样,使用 throw
方法,例如,抛出一个新的参数异常:
throw new IllegalArgumentException
捕获异常
异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。
因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。
捕捉异常的catch子句,语法与其他语言中不太一样。在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句,如下例所示:
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
object Test {
def main(args: Array[String]) {
try {
val f = new FileReader("input.txt")
} catch {
case ex: FileNotFoundException =>{
println("Missing file exception")
}
case ex: IOException => {
println("IO Exception")
}
} finally {
println("Exiting finally...")
}
}
}
执行以上代码,输出结果为:
Missing file exception
Exiting finally...
Scala 提取器(Extractor)
提取器是从传递给它的对象中提取出构造该对象的参数。
Scala 提取器是一个带有unapply方法的对象。unapply方法算是apply方法的反向操作:unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。
以下实例演示了邮件地址的提取器对象:
object Test {
def main(args: Array[String]) {
println ("Apply 方法 : " + apply("Zara", "gmail.com"));
println ("Unapply 方法 : " + unapply("Zara@gmail.com"));
println ("Unapply 方法 : " + unapply("Zara Ali"));
}
// 注入方法 (可选)
def apply(user: String, domain: String) = {
user +"@"+ domain
}
// 提取方法(必选)
def unapply(str: String): Option[(String, String)] = {
val parts = str split "@"
if (parts.length == 2){
Some(parts(0), parts(1))
}else{
None
}
}
}
执行以上代码,输出结果为:
Apply 方法 : Zara@gmail.com
Unapply 方法 : Some((Zara,gmail.com))
Unapply 方法 : None
以上对象定义了两个方法: apply 和 unapply 方法。通过 apply 方法我们无需使用 new 操作就可以创建对象。所以你可以通过语句 Test("Zara", "gmail.com") 来构造一个字符串 "Zara@gmail.com"。
unapply方法算是apply方法的反向操作:unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。实例中我们使用 Unapply 方法从对象中提取用户名和邮件地址的后缀。
提取器使用模式匹配
在我们实例化一个类的时,可以带上0个或者多个的参数,编译器在实例化的时会调用 apply 方法。我们可以在类和对象中都定义 apply 方法。
就像我们之前提到过的,unapply 用于提取我们指定查找的值,它与 apply 的操作相反。 当我们在提取器对象中使用 match 语句时,unapply 将自动执行,如下所示:
object Test {
def main(args: Array[String]) {
val x = Test(5)
println(x)
x match {
case Test(num) => println(x + " 是 " + num + " 的两倍!")
//unapply 被调用
case _ => println("无法计算")
}
}
def apply(x: Int) = x*2
def unapply(z: Int): Option[Int] = if (z%2==0) Some(z/2) else None
}
执行以上代码,输出结果为:
10
10 是 5 的两倍!
Scala 文件 I/O
Scala 进行文件写操作,直接用的都是 java中 的 I/O 类 (java.io.File):
import java.io._
object Test {
def main(args: Array[String]) {
val writer = new PrintWriter(new File("test.txt" ))
writer.write("BarryW")
writer.close()
}
}
执行以上代码,会在你的当前目录下生产一个 test.txt 文件,文件内容为"菜鸟教程".
从屏幕上读取用户输入
有时候我们需要接收用户在屏幕输入的指令来处理程序。实例如下:
object Test {
def main(args: Array[String]) {
print("请输入: " )
val line = Console.readLine
println("谢谢,你输入的是: " + line)
}
}
从文件上读取内容
从文件读取内容非常简单。我们可以使用 Scala 的 Source 类及伴生对象来读取文件。
以下实例演示了从 "test.txt"(之前已创建过) 文件中读取内容:
import scala.io.Source
object Test {
def main(args: Array[String]) {
println("文件内容为:" )
Source.fromFile("test.txt" ).foreach{
print
}
}
}
以上都是Scala中最简单基础的入门,以及一些在Java等面向对象程序中比较常用的功能, 我接下来的文章中,会继续介绍Scala的一些高级
功能和别扭
的语法糖, Scala就是通过那些别扭
的语法大大缩减了代码量, 并且使程序更加易写易读. 希望本文可以让大家感受到FP简洁不简单
的魅力.