以下内容基于对官方教程 TOUR OF SCALA 中文版的学习,文字和代码内容绝大多数来源于官方教程,顺序有所改变。
目录
目录
基础语法
常量定义使用val
变量定义var
var x: Int = 1 + 1, x为变量名,Int为变量类型,编译器可以推断的时候,类型可以省略不写
你可以组合几个表达式,并且用{}包围起来,我们称之为代码块(block)。代码块中最后一个表达式的结果,也正是整个块的结果。
类型层次
Any 是所有类型的超类型,也称为顶级类 型。它定义了一些通用的方法如equals、hashCode和toString
AnyVal 代表值类型
Double
Float
Long
Int
Short
Byte
Char
Unit 相当于java的void
Boolean
AnyRef 代表引用类型。AnyRef相当于java.lang.Object
List
Option
YourClass
Nothing 是所有类型的子类型,也称为底部类型。没有一个值是Nothing类型的。它的用途之一是给出非正常终止的信号,如抛出异常、程序退出或者一个无限循环
Null 是所有引用类型的子类型,它有一个单例值由关键字null所定义。Null主要是使得Scala满足和其他JVM语言的互操作性,但是几乎不应该在Scala代码中使用。
自动类型转换
Byte -> Short -> Int -> Long -> Float -> Double
Char -> Int
转Float和Double时可能丢精度
类
Scala中的类是用于创建对象的蓝图,其中包含了方法、常量、变量、类型、对象、特质、类,这些统称为成员。
泛型在类名后使用[]标识
类的主构造函数写在类名之后
主构造函数的参数默认为类的成员
val/var修饰的为类的公开常量/变量,否则为私有变量
参数可以给默认值,带默认值的参数可以在新建变量时不给值
多个默认值参数的时候,可以指定参数名给值
辅构造函数可以有多个,以def this的形式定义,参数和成员变量没有直接关系,需要手动赋值
辅助构造函数的第一行必须调用主构造函数或者其他辅助构造函数
成员变量定义时必须显示赋值, 可以使用"_"进行默认初始化
成员默认为public,加private变成私有后需要自己定义方法进行访问和赋值
类中可以嵌套类,方法中可以嵌套方法
class Person[A](val age: Int, var name: String, sex: Int = 0, math: Int = 0) {
private var privateFiled1: String = _
var filed2: A = _
var grade: Int = 5
private var classes: String = _
def this(grade: Int, classes: String, name: String) {
this(10, name, 1)
this.grade = grade;
this.classes = classes;
}
def filed1 = privateFiled1
def setFiled1(inFiled1: String) = {
privateFiled1 = inFiled1
}
def factorial(x: Int): Int = {
def fact(x: Int, accumulator: Int): Int = {
if (x <= 1) accumulator
else fact(x - 1, x * accumulator)
}
fact(x, 1)
}
def printInfo: Unit = {
println(s"printInfo ->grade:$grade,classes:${classes},name:${name},age:${age},sex:${sex},privateFiled1:${privateFiled1},filed2:${filed2}")
}
override def toString: String =
s"grade:$grade,classes:$classes,name:$name,age:$age,sex:$sex,privateFiled1:${privateFiled1},filed2:${filed2}"
}
object StudyMain {
def main(args: Array[String]) = {
var person1 = new Person[Int](6, "二", "王小二")
person1.setFiled1("abcd")
person1.filed2 = 99
person1.printInfo
println(person1)
var person2 = new Person[String](12, "李小红", math=100)
person2.filed2 = "efgh"
person2.printInfo
}
}
内部类
内部类是绑定到外部对象的。
默认情况下,内部类对象的类型为"外部对象名.内部类"
class Graph {
class Node {
var connectedNodes: List[Node] = Nil
def connectTo(node: Node) {
if (!connectedNodes.exists(node.equals)) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
val graph1: Graph = new Graph
val node1: graph1.Node = graph1.newNode
val node2: graph1.Node = graph1.newNode
node1.connectTo(node2) // legal
val graph2: Graph = new Graph
val node3: graph2.Node = graph2.newNode
node1.connectTo(node3) // illegal!
类型 graph1.Node 与类型 graph2.Node 完全不同。
在 Java 中将分配相同的类型 Graph.Node
在Scala中也可以表示出这种类型,它写成了 Graph#Node。 如果我们希望能够连接不同图形的节点,我们必须通过以下方式更改图形类的初始实现的定义:
class Graph {
class Node {
var connectedNodes: List[Graph#Node] = Nil
def connectTo(node: Graph#Node) {
if (!connectedNodes.exists(node.equals)) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
案例类(Case classes)
实例化案例类Book时,并没有使用关键字new,这是因为案例类有一个默认的apply方法来负责对象的创建。
当你创建包含参数的案例类时,这些参数是公开(public)的val
你不能给message1.sender重新赋值,因为它是一个val(不可变)。在案例类中使用var也是可以的,但并不推荐这样。
案例类在比较的时候是按值比较而非按引用比较
你可以通过copy方法创建一个案例类实例的浅拷贝,同时可以指定构造参数来做一些改变,未指定的参数不做改变
case class Message(sender: String, recipient: String, body: String)
val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?")
val messagesAreTheSame = message2 == message3 // true
val message4 = Message("julien@bretagne.fr", "travis@washington.us", "Me zo o komz gant ma amezeg")
val message5 = message4.copy(sender = message4.recipient, recipient = "claire@bourgogne.fr")
message5.sender // travis@washington.us
message5.recipient // claire@bourgogne.fr
message5.body // "Me zo o komz gant ma amezeg"
单例对象
单例对象是一种特殊的类,有且只有一个实例。和惰性变量一样,单例对象是延迟创建的,当它第一次被使用时创建。
当对象定义于顶层时(即没有包含在其他类中),单例对象只有一个实例。
当对象定义在一个类或方法中时,单例对象表现得和惰性变量一样。
package logging
object Logger {
def info(message: String): Unit = println(s"INFO: $message")
}
import logging.Logger.info
class Test {
info("Created projects") // Prints "INFO: Created projects"
}
伴生对象
当一个单例对象和某个类共享一个名称时,这个单例对象称为 伴生对象。 同理,这个类被称为是这个单例对象的伴生类。
类和它的伴生对象可以互相访问其私有成员。
使用伴生对象来定义那些在伴生类中不依赖于实例化对象而存在的成员变量或者方法。
在 Java 中 static 成员对应于 Scala 中的伴生对象的普通成员。
在 Java 代码中调用伴生对象时,伴生对象的成员会被定义成伴生类中的 static 成员。这称为 静态转发。
class Email(val username: String, val domainName: String)
object Email {
def fromString(emailString: String): Option[Email] = {
emailString.split('@') match {
case Array(a, b) => Some(new Email(a, b))
case _ => None
}
}
}
提取器对象
提取器对象是一个包含有 unapply 方法的单例对象。
apply 方法就像一个构造器,接受参数然后创建一个实例对象,反之 unapply 方法接受一个实例对象然后返回最初创建它所用的参数。
提取器常用在模式匹配和偏函数中。
unapply 方法的返回值应当符合下面的某一条:
如果只是用来判断真假,可以返回一个 Boolean 类型的值。例如 case even()。
如果只是用来提取单个 T 类型的值,可以返回 Option[T]。
如果你想要提取多个值,类型分别为 T1,...,Tn,可以把它们放在一个可选的元组中 Option[(T1,...,Tn)]。
有时,要提取的值的数量不是固定的,因此我们想根据输入来返回随机数量的值。可以用 unapplySeq 方法来定义提取器,此方法返回 Option[Seq[T]]。
常见的例子有,用 case List(x, y, z) => 来解构一个列表 List,以及用一个正则表达式 Regex 来分解一个字符串 String,例如 case r(name, remainingFields @ _*) =>。
import scala.util.Random
object CustomerID {
def apply(name: String) = s"$name--${Random.nextLong}"
def unapply(customerID: String): Option[String] = {
val stringArray: Array[String] = customerID.split("--")
if (stringArray.tail.nonEmpty) Some(stringArray.head) else None
}
}
val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908
customer1ID match {
case CustomerID(name) => println(name) // prints Sukyoung
case _ => println("Could not extract a CustomerID")
}
val customer2ID = CustomerID("Nico")
val CustomerID(name) = customer2ID
println(name) // prints Nico
特质
特质使用关键字trait定义,类似java的接口
特质可以定义常量/变量、方法
其他类通过使用extends关键字来扩展特质,然后使用 override 关键字来实现trait里面的任何抽象成员
通过混入(MIXIN)来组合类
当某个特质被用于组合类时,被称为混入。一个类只能有一个父类但是可以有多个混入(分别使用关键字extends和with)。混入和某个父类可能有相同的父类。
abstract class AbsIterator {
type T
def hasNext: Boolean
def next(): T
}
class StringIterator(s: String) extends AbsIterator {
type T = Char
private var i = 0
def hasNext = i < s.length
def next() = {
val ch = s charAt i
i += 1
ch
}
}
trait RichIterator extends AbsIterator {
def foreach(f: T => Unit): Unit = while (hasNext) f(next())
}
object StringIteratorTest extends App {
class RichStringIter extends StringIterator("Scala") with RichIterator
val richStringIter = new RichStringIter
richStringIter foreach println
}
抽象类型
特质和抽象类可以包含一个抽象类型成员,意味着实际类型可由具体实现来确定。
含有抽象类型成员的特质或类(classes)经常和匿名类的初始化一起使用。
trait Buffer {
type T
val element: T
}
abstract class SeqBuffer extends Buffer {
type U
type T <: Seq[U]
def length = element.length
}
abstract class IntSeqBuffer extends SeqBuffer {
type U = Int
}
def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
new IntSeqBuffer {
type T = List[U]
val element = List(elem1, elem2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
复合类型
有时需要表明一个对象的类型是其他几种类型的子类型。 在 Scala 中,这可以表示成 复合类型,即多个类型的交集。
obj: A with B with C ... { refinement }
自类型
自类型用于声明一个特质必须混入其他特质,尽管该特质没有直接扩展其他特质。 这使得所依赖的成员可以在没有导入的情况下使用。
自类型是一种细化 this 或 this 别名之类型的方法。 语法看起来像普通函数语法,但是意义完全不一样。
trait User {
def username: String
}
trait Tweeter {
this: User => // 重新赋予 this 的类型
def tweet(tweetText: String) = println(s"$username: $tweetText")
}
class VerifiedTweeter(val username_ : String) extends Tweeter with User { // 我们混入特质 User 因为 Tweeter 需要
def username = s"real $username_"
}
val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade") // 打印出 "real Beyoncé: Just spilled my glass of lemonade"
元组
元组是一个可以容纳不同类型元素的类,元组是不可变的。当需要返回多个值时,可以使用元组。
元组包含一系列类:Tuple2,Tuple3...Tuple22
使用下划线语法来访问元组中的元素, ‘tuple._n’ 取出了第 n 个元素(假设有足够多元素)
元组也支持解构,类似js
元组解构也可用于模式匹配或者for循环
类型 Unit 的值 () 在概念上与类型 Tuple0 的值 () 相同。 Tuple0 只能有一个值,因为它没有元素。
用户有时可能在元组和 case 类之间难以选择。 通常,如果元素具有更多含义,则首选 case 类。
val ingredient = ("Sugar" , 25):Tuple2[String, Int]
println(ingredient._1) // Sugar
println(ingredient._2) // Sugar
val (name, quantity) = ingredient
println(name) // Sugar
println(quantity) // 25
val planetDistanceFromSun = List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6 ), ("Mars", 227.9), ("Jupiter", 778.3))
for ((name, distance) <- planetDistanceFromSun) {
println(s"$name is $distance millions km far from Sun")
}
高阶函数
高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数。
使用函数值作为参数,或者返回值为函数值的“函数”和“方法”,均称之为“高阶函数”。
val salaries = Seq(20000, 70000, 40000)
val doubleSalary = (x: Int) => x * 2
val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000)
val newSalaries2 = salaries.map(x => x * 2) // List(40000, 140000, 80000),匿名函数
val newSalaries3 = salaries.map(_ * 2) // List(40000, 140000, 80000),编译器自动识别
private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
salaries.map(promotionFunction)
def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
val schema = if (ssl) "https://" else "http://"
(endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
}
val domainName = "www.example.com"
def getURL = urlBuilder(ssl=true, domainName)
val endpoint = "users"
val query = "id=1"
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String
可以传入一个对象方法作为高阶函数的参数,这是因为Scala编译器会将方法强制转换为一个函数。
case class WeeklyWeatherForecast(temperatures: Seq[Double]) {
private def convertCtoF(temp: Double) = temp * 1.8 + 32
def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // 编译时会转换成x => convertCtoF(x)
}
多参数列表(柯里化Currying)
方法可以定义多个参数列表,当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化。
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val numberFunc = numbers.foldLeft(List[Int]())_
val squares = numberFunc((xs, x) => xs:+ x*x)
print(squares.toString()) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
val cubes = numberFunc((xs, x) => xs:+ x*x*x)
print(cubes.toString()) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000)
模式匹配
模式匹配是检查某个值(value)是否匹配某一个模式的机制,一个成功的匹配同时会将匹配值解构为其组成部分。它是Java中的switch语句的升级版
为了让匹配更加具体,可以使用模式守卫,也就是在模式后面加上if <boolean expression>
val x: Int = Random.nextInt(10)
x match {
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"
}
案例类(case classes)的匹配
abstract class Notification
case class Email(sender: String, title: String, body: String) extends Notification
case class SMS(caller: String, message: String) extends Notification
case class VoiceRecording(contactName: String, link: String) extends Notification
def showNotification(notification: Notification): String = {
notification match {
case Email(sender, title, _) =>
s"You got an email from $sender with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
s"you received a Voice Recording from $name! Click the link to hear it: $link"
}
}
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
notification match {
case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
"You got an email from special someone!"
case SMS(number, _) if importantPeopleInfo.contains(number) =>
"You got an SMS from special someone!"
case other =>
showNotification(other) // nothing special, delegate to our original showNotification function
}
}
元组的匹配
val planetDistanceFromSun = List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6 ), ("Mars", 227.9), ("Jupiter", 778.3))
planetDistanceFromSun.foreach{ tuple => {
tuple match {
case ("Mercury", distance) => println(s"Mercury is $distance millions km far from Sun")
case p if(p._1 == "Venus") => println(s"Venus is ${p._2} millions km far from Sun")
case p if(p._1 == "Earth") => println(s"Blue planet is ${p._2} millions km far from Sun")
case _ => println("Too far....")
}
}
}
仅匹配类型
特质(trait)和类(class)可以用sealed标记为密封的,这意味着其所有子类都必须与之定义在相同文件中,从而保证所有子类型都是已知的。
sealed abstract class Device
case class Phone(model: String) extends Device {
def screenOff = "Turning screen off"
}
case class Computer(model: String) extends Device {
def screenSaverOn = "Turning screen saver on..."
}
def goIdle(device: Device) = device match {
case p: Phone => p.screenOff
case c: Computer => c.screenSaverOn
}
正则表达式模式
.r方法可使任意字符串变成一个正则表达式。
import scala.util.matching.Regex
val numberPattern: Regex = "[0-9]".r
numberPattern.findFirstMatchIn("awesomepassword") match {
case Some(_) => println("Password OK")
case None => println("Password must contain a number")
}
val keyValPattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r
val input: String =
"""background-color: #A03300;
|background-image: url(img/header100.png);
|background-position: top center;
|background-repeat: repeat-x;
|background-size: 2160px 108px;
|margin: 0;
|height: 108px;
|width: 100%;""".stripMargin
for (patternMatch <- keyValPattern.findAllMatchIn(input))
println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}")
型变
型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。
Scala支持 泛型类 的类型参数的型变注释,允许它们是协变的,逆变的,或在没有使用注释的情况下是不变的。
在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。
协变(covariant)
使用注释 +A,可以使一个泛型类的类型参数 A 成为协变。
对于某些类 class List[+A],使 A 成为协变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 List[A] 就是 List[B] 的子类型。
abstract class Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
object CovarianceTest extends App {
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach { animal =>
println(animal.name)
}
}
val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
// Whiskers
// Tom
printAnimalNames(dogs)
// Fido
// Rex
}
逆变(contravariant)
通过使用注释 -A,可以使一个泛型类的类型参数 A 成为逆变。
对于某个类 class Writer[-A] ,使 A 逆变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 Writer[B] 是 Writer[A] 的子类型。
abstract class Printer[-A] {
def print(value: A): Unit
}
class AnimalPrinter extends Printer[Animal] {
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat] {
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}
object ContravarianceTest extends App {
val myCat: Cat = Cat("Boots")
def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
}
val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter
printMyCat(catPrinter)
printMyCat(animalPrinter)
}
不变(invariant)
默认情况下,Scala中的泛型类是不变的。 这意味着它们既不是协变的也不是逆变的。
类型上界
T <: A这样声明的类型上界表示类型变量T应该是类型A的子类
类型下界
B >: A 表示类型参数 B 或抽象类型 B 是类型 A 的超类型
trait Node[+B] {
def prepend[U >: B](elem: U): Node[U]
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
}
case class Nil[+B]() extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
}
隐式参数
方法可以具有 隐式 参数列表,由参数列表开头的 implicit 关键字标记。
如果参数列表中的参数没有像往常一样传递, Scala 将查看它是否可以获得正确类型的隐式值,如果可以,则自动传递。
Scala 在调用包含有隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数 (无前缀)。
然后,它在所有伴生对象中查找与隐式候选类型相关的有隐式标记的成员。
abstract class Monoid[A] {
def add(x: A, y: A): A
def unit: A
}
object ImplicitTest {
implicit val stringMonoid: Monoid[String] = new Monoid[String] {
def add(x: String, y: String): String = x concat y
def unit: String = ""
}
implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
if (xs.isEmpty) m.unit
else m.add(xs.head, sum(xs.tail))
def main(args: Array[String]): Unit = {
println(sum(List(1, 2, 3))) // uses IntMonoid implicitly
println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
}
}
隐式转换
一个从类型 S 到类型 T 的隐式转换由一个函数类型 S => T 的隐式值来定义,或者由一个可转换成所需值的隐式方法来定义
隐式转换在两种情况下会用到:
如果一个表达式 e 的类型为 S, 并且类型 S 不符合表达式的期望类型 T。
在一个类型为 S 的实例对象 e 中调用 e.m, 如果被调用的 m 并没有在类型 S 中声明。
如果一个隐式方法 List[A] => Ordered[List[A]],以及一个隐式方法 Int => Ordered[Int] 在上下文范围内,那么对下面两个类型为 List[Int] 的列表的操作是合法的:
List(1, 2, 3) <= List(4, 5)
当调用一个接受 java.lang.Integer 作为参数的 Java 方法时,你完全可以传入一个 scala.Int。那是因为 Predef 包含了以下的隐式转换:
import scala.language.implicitConversions
implicit def int2Integer(x: Int) =
java.lang.Integer.valueOf(x)
编译器会在编译隐式转换定义时发出警告,要关闭警告,执行以下任一操作:
将 scala.language.implicitConversions 导入到隐式转换定义的上下文范围内
启用编译器选项 -language:implicitConversions
多态方法
Scala 中的方法可以按类型和值进行参数化。 语法和泛型类类似。 类型参数括在方括号中,而值参数括在圆括号中。
def listOfDuplicates[A](x: A, length: Int): List[A] = {
if (length < 1)
Nil
else
x :: listOfDuplicates(x, length - 1)
}
println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3)
println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La)
类型推断
Scala 编译器通常可以推断出表达式的类型,因此你不必显式地声明它。
对于递归方法,编译器无法推断出结果类型。
通常认为,公开可访问的 API 成员应该具有显示类型声明以增加可读性。 因此,我们建议你将代码中向用户公开的任何 API 明确指定类型。
val businessName = "Montreux Jazz Café" // String 类型
def squareOf(x: Int) = x * x // Int类型
def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) // 编译失败
case class MyPair[A, B](x: A, y: B)
val p = MyPair(1, "scala") // 类型: MyPair[Int, String]
def id[T](x: T) = x
val q = id(1) // 类型: Int
编译器从不推断方法形式参数的类型。 但是,在某些情况下,当函数作为参数传递时,编译器可以推断出匿名函数形式参数的类型。
Seq(1, 3, 4).map(x => x * 2) // List(2, 6, 8)
此外,类型推断有时会推断出太具体的类型。 假设我们这么写:
var obj = null // 因为 obj 推断出的类型是 Null,由于该类型的唯一值是 null,因此无法分配其他的值。
运算符
在Scala中,运算符即是方法。 任何具有单个参数的方法都可以用作 中缀运算符。
你可以使用任何合法标识符作为运算符。 包括像 add 这样的名字或像 + 这样的符号。
case class Vec(x: Double, y: Double) {
def +(that: Vec) = Vec(this.x + that.x, this.y + that.y)
}
val vector1 = Vec(1.0, 1.0)
val vector2 = Vec(2.0, 2.0)
val vector3 = vector1 + vector2
vector3.x // 3.0
vector3.y // 3.0
case class MyBool(x: Boolean) {
def and(that: MyBool): MyBool = if (x) that else this
def or(that: MyBool): MyBool = if (x) this else that
def negate: MyBool = MyBool(!x)
}
def not(x: MyBool) = x.negate
def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y)
当一个表达式使用多个运算符时,将根据运算符的第一个字符来评估优先级:
(characters not shown below)
* / %
+ -
:
= !
< >
&
^
|
(all letters, $, _)
a + b ^? c ?^ d less a ==> b | c
等价于
((a + b) ^? (c ?^ d)) less ((a ==> b) | c)
?^ 具有最高优先级,因为它以字符 ? 开头。 + 具有第二高的优先级,然后依次是 ==>, ^?, |, 和 less。
传名参数
传名参数 仅在被使用时触发实际参数的求值运算。 它们与 传值参数 正好相反。 要将一个参数变为传名参数,只需在它的类型前加上 =>
传名参数的优点是,如果它们在函数体中未被使用,则不会对它们进行求值。
另一方面,传值参数的优点是它们仅被计算一次。
如果参数是计算密集型或长时间运行的代码块,如获取 URL,这种延迟计算参数直到它被使用时才计算的能力可以帮助提高性能。
def whileLoop(condition: => Boolean)(body: => Unit): Unit =
if (condition) {
body
whileLoop(condition)(body)
}
var i = 2
whileLoop (i > 0) {
println(i)
i -= 1
} // prints 2 1
注解
注解将元信息与定义相关联。 例如,方法之前的注解 @deprecated 会导致编译器在该方法被使用时打印警告信息。
确保编码正确性的注解
如果不满足条件,某些注解实际上会导致编译失败。 例如,注解 @tailrec 确保方法是 尾递归。 尾递归可以保持内存需求不变。
当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。
尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
import scala.annotation.tailrec
def factorial(x: Int): Int = {
@tailrec
def factorialHelper(x: Int, accumulator: Int): Int = {
if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x)
}
factorialHelper(x, 1)
}
影响代码生成的注解
像 @inline 这样的注解会影响生成的代码(即你的 jar 文件可能与你没有使用注解时有不同的字节)。
内联表示在调用点插入被调用方法体中的代码。 生成的字节码更长,但有希望能运行得更快。
使用注解 @inline 并不能确保方法内联,当且仅当满足某些生成代码大小的启发式算法时,它才会触发编译器执行此操作。
Java 注解
在编写与 Java 互操作的 Scala 代码时,注解语法中存在一些差异需要注意。
注意: 确保你在开启 -target:jvm-1.8 选项时使用 Java 注解。
@interface Source {
public String URL();
public String mail();
}
@Source(URL = "https://coders.com/",
mail = "support@coders.com")
public class MyClass extends HisClass ...
@Source(URL = "https://coders.com/",
mail = "support@coders.com")
class MyScalaClass ...
@interface SourceURL {
public String value();
public String mail() default "";
}
@SourceURL("https://coders.com/")
public class MyClass extends HisClass ...
@SourceURL("https://coders.com/")
class MyScalaClass ...
@SourceURL(value = "https://coders.com/",
mail = "support@coders.com")
public class MyClass extends HisClass ...
@SourceURL("https://coders.com/",
mail = "support@coders.com")
class MyScalaClass ...
包和导入
Scala 使用包来创建命名空间,从而允许你创建模块化程序
一个惯例是将包命名为与包含 Scala 文件的目录名相同。 但是,Scala 并未对文件布局作任何限制。
包名称应全部为小写,如果代码是在拥有独立网站的组织内开发的,则应采用以下的约定格式:<top-level-domain>.<domain-name>.<project-name>。
import 语句用于导入其他包中的成员(类,特质,函数等)。 使用相同包的成员不需要 import 语句。 导入语句可以有选择性。
Scala 不同于 Java 的一点是 Scala 可以在任何地方使用导入
包 scala 和 java.lang 以及 object Predef 是默认导入的
package users
class User
package users {
package administrators {
class NormalUser
}
package normalusers {
class NormalUser
}
}
import users._ // 导入包 users 中的所有成员
import users.User // 导入类 User
import users.{User, UserPreferences} // 仅导入选择的成员
import users.{UserPreferences => UPrefs} // 导入类并且设置别名
如果存在命名冲突并且你需要从项目的根目录导入,请在包名称前加上 _root_:
package accounts
import _root_.users._
包对象
Scala 提供包对象作为在整个包中方便的共享使用的容器。
包对象中可以定义任何内容,而不仅仅是变量和方法。 例如,包对象经常用于保存包级作用域的类型别名和隐式转换。 包对象甚至可以继承 Scala 的类和特质。
按照惯例,包对象的代码通常放在名为 package.scala 的源文件中。
每个包都允许有一个包对象。 在包对象中的任何定义都被认为是包自身的成员。
// in file gardening/fruits/Fruit.scala
package gardening.fruits
case class Fruit(name: String, color: String)
object Apple extends Fruit("Apple", "green")
object Plum extends Fruit("Plum", "blue")
object Banana extends Fruit("Banana", "yellow")
// in file gardening/fruits/package.scala
package gardening
package object fruits {
val planted = List(Apple, Plum, Banana)
def showFruit(fruit: Fruit): Unit = {
println(s"${fruit.name}s are ${fruit.color}")
}
}
// in file PrintPlanted.scala
import gardening.fruits._
object PrintPlanted {
def main(args: Array[String]): Unit = {
for (fruit <- planted) {
showFruit(fruit)
}
}
}
package object fruits extends FruitAliases with FruitHelpers {
// helpers and variables follows here
}