简介
scala是一门综合了面向对象和函数式编程概念的静态类型的编程语言。
函数式编程以两大核心理念为指导:第一个理念是函数是一等的值。(可以将函数作为参数传递给其他函数,作为返回值返回它们。)第二个核心理念是程序中的操作应该将输入值映射成输出值,而不是当场修改数据。不可变数据结构是函数式编程的基石之一。函数式编程的这个核心理念的另一种表述方法是不应该有副作用。方法只能通过接受入参和返回结果这两种方式与外部环境通信。
为什么要用Scala编程?
- 兼容性:Scala程序会被编译成jvm字节码。可以与Java无缝互调。
- 精简性:Scala的类型推断。
- 高级抽象。
- 静态类型。
快速入门
- 所有Java的基本类型在Scala包中都有对应的类。
scala.Boolean对应Java的boolean,scala.Float对应Java的float。
Scala的字符串是用Java的String类实现的。特质(trait)跟Java的接口类似。
Scala的Unit类型跟Java的void类型类似。 - Scala变量分为两种:val和var。
val和Java的final变量类似,一旦初始化就不能被重新赋值。而var则不同,类似于Java的非final变量,在整个生命周期内var可以被重新赋值。
当你用val定义一个变量时,变量本身不能被重新赋值,但是它指向的那个对象是有可能发生改变的。 - Scala的类型推断能力,能够推断出那些不显式指定的类型
Scala编译器并不会推断函数参数的类型 - 函数式编程与指令式编程
e.g.args.foreach(println)
- Scala用圆括号而不是方括号来访问数组
数组不过是类的实例,这一点跟其他Scala实例没有本质区别。 - 函数式编程的一个重要理念之一是方法不能有副作用。
一个方法唯一要做的是计算并返回一个值。 - Scala数组是一个拥有相同类型的对象的可变序列。
数组可变的对象。Scala的List是不可变的。元组也是不可变的。元组可以容纳不同类型的元素。 - 为什么不在列表的末尾追加元素?
因为往末尾追加元素的操作所需要时间随着列表的大小线性增加。而使用:: 在列表的前面添加元素只需要常量的时间。 - 如果方法名的最后一个字符是冒号,该方法的调用会发生在它的右操作元上。
- 访问元组中的元素为什么是_N形式?
背后的原因是列表的apply方法永远只返回同一种类型,但是元组中的元素可以是不同类型的。字段名是从1开始而不是0开始,这是由其他同样支持静态类型的元组的语言设定的传统。 - 集和映射
set和map提供了可变和不可变两种选择。
scala.collection
Set/Map
<<trait>>
|
scala.collection.immutable scala.collection.mutable
Set /Map Set/Map
<<trait>> <<trait>>
| |
scala.collection.immutable scala.collection.mutable
HashSet/HashMap HashSet/HashMap
- 识别函数式编程风格
一个向函数式风格转变的方向是尽量不用var。 - 从文件读取文本行
import scala.io.Source
Source.fromFile(fileName).getLines()
# 返回一个Iterator[String]
类和对象
Scala可以对分号进行自动推断。
字段保留了对象的状态,或者说数据,而方法用这些数据来对对象执行计算。
要将某个字段声明为私有,可以在字段前面加上private这个访问修饰符。
public是Scala的默认访问级别。
Scala方法的参数的一个重要的特征是它们都是val而不是var。
推荐的风格是避免使用任何显示的return语句。在没有任何显示的return语句时,Scala方法返回的是该方法计算出的最后一个表达式的值。
Scala类不允许由静态成员。Scala提供了单例对象。关键字是object。
当单例对象跟某个类共用同一个名字时,它被称为这个类的伴生对象。同时,类又叫做这个单例对象的伴生类。类和它的伴生对象可以互相访问对方的私有成员。不能实例化。直接调用伴生对象名。单例对象在有代码首次访问时才被初始化。没有同名的伴生类的单例对象称为孤立对象。
类和单例对象的一个区别是单例对象不接受参数,而类可以。
Scala在每个Scala源码文件都隐式的引入了java.lang和scala包的成员,以及名为Predef的单例对象的所有成员。
scala提供了一个特质scala.App。
基础类型和操作
Scala基础类型:java.lang.String, scala.Int, Long, Short, Byte, Float, Double, Char, Boolean。
字符串插值:s插值器:对内嵌的每个表达式求值,
raw插值器:不识别字符转义序列,
f插值器:可以进行格式化。
Scala提供了eq方法用于比较引用相等性的机制。
富包装类:scala.runtime.RichByte, scala.runtime.RichShort, ...
scala.collection.immutable.StringOps。
函数式对象
Scala编译器会将你在类定义体中给出的非字段或方法定义的代码编译进类的主构造方法中。
关键字this指向当前执行方法的调用对象,当被用在构造方法里的时候,指向被构造的对象实例。
Scala的辅助构造方法以def this(...)
开始。在Scala中,每个辅助构造方法都必须首先调用同一个类的另一个构造方法。主构造方法就是类的单一入口。只有主构造方法可以调用超类的构造方法。
内建的控制结构
控制结构包括:if, while, do while, for, try catch finally, match case。
if表达式可以有返回值。val filename = if (!args.isEmpty) args(0) else "default.txt"
while循环没有返回值。
for循环可以有返回值。val forLineLengths = for {file <- filesHere if file.getName.endsWith(".scala") line <- fileLines(file) trimmed = line.trim if trimmed.matches(".*for.*") } yield trimmed.length
match 表达式会返回值。val friend = firstArg match { case "salt" => "pepper" case "chips" => "salsa" case "eggs" => "bacon" case _ => "huh?" } println(friend)
函数和闭包
匿名函数:(x: Int) => x + 1
部分应用的函数是一个表达式,在这个表达式中,并不给出函数需要的所有参数,而是给出部分,或完全不给。def sum(a: Int, b: Int, c: Int) = a + b + c; val a = sum _; a(1, 2, 3)
尾递归函数并不会在每次调用时构建一个新的栈帧,所有的调用都会在同一个栈帧中执行。
组合和继承
组合的意思是一个类可以包含对另一个类的引用,利用这个引用类来帮助它完成任务;而继承是超类\子类的关系。
一个包含抽象成员的类本身也要声明为抽象的,做法是在class关键字之前写上abstract修饰符。
一个方法只要没有实现,那么它就是抽象的。
继承的意思是超类的所有成员也是子类的成员,但是有两个例外。一个是超类的私有成员并不会被子类继承;二是如果子类里已经实现了相同名称和参数的成员,那么该成员不会被继承。
Scala要求在所有重写了父类具体成员的成员之前加上这个修饰符。
要调用超类的构造方法,只需将你打算传入的入参放在超类名称后的圆括号里即可。
多态的意思是多种形式。动态绑定的意思是说实际被调用的方法实现是在运行时基于对象的类决定的,而不是变量或者表达式的类型决定的。
工厂对象包含创建其他对象的方法。使用方用这些工厂方法来构造对象,而不是直接用new构建对象。
继承关系
在Scala中,每个类都继承自同一个名为Any的超类。Scala还在继承关系的底部定义了Null和Nothing,Nothing是每一个其他类的子类。
class Any() {
final def ==(that: Any): Boolean
final def !=(that: Any): Boolean
def equales(that: Any): Boolean
//
def ##: Int
def hashCode:Int
def toString: String
}
子类可以通过重写equals方法来定制==和!=的含义。
根类Any有两个子类: AnyVal和AnyRef。AnyVal是Scala中所有值类的父类,Scala提供了几个内建的值类:Byte、Short、Char、Int、Long、Float、Double、Boolean和Unit。前八个对应Java的基本类型。另外的Unit对应Java的void的类型。AnyRef是Scala所有引用类的基类。在Java平台上AnyRef事实上只是java.lang.Object的一个别名。AnyRef类定义了一个eq方法,该方法不能被重写,实现为引用相等性,还定义了一个反义的方法ne。
Null类是null引用的类型,它是每个引用类的子类。Nothing是每个其他类型的子类型。并不存在在个类型的任何值。Nothing的用途之一是给出非正常终止的信号。
def error(message: String): Nothing = {
throw new RuntimeException(message)
}
def divide(x: Int, y: Int): Int = {
if (y != 0) x / y
else error("can't divide by zero")
}
特质
特质是Scala代码复用的基础单元。类可以同时混入任意数量的特质。可以使用extends或者with关键字将它混入类中。定义一个特质使用trait关键字。特质最常用的使用场景是将廋接口拓宽为富接口以及定义可叠加的修改。特质与Java的接口类似,但是功能更强大。
类与特质的区别是特质不能有任何的类参数,特质中的super的调用是动态绑定的。
包和引入
Scala的引入可以出现在任何地方。
Scala对每个程序都隐式的添加了一些引入。
import java.lang._
import scala._
import Predef._
标private的成员只在包含该定义的类或对象的内部可见,在Scala中,这个规则同样适用于内部类。
在Scala中,protected的成员只能从定义该成员的子类访问。
断言和测试
断言和测试是用来检查软件行为符合预期的两种重要手段。
在Scala中,断言的写法是对预定义方法assert的调用。如果条件condition不满足,表达式assert(condition)将抛出AssertionError。assert还有另一个版本,assert(condition, explanation),condition不满足,那么就抛出包含给定的explanation的AssertionError。
ensuring() 这个方法可以被用于任何结果类型。ensuring()是对返回结果行进行断言的方法。
用Scala写测试,有很多选择,例如:ScalaTest、specs2和ScalaCheck。
样例类和模式匹配
样例类是Scala用来对对象进行模式匹配而并不需要大量的样板代码的方式。带有case这种修饰符的类称作样例类。模式匹配包含一系列以case关键字打头的可选分支。
密封类除了在同一个文件中定义的子类之外,不能添加新的子类。只需要在类继承关系的顶部那个类的类名前面加上sealed关键字就成为了密封类。
Scala由一个名为Option的标准类型来表示可选值。这样的值可以有两种形式:Some(x),其中x是那个实际的值;或者None对象,代表没有值。将可选值解开最常见的方式是通过模式匹配。
可变对象
对于可变对象而言,方法调用或者字段访问的结果可能取决于之前这个对象被执行了那些操作。同样的操作在不同的时间会返回不同的结果。
在Scala中,每一个非私有的var成员都隐式的定义了对应的getter和setter方法。
类型参数化
泛型的意思是我们用一个泛化的类或者特质来定义许多具体的类型。在Scala中,泛型类型默认的子类型规则是不变的。在类型形参前面加上+
表示子类型关系在这个参数上是协变的。加上-
表示逆变的。类型参数是协变的、逆变的、不变的被称为类型参数的型变。+
和-
被称为型变注解。用U >: T
这样的语法定义了U
的下界为T
。用T <: U
这样的语法定义了T
的上界为U
。
抽象成员
如果类或者特质的某个成员在当前类中没有完整的定义,那么它就是抽象的。抽象成员的本意是为了让声明该成员的类的子类来实现。在Scala中,抽象成员包括抽象方法、抽象字段(val 、var)、抽象类型(type)。
trait Abstract {
type T
def transform(x: T): T
val initial: T
var current: T
}
#具体实现
class Concrete extends Abstract {
type T = String
def transform(x: String) = x + x
val initial = "hi"
var current = initial
}
创建新枚举的方式是定义一个扩展自scala.Enumeration类的对象。
object Direction extends Enumeration {
val North = Value("North")
val East = Value("East")
val South = Value("South")
val West = Value("West")
}
#引入枚举类的所有值
import Direction._
#遍历枚举的值
for (d <- Direction.values) print(d + " ")
#获取值的编号
Direction.East.id #1
#获取指定编号的值
Direction(1) #East
隐式转换与隐式参数
隐式定义指的是那些我们允许编译器插入程序以解决类型错误的定义。关键字是implicit。可以用implicit来标记任何变量、函数、类和对象定义。
implicit def intToString(x: Int) = x.toString
通常是把一些有用的隐式转换放到一个命名为Preamble对象中,这样就可以通过import Preamble._
来访问这些隐式转换。
Scala总共有三个地方会使用到隐式定义:从一个类型转换到一个预期的类型;对某个成员选择接收端(即字段、方法调用等)的转换;隐式参数。
#隐式转换到一个预期的参数
implicit def doubleToInt(x: Double) = x.toInt
val i: Int = 3.5
#只要你看见了有人调用了接受类中不存在的方法,那么很可能使用了隐式转换
#隐式类是一个以implicit关键字打头的类
#隐式类必须存在于另一个对象、类或、特质中
#隐式参数
class PreferredPrompt(val preference: String)
object Greeter {
def greet(name: String)(implicit prompt: PrefereedPrompt) = {
println("Welcome, " + name + ". The system is ready.")
println(prompt.preference)
}
}
object JoesPrefs {
implicit val prompt = new PreferredPrompt("Yes, Master> ")
}
import JoesPrefs._
#需要两个参数,但是只传入了一个参数,另一个参数是隐式传入的。
Greeter.greet("Joe")
#返回结果
Welcome, Joe. The system is ready.
Yes, master
当前作用域内由多个隐式转换都满足要求时,如果可用的转换当中有某个转换严格来说比其他的更具体,那么编译器就会选择这个更具体的转换。
实现列表
package scala
abstract class List[+T] {
def isEmpty: Boolean
def head: T
def tail: List[T]
}
case object Nil extends List[Nothing] {
override def isEmpty = true
def head: Nothing = throw new NoSuchElementException("head of empty list")
def tail: List[Nothing] = throw new NoSuchElementException("tail of empty list")
}
final case class ::[T](hd: U, private var tl: List[U]) extends List[U] {
def head = hd
def tail = tl
override def isEmpty: Boolean = false
}
package scala.collection.immutable
final class ListBuffer[T] extends Buffer[T] {
private var start: List[T] = Nil
private var last0: ::[T] = _
private var exported: Boolean = false
...
}
重访for表达式
所有最终交出(yield)结果的for表达式都会被编译器翻译成对高阶函数map、flatMap和withFilter的调用。所有不带yield的for循环会被翻译成更小集的高阶函数:只有withFilter和foreach。
深入集合类
#集合继承关系图
Traversable
Iterable
Seq
IndexedSeq
Vector
ResizableArray
GenericArray
LinearSeq
MutableList
List
Stream
Buffer
ListBuffer
ArrayBuffer
Set
SortedSet
TreeSet
HashSet (可变的)
LinkedHashSet (有序的)
HashSet (不可变的)
BitSet
EmptySet, Set1, Set2, Set3, Set4
Map
SortedMap
TreeMap
HashMap (可变的)
LinkedHashMap (可变的)
HashMap (不可变的)
EmptyMap, Map1, Map2, Map3, Map4
- Traversable特质:
抽象方法:
def foreach[U](f: Elem => U)
具体方法:
++, map, flatMap, collect, toIndexedSeq, toIterable, toStream, toArray, toList, toSeq, toSet, toMap, isEmpty, nonEmpty, size, head, last, tail, init, slice, take, drop, filter, exists, sum, min, max mkString, addString, view, ... - Iterable特质:
该特质的所有方法都是用抽象方法iterator来定义的。
重写foreach方法:
def foreach[U](f: Elem => U): Unit = {
val it = iterator
while (it.hasNext) f(it.next())
}
位于Iterable之下有三个特质: Seq, Set, Map.
- Seq特质:
方法:
apply, length, indexOf, +:, :+, updated, sorted, sortWith, sortBy, reverse, startsWith, endsWith, contains, diff, union, ...
buffer:
方法:
+=, ++=, +=:, ++=:, insert, -=, remove, ... - Set特质:
是没有重复元素的Iterable。
不可变集:
方法:
contains, apply, +, ++, -, --, union, diff, ...
可变集:
方法:
+=, ++=, add, -=, --=, remove, ... - Map特质:
是有键值对组成的Iterable。
不可变映射:
方法:
apply, get, getOrElse, contains, +, ++, updated, -, --, keys, keySet, values, ...
可变映射:
方法:
+=, ++=, put, -=, --=, remove, ... - List:
是有限的不可变序列 - Stream:
其元素是惰性计算的。可以无限长。
val str = 1 #:: 2 #:: 3 #:: Stream.empty
- Vector:
是对头部之外的元素也提供高效访问的集合类型。不可变。
val vec = scala.collection.immutable.Vector.empty
val vec2 = vec :+ 1 :+ 2
val vec3 = 100 +: vec2
- Stack:
不可变:
val stack = scala.collection.immutable.Stack.empty
val hasOne = stack.push(1)
hasOne.top
hasOne.pop
可变:
val stack = scala.collection.mutable.Stack[Int]
stack.push(1)
stack.top
stack.pop
- Queue:
不可变:
val empty = scala.collection.immutable.Queue[Int]()
val has1 = empty.enqueue(1)
val has123 = has1.enqueue(List(2, 3))
val (element, has123) = has123.dequeue
可变:
val queue = scala.collection.mutable.Queue[String]
queue += "a"
queue.dequeue
- Range:
1 to 3
5 to 14 by 3
1 until 3
- BitSet:
不可变:
val bits = scala.collection.immutable.BitSet.empty
可变:
val bits = scala.collection.mutable.BitSet.empty
bits += 1
- ListMap:
val map = scala.collection.immutable.ListMap(1 -> "one")
map(1)
- ArrayBuffer:
val buf = scala.collection.mutable.ArrayBuffer.empty
buf += 1
- ListBuffer:
val buf = scala.collection.mutable.ListBuffer.empty[Int]
buf += 1
- StringBuilder:
val buf = new StringBuilder
buf += 'a'
- Array:
和Java的数组一一对应。支持泛型。
Scala集合框架
提取器
提取器是拥有名为unapply的成员方法的对象。
正则表达式:import scala.util.matching.Regex
val regex = new Regex("""(-)?(\d+)(\.\d*)?""")
原生字符串是由一对三个连续双引号括起来的字符序列。
val regex = """(-)?(\d+)(\.\d*)?""".r
regex findFirstIn str
regex findAllIn str
regex findPrefixOf str
注解
Scala在内部将注解表示为仅仅是对某个注解类的构造方法的调用。
(将@换成new,就能得到一个合法的创建实例的表达式)
import annotation._
注解的用处:
- 自动生成文档,就像Scaladoc那样。
- 格式化代码。
- 检查常见的代码问题。
- 类型检查。
// 声明该方法已经过时了。
@deprecated("use newShinyMethod() instead")
def bigMistaks() = // ...
// 声明不检查
(e: @unchecked) match {
// 没有全面覆盖的case 。。。
}
// 用在并发编程中。
@volatile
//二进制序列化
@serializable
//不应该被序列化的字段
@transient
//自动为字段生成get和set方法
@scala.reflect.BeanProperty
使用XML
XML是一种半结构化数据。
import scala.xml.Elem
import scala.xml.NodeSeq
import scala.xml.Node
import scala.xml.XML
对象相等性
class Point (val x: Int, val y: Int) {
// 计算hash值
override def hashCode = (x, y).##
override def equals (other: Any) = other match {
case that: Point =>
(that canEqual this) &&
(this.x == that.x) && (this.y == that.y)
case _ => false
}
def canEqual(other: Any) = other.isInstanceOf[Point]
}
结合Scala和Java
Scala的实现方式是将代码翻译成标准的Java字节码。
Java并没有单例对象的确切对应,不过他的确有静态方法。Scala对单例对象的翻译采用了静态和实例方法相结合的方式。
编写任何特质都会创建一个同名的Java接口。
所有Scala方法都被翻译成没有声明任何抛出异常的Java方法。