文章目录
- Scala包
- [Scala 关键字](#Scala 关键字)
- [| @ | |](#| @ | |)
- 变量定义
-
- 声明变量类型
- 几种内置类型
-
- BigInt和BigDecimal
- [String and Char](#String and Char)
- 控制结构
-
- match表达式
- try-catch-finally
- for循环和表达式
- [while do/while](#while do/while)
- Scala方法
- trait
- 集合类
- 表达式
-
- [for 表达式](#for 表达式)
- [match 表达式](#match 表达式)
-
- [Using a match expression as the body of a method](#Using a match expression as the body of a method)
- [Handling alternate cases](#Handling alternate cases)
- [Using if expressions in case statements](#Using if expressions in case statements)
- CLASS
-
- [Basic class constructor](#Basic class constructor)
- [val makes fields read-only](#val makes fields read-only)
-
- [Class constructors](#Class constructors)
- [Other Scala class examples](#Other Scala class examples)
- 辅助类构造函数
- 提供构造函数参数的默认值
- 枚举
- [trait 和 abstract class](#trait 和 abstract class)
-
- trait
-
- [using scala trait as interface](#using scala trait as interface)
- [using scala trait like abstract class](#using scala trait like abstract class)
- [abstract class](#abstract class)
- 函数
Scala包
scala包和java包的声明一样在源文件的第一行;
scala包的导入和java包有些不同,java导入包使用import关键字
scala导入包也是import但是有几种不同的写法:
-
导入某个类:和java一样
-
导入某个包下的所有的类
这个scala和java的写法有些不一样,java是
import java.util.*
;这种写法,但是scala不能这种写法而是import java.util._
; -
导入某个包下的某几个类
java需要一个类一个类去导入,scala可以这样些
scala
import java.uitl.{List,ArrayList}
import java.awt.{Color, Font}
// 重命名成员
import java.util.{HashMap => JavaHashMap}
// 隐藏成员
import java.util.{HashMap => _, _} // 引入了util包的所有成员,但是HashMap被隐藏了
包的定义:
Scala 使用 package
关键字定义包,在Scala将代码定义到某个包中有两种方式:
- 第一种方法和 Java 一样,在文件的头定义包名,这种方法就后续所有代码都放在该包中。 比如:
scala
package com.runoob
class HelloWorld{}
- 第二种方法有些类似 C#,如:
scala
package com.runoob {
class HelloWorld {}
}
第二种方法,可以在一个文件中定义多个包。
注意:默认情况下,Scala 总会引入 java.lang._ 、 scala._ 和 Predef._,这里也能解释,为什么以scala开头的包,在使用时都是省去scala.的。
Scala 关键字
abstract | case | catch | class |
---|---|---|---|
def | do | else | extends |
false | final | finally | for |
forSome | if | implicit | import |
lazy | match | new | null |
object | override | package | private |
protected | return | sealed | super |
this | throw | trait | try |
true | type | val | var |
while | with | yield |
- | : | = | =>
<- | <: |<% | >:
| @ | |
变量定义
数据类型
Scala与Java具有相同的数据类型,具有相同的内存占用和精度。以下是提供Scala中可用的所有数据类型的详细信息的表格:
序号 | 数据类型 | 说明 |
---|---|---|
1 | Byte | 8位有符号值,范围从-128至127 |
2 | Short | 16位有符号值,范围从-32768至32767 |
3 | Int | 32位有符号值,范围从-2147483648至2147483647 |
4 | Long | 64位有符号值,范围从-9223372036854775808至9223372036854775807 |
5 | Float | 32位IEEE 754单精度浮点值 |
6 | Double | 64位IEEE 754双精度浮点值 |
7 | Char | 16位无符号Unicode字符。范围从U+0000到U+FFFF |
8 | String | 一个Char类型序列 |
9 | Boolean | 文字值true或文字值false |
10 | Unit | 对应于无值 |
11 | Null | null或空引用 |
12 | Nothing | 每种其他类型的亚型; 不包括无值 |
13 | Any | 任何类型的超类型; 任何对象的类型为Any |
14 | AnyRef | 任何引用类型的超类型 |
上面列出的所有数据类型都是对象。
Scala中没有类似Java中那样的原始类型。这意味着您可以调用Int,Long等方法。
val 定义变量相当于java 的final不可变
声明变量类型
scala
val x = 1
val s = "a string"
val p = new Person("Regina")
或
scala
val x: Int = 1
val s: String = "a string"
val p: Person = new Person("Regina")
几种内置类型
Scala带有您期望的标准数字数据类型。在Scala中,所有这些数据类型都是成熟的对象(不是原始数据类型)。
这些示例说明如何声明基本数字类型的变量:
scala
val b: Byte = 1
val x: Int = 1
val l: Long = 1
val s: Short = 1
val d: Double = 2.0
val f: Float = 3.0
在第四个例子,如果你没有明确指定类型,数量1将默认为Int,所以如果你想在其他数据类型中的一种- Byte,Long或者Short-你需要显式声明的类型,如图所示。带小数的数字(如2.0)将默认为a Double,因此,如果需要a Float,则需要声明a Float,如最后一个示例所示。
BigInt和BigDecimal
For large numbers Scala also includes the types BigInt and BigDecimal:
scala
var b = BigInt(1234567890)
var b = BigDecimal(123456.789)
String and Char
Scala also has String
and Char
data types, which you can generally declare with the implicit form:
scala
val name = "Bill"
val c = 'a'
Scala字符串有很多不错的特性,但是我们想花点时间来强调两个特性,我们将在本书的其余部分中使用它们。第一个特性是Scala有一个很好的、类似Ruby的方法来合并多个字符串。考虑到这三个变量:
scala
val firstName = "John"
val mi = 'C'
val lastName = "Doe"
you can append them together like this, if you want to:
scala
val name = firstName + " " + mi + " " + lastName
However, Scala provides this more convenient form:
scala
val name = s"$firstName $mi $lastName"
如图所示,您只需在字符串前面加上字母s,然后在字符串中变量名前面加上$符号。此功能称为string interpolation
。
控制结构
与Java和许多其他语言不同,if / else构造返回一个值,因此,除其他外,您可以将其用作三元运算符:
scala
val x = if (a < b) a else b
match表达式
Scala有一个match表达式,其最基本的用法就像一个Java switch语句:
scala
val result = i match {
case 1 => "one"
case 2 => "two"
case _ => "not 1 or 2"
}
这match是用作方法主体并针对许多不同类型进行匹配的示例:
scala
def getClassAsString(x: Any):String = x match {
case s: String => s + " is a String"
case i: Int => "Int"
case f: Float => "Float"
case l: List[_] => "List"
case p: Person => "Person"
case _ => "Unknown"
}
try-catch-finally
Scala的try / catch控制结构使您可以捕获异常。它类似于Java,但是其语法与match表达式一致:
scala
try {
writeToFile(text)
} catch {
case fnfe: FileNotFoundException => println(fnfe)
case ioe: IOException => println(ioe)
}
try {
// your scala code here
}
catch {
case foo: FooException => handleFooException(foo)
case bar: BarException => handleBarException(bar)
case _: Throwable => println("Got some other kind of Throwable exception")
} finally {
// your scala code here, such as closing a database connection
// or file handle
}
for循环和表达式
Scala for循环-我们通常在本书中将其称为for循环 -如下所示:
scala
for (arg <- args) println(arg)
// "x to y" syntax
for (i <- 0 to 5) println(i)
// "x to y by" syntax
for (i <- 0 to 10 by 2) println(i)
您还可以将yield关键字添加到for循环中,以创建产生结果的for表达式。这是一个for表达式,它将序列1到5中的每个值加倍:
scala
val x = for (i <- 1 to 5) yield i * 2 //返回一个Vector
这是另一个针对字符串列表的for表达式:
scala
val fruits = List("apple", "banana", "lime", "orange")
val fruitLengths = for {
f <- fruits
if f.length > 4
} yield f.length
Using for and foreach with Maps
You can also use for
and foreach
when working with a Scala Map
(which is similar to a Java HashMap
). For example, given this Map
of movie names and ratings:
scala
val ratings = Map(
"Lady in the Water" -> 3.0,
"Snakes on a Plane" -> 4.0,
"You, Me and Dupree" -> 3.5
)
//for 循环
for ((name,rating) <- ratings) println(s"Movie: $name, Rating: $rating")
//或 for 表达式
ratings.foreach {
case(movie, rating) => println(s"key: $movie, value: $rating")
}
while do/while
scala
// while loop
while(condition) {
statement(a)
statement(b)
}
// do-while
do {
statement(a)
statement(b)
}
while(condition)
Scala方法
就像其他OOP语言一样,Scala类也具有方法,这就是Scala方法语法的样子:
scala
def sum(a: Int, b: Int): Int = a + b
def concatenate(s1: String, s2: String): String = s1 + s2
您不必声明方法的返回类型,因此,如果愿意,编写这两个方法是完全合法的:
scala
def sum(a: Int, b: Int) = a + b
def concatenate(s1: String, s2: String) = s1 + s2
trait
scala
class Dog(name: String) extends Speaker with TailWagger with Runner {
def speak(): String = "Woof!"
}
集合类
Scala集合的主要类别
类 | 描述 |
---|---|
ArrayBuffer | 索引的可变序列 |
List | 线性(链表),不可变序列 |
Vector | 索引不变的序列 |
Map | 基Map(键/值对)类 |
Set | 基Set类 |
Map和Set有可变版本和不可变版本。
ArrayBuffer
这是一个可变序列,因此您可以使用其方法来修改其内容,并且这些方法类似于Java序列上的方法。
要使用,ArrayBuffer您必须先将其导入:
import scala.collection.mutable.ArrayBuffer
val ints = ArrayBuffer[Int]()
val names = ArrayBuffer[String]()
ArrayBuffer就可以通过多种方式向其中添加元素
val ints = ArrayBuffer[Int]()
ints += 1
ints += 2
这只是创建ArrayBuffer和添加元素的一种方法。您还可以ArrayBuffer使用以下初始元素创建一个:
val nums = ArrayBuffer(1, 2, 3)
可以通过以下几种方法向其中添加更多元素ArrayBuffer:
// add one element
nums += 4
// add multiple elements
nums += 5 += 6
// add multiple elements from another collection
nums ++= List(7, 8, 9)
可以ArrayBuffer使用-=和--=方法从中删除元素:
// remove one element
nums -= 9
// remove multiple elements
nums -= 7 -= 8
// remove multiple elements using another collection
nums --= Array(5, 6)
其他方法
val a = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3)
a.append(4) // ArrayBuffer(1, 2, 3, 4)
a.append(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6)
a.appendAll(Seq(7,8)) // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8)
a.clear // ArrayBuffer()
val a = ArrayBuffer(9, 10) // ArrayBuffer(9, 10)
a.insert(0, 8) // ArrayBuffer(8, 9, 10)
a.insertAll(0, Vector(4, 5, 6, 7)) // ArrayBuffer(4, 5, 6, 7, 8, 9, 10)
a.prepend(3) // ArrayBuffer(3, 4, 5, 6, 7, 8, 9, 10)
a.prepend(1, 2) // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
a.prependAll(Array(0)) // ArrayBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g)
a.remove(0) // ArrayBuffer(b, c, d, e, f, g)
a.remove(2, 3) // ArrayBuffer(b, c, g)
val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g)
a.trimStart(2) // ArrayBuffer(c, d, e, f, g)
a.trimEnd(2) // ArrayBuffer(c, d, e)
List
List类是线性的,不可变的序列。这意味着它是一个您无法修改的链表。每当您想添加或删除List元素时,都可以List从一个现存的中创建一个新元素List。
列表赋值
scala
val ints = List(1, 2, 3)
val names = List("Joel", "Chris", "Ed")
//也可以根据需要声明List的类型,尽管通常不必这样做:
val ints: List[Int] = List(1, 2, 3)
val names: List[String] = List("Joel", "Chris", "Ed")
val nums = List.range(0, 10)
val nums = (1 to 10 by 2).toList
val letters = ('a' to 'f').toList
val letters = ('a' to 'f' by 2).toList
Adding elements to a List
因为List是不可变的,所以不能向其中添加新元素。相反,您可以通过在现有元素之前或之后添加元素来创建新列表List。例如,鉴于此List:
val a = List(1,2,3)
//可以像这样将元素放在前面List:
val b = 0 +: a
val e = List(-1, 0) ++: a
访问元素
scala> var c = List(1,2,3,6,7)
c: List[Int] = List(1, 2, 3, 6, 7)
scala> c(1)
res0: Int = 2
scala> c(4)
res1: Int = 7
现在,IDE帮了我们很大的忙,但是记住这些方法名的一种方法是认为:字符表示序列所在的一侧,所以当您使用+:时,您知道列表需要在右侧,如下所示:
0+:a
同样,当您使用+时,您知道列表需要在左侧:
a:+4
因为List是一个链表类,所以不应该试图通过大列表的索引值来访问它们的元素。例如,如果列表中有一百万个元素,那么访问myList(999999)这样的元素将需要很长时间。如果要访问这样的元素,请改用Vector
或ArrayBuffer
。
you can also create the exact same list this way:
val list = 1 :: 2 :: 3 :: Nil
This works because a List is a singly-linked list that ends with the Nil
element.
Sequence methods
scala
val nums = (1 to 10).toList
val names = List("joel", "ed", "chris", "maurice")
这是foreach方法:
scala
scala> names.foreach(println)
joel
ed
chris
maurice
这是filter方法,然后是foreach:
scala
//这里的_代表每个元素
scala> nums.filter(_ < 4).foreach(println)
1
2
3
以下是该map方法的一些示例
scala
scala> val doubles = nums.map(_ * 2)
doubles: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
scala> val capNames = names.map(_.capitalize)
capNames: List[String] = List(Joel, Ed, Chris, Maurice)
scala> val lessThanFive = nums.map(_ < 5)
lessThanFive: List[Boolean] = List(true, true, true, true, false, false, false, false, false, false)
foldLeft:
scala
scala> nums.foldLeft(0)(_ + _)
res0: Int = 55
scala> nums.foldLeft(1)(_ * _)
res1: Int = 3628800
Vector
Vector类是一个索引的,不变的序列。
创建
可以通过以下几种方法创建Vector:
val nums = Vector(1, 2, 3, 4, 5)
val strings = Vector("one", "two")
val peeps = Vector(
Person("Bert"),
Person("Ernie"),
Person("Grover")
)
因为Vector是不可变的,所以不能向其中添加新元素。相反,您可以通过将元素添加或添加到现有之前创建新序列Vector。例如,鉴于此Vector
val a = Vector(1,2,3)
//您可以添加以下元素:
val b = a :+ 4
//还有这个:
val b = a ++ Vector(4, 5)
//也可以在前面加上这样的内容:
val b = 0 +: a
//还有这个:
val b = Vector(-1, 0) ++: a
Map
Scala具有可变和不变的Map类。这里,我们将展示如何使用可变的类。
创建
import scala.collection.mutable.Map
val states = Map(
"AK" -> "Alaska",
"IL" -> "Illinois",
"KY" -> "Kentucky"
)
添加元素
states += ("AL" -> "Alabama")
states += ("AR" -> "Arkansas", "AZ" -> "Arizona")
states ++= Map("CA" -> "California", "CO" -> "Colorado")
删除元素
tates -= "AR"
states -= ("AL", "AZ")
states --= List("AL", "AZ")
更新地图元素
states("AK") = "Alaska, A Really Big State"
遍历地图
val ratings = Map(
"Lady in the Water"-> 3.0,
"Snakes on a Plane"-> 4.0,
"You, Me and Dupree"-> 3.5
)
//for循环语法
for ((k,v) <- ratings) println(s"key: $k, value: $v")
//将match表达式与foreach方法一起使用
ratings.foreach {
case(movie, rating) => println(s"key: $movie, value: $rating")
}
Set
Scala具有可变和不变的Set类。在本课程中,我们将展示如何使用可变的类。
添加元素
添加到一个可变Set用+=,++=和add方法。
set += 1
set += 2 += 3
set ++= Vector(4, 5)
删除元素
以使用-=和--=方法从集合中删除元素
set -= 1
set -= (2, 3)
set --= Array(4,5)
还有更多使用集合的方法,包括clear和remove:
Tuples
元组可让您将元素的异构集合放入一个小容器中。元组可以包含两个到22个值,并且它们都可以是不同的类型。
scala
val t = (11, "Eleven", new Person("Eleven"))
t._1
t._2
t._3
或将元组字段分配给变量:
scala
val (num, string, person) = (11, "Eleven", new Person("Eleven"))
表达式
for 表达式
首字母大写
scala
val names = List("adam", "david", "frank")
//yield 转换
val ucNames = for (name <- names) yield name.capitalize
Success! Each name in the new variable ucNames is capitalized.
yield关键字
scala
val doubledNums = for (n <- nums) yield n * 2
val ucNames = for (name <- names) yield name.capitalize
scala
val names = List("_adam", "_david", "_frank")
val capNames = for (name <- names) yield {
val nameWithoutUnderscore = name.drop(1)
val capName = nameWithoutUnderscore.capitalize
capName
}
match 表达式
Scala has a concept of a match expression. In the most simple case you can use a match expression like a Java switch statement:
scala
// i is an integer
i match {
case 1 => println("January")
case 2 => println("February")
case 3 => println("March")
case 4 => println("April")
case 5 => println("May")
case 6 => println("June")
case 7 => println("July")
case 8 => println("August")
case 9 => println("September")
case 10 => println("October")
case 11 => println("November")
case 12 => println("December")
// catch the default with a variable so you can print it
case _ => println("Invalid month")
}
match 表达式
scala
val monthName = i match {
case 1 => "January"
case 2 => "February"
case 3 => "March"
case 4 => "April"
case 5 => "May"
case 6 => "June"
case 7 => "July"
case 8 => "August"
case 9 => "September"
case 10 => "October"
case 11 => "November"
case 12 => "December"
case _ => "Invalid month"
}
Using a match expression as the body of a method
正常的函数
scala
def convertBooleanToStringMessage(bool: Boolean): String = {
if (bool) "true" else "false"
}
match表达式
scala
def convertBooleanToStringMessage(bool: Boolean): String = bool match {
case true => "you said true"
case false => "you said false"
}
Handling alternate cases
match表达式使您可以在单个case语句中处理多种情况。
为了说明这一点,假设您想像Perl编程语言那样对"布尔相等"进行求值:a 0或空白字符串求值为false,其他任何求值为true。这是您使用match表达式编写方法的方式,该表达式的计算结果为true和false:
example_1
def isTrue(a: Any) = a match {
case 0 | "" => false
case _ => true
}
example_2
val evenOrOdd = i match {
case 1 | 3 | 5 | 7 | 9 => println("odd")
case 2 | 4 | 6 | 8 | 10 => println("even")
case _ => println("some other number")
}
example_3
cmd match {
case "start" | "go" => println("starting")
case "stop" | "quit" | "exit" => println("stopping")
case _ => println("doing nothing")
}
Using if expressions in case statements
example_1
count match {
case 1 => println("one, a lonely number")
case x if x == 2 || x == 3 => println("two's company, three's a crowd")
case x if x > 3 => println("4+, that's a party")
case _ => println("i'm guessing your number is zero or less")
}
提高可读性可在if表达式添加括号
count match {
case 1 => println("one, a lonely number")
case x if (x == 2 || x == 3) => println("two's company, three's a crowd")
case x if (x > 3) => println("4+, that's a party")
case _ => println("i'm guessing your number is zero or less")
}
example_2
i match {
case a if 0 to 9 contains a => println("0-9 range: " + a)
case b if 10 to 19 contains b => println("10-19 range: " + b)
case c if 20 to 29 contains c => println("20-29 range: " + c)
case _ => println("Hmmm...")
}
example_3
stock match {
case x if (x.symbol == "XYZ" && x.price < 20) => buy(x)
case x if (x.symbol == "XYZ" && x.price > 50) => sell(x)
case x => doNothing(x)
}
CLASS
Basic class constructor
example_1
scala
//这是一个Scala类,其构造函数定义了两个参数,firstName和lastName:
class Person(var firstName: String, var lastName: String)
//有了这个定义,您就可以Person像这样创建新的实例:
val p = new Person("Bill", "Panner")
//在类构造函数中定义参数会自动在该类中创建字段,
//在本示例中,您可以像这样访问firstName和lastName字段:
println(p.firstName + " " + p.lastName)
在此示例中,由于两个字段都被定义为var字段,因此它们也是可变的,这意味着可以更改它们。这是您如何更改它们:
scala> p.firstName = "William"
p.firstName: String = William
scala> p.lastName = "Bernheim"
p.lastName: String = Bernheim
val makes fields read-only
在第一个示例中,两个字段都被定义为var
字段:
scala
class Person(var firstName: String, var lastName: String)
这使得这些字段可变。您还可以将它们定义为val
字段,这使它们不可变:
scala
class Person(val firstName: String, val lastName: String)
Class constructors
在Scala中,类的主构造函数是以下各项的组合:
- 构造函数参数
- 在类主体中调用的方法
- 在类主体中执行的语句和表达式
Other Scala class examples
scala
class Pizza (var crustSize: Int, var crustType: String)
// a stock, like AAPL or GOOG
class Stock(var symbol: String, var price: BigDecimal)
// a network socket
class Socket(val timeout: Int, val linger: Int) {
override def toString = s"timeout: $timeout, linger: $linger"
}
class Address (
var street1: String,
var street2: String,
var city: String,
var state: String
)
辅助类构造函数
您可以通过定义名为this
的方法来定义辅助类构造函数:
- 每个辅助构造函数必须具有不同的签名(不同的参数列表)
- 每个构造函数都必须调用先前定义的构造函数之一
Here's an example of a Pizza class that defines multiple constructors:
scala
val DefaultCrustSize = 12
val DefaultCrustType = "THIN"
// the primary constructor
class Pizza (var crustSize: Int, var crustType: String) {
// one-arg auxiliary constructor
def this(crustSize: Int) {
this(crustSize, DefaultCrustType)
}
// one-arg auxiliary constructor
def this(crustType: String) {
this(DefaultCrustSize, crustType)
}
// zero-arg auxiliary constructor
def this() {
this(DefaultCrustSize, DefaultCrustType)
}
override def toString = s"A $crustSize inch pizza with a $crustType crust"
}
val p1 = new Pizza(DefaultCrustSize, DefaultCrustType)
val p2 = new Pizza(DefaultCrustSize)
val p3 = new Pizza(DefaultCrustType)
val p4 = new Pizza
关于此示例,有两个重要说明:
- 在DefaultCrustSize和DefaultCrustType变量不处理这种情况的优选方式,但因为我们并没有给出如何处理枚举的是,我们用这种方式来让事情变得简单。
- 辅助类构造函数是一个很棒的功能,但是由于您可以将默认值用于构造函数参数,因此您不需要经常使用此功能。下一课演示如何使用这样的默认参数值构造函数
提供构造函数参数的默认值
Scala允许您提供构造函数参数的默认值。
example_1
class Socket(var timeout: Int, var linger: Int) {
override def toString = s"timeout: $timeout, linger: $linger"
}
class Socket(var timeout: Int = 2000, var linger: Int = 3000) {
override def toString = s"timeout: $timeout, linger: $linger"
}
通过提供参数的默认值,您现在可以Socket通过多种不同方式创建新的参数:
new Socket()
new Socket(1000)
new Socket(4000, 6000)
Named parameters(命名参数)
class Socket(var timeout: Int, var linger: Int) {
override def toString = s"timeout: $timeout, linger: $linger"
}
可以这样创建一个新的Socket:
val s = new Socket(timeout=2000, linger=3000)
该功能有时会派上用场,例如当所有类构造函数参数都具有相同的类型时(例如Int本示例中的参数)。例如,有些人发现此代码:
val s = new Socket(timeout=2000, linger=3000)
比以下代码更具可读性:
val s = new Socket(2000, 3000)
枚举
枚举是创建常量组的有用工具,这些常量组可以是星期几,一年中的月份,一副纸牌中的花色等,您可以使用一组相关的常量值。
sealed trait DayOfWeek
case object Sunday extends DayOfWeek
case object Monday extends DayOfWeek
case object Tuesday extends DayOfWeek
case object Wednesday extends DayOfWeek
case object Thursday extends DayOfWeek
case object Friday extends DayOfWeek
披萨相关的枚举
sealed trait Topping
case object Cheese extends Topping
case object Pepperoni extends Topping
case object Sausage extends Topping
case object Mushrooms extends Topping
case object Onions extends Topping
sealed trait CrustSize
case object SmallCrustSize extends CrustSize
case object MediumCrustSize extends CrustSize
case object LargeCrustSize extends CrustSize
sealed trait CrustType
case object RegularCrustType extends CrustType
case object ThinCrustType extends CrustType
case object ThickCrustType extends CrustType
这些枚举为处理披萨馅料,大小和类型提供了一种不错的方法
给定这些枚举,我们可以定义一个Pizza这样的类:
class Pizza (var crustSize: CrustSize = MediumCrustSize, var crustType: CrustType = RegularCrustType) {
// ArrayBuffer is a mutable sequence (list)
val toppings = scala.collection.mutable.ArrayBuffer[Topping]()
def addTopping(t: Topping): Unit = toppings += t
def removeTopping(t: Topping): Unit = toppings -= t
def removeAllToppings(): Unit = toppings.clear()
}
trait 和 abstract class
trait
using scala trait as interface
一种使用Scala的trait方法就像原始的Java一样interface,在其中您可以为某些功能定义所需的接口,但是您没有实现任何行为。
example_1
scala
trait TailWagger {
def startTail(): Unit
def stopTail(): Unit
}
等效与Java
java
public interface TailWagger {
public void startTail();
public void stopTail();
}
您可以编写一个扩展特征并实现如下方法的类:
class Dog extends TailWagger {
// the implemented methods
def startTail(): Unit = println("tail is wagging")
def stopTail(): Unit = println("tail is stopped")
}
请注意,无论哪种情况,都可以使用extends关键字创建一个扩展单个特征的类
extends和with用于从多个trait创建类:
using scala trait like abstract class
example_1
trait Pet {
def speak = println("Yo") // concrete implementation of a speak method
def comeToMaster(): Unit // abstract
}
class Dog(name: String) extends Pet {
def comeToMaster(): Unit = println("Woo-hoo, I'm coming!")
}
-
Overriding an implemented method
class Cat extends Pet {
// override 'speak'
override def speak(): Unit = println("meow")
def comeToMaster(): Unit = println("That's not gonna happen.")
}
-
混合有行为的多个trait
trait Speaker {
def speak(): String //abstract
}trait TailWagger {
def startTail(): Unit = println("tail is wagging")
def stopTail(): Unit = println("tail is stopped")
}trait Runner {
def startRunning(): Unit = println("I'm running")
def stopRunning(): Unit = println("Stopped running")
}
创建一个Dog扩展所有这些特征的类,同时为speak方法提供行为:
class Dog(name: String) extends Speaker with TailWagger with Runner {
def speak(): String = "Woof!"
}
class Cat extends Speaker with TailWagger with Runner {
def speak(): String = "Meow"
override def startRunning(): Unit = println("Yeah ... I don't run")
override def stopRunning(): Unit = println("No need to stop")
}
- 即时混合trait
使用具有具体方法的特征可以做的一件非常有趣的事情是将它们动态混合到类中。例如
trait TailWagger {
def startTail(): Unit = println("tail is wagging")
def stopTail(): Unit = println("tail is stopped")
}
trait Runner {
def startRunning(): Unit = println("I'm running")
def stopRunning(): Unit = println("Stopped running")
}
以在创建Dog实例时创建一个混合了这些特征的Dog实例
val d = new Dog("Fido") with TailWagger with Runner
此示例之所以有效,是因为定义了TailWagger和Runner特性中的所有方法(它们不是抽象的)。
abstract class
Scala还具有类似于Java的抽象类的抽象类的概念。但是,由于特征是如此强大,因此您几乎不需要使用抽象类。实际上,仅在以下情况下才需要使用抽象类:
- 要创建一个需要构造函数参数的基类
- Scala代码将从Java代码中调用
创建一个需要构造函数参数的基类
Scala trait不允许使用构造函数参数
// this won't compile
trait Animal(name: String)
因此,只要基本行为必须具有构造函数参数,就需要使用抽象类:
abstract class Animal(name: String)
注意,一个类只能扩展一个抽象类。
Scala代码将从Java代码中调用
因为Java对Scala traits
一无所知,如果要从Java代码调用Scala代码,则需要使用抽象类而不是特质。
Abstract class syntax
abstract class Pet (name: String) {
def speak(): Unit = println("Yo") // concrete implementation
def comeToMaster(): Unit // abstract method
}
class Dog(name: String) extends Pet(name) {
override def speak() = println("Woof")
def comeToMaster() = println("Here I come!")
}
注意如何name传递
所有这些代码都类似于Java,因此我们将不对其进行详细说明。需要注意的一件事是如何将name构造函数参数从Dog类构造函数传递给Pet构造函数:
class Dog(name: String) extends Pet(name) {
记住,Pet声明name为构造函数参数:
abstract class Pet (name: String) { ...
因此,此示例说明如何将构造函数参数从Dog类传递给Pet抽象类。您可以验证此代码是否适用:
abstract class Pet (name: String) {
def speak: Unit = println(s"My name is $name")
}
class Dog(name: String) extends Pet(name)
val d = new Dog("Fido")
d.speak
函数
匿名函数
val ints = List(1,2,3)
//要创建更大的列表时,还可以使用List的 range方法创建它们
val ints = List.range(1, 10)
example_1
//给出如下列表
val ints = List(1,2,3)
//每个元素加倍来创建新列表ints
val doubledInts = ints.map(_ * 2)
//其中
_ * 2 //此代码是匿名函数
//也可以这样写:
val doubledInts = ints.map((i: Int) => i * 2)
val doubledInts = ints.map(i => i * 2)
//等效于此Java代码
List<Integer> ints = new ArrayList<>(Arrays.asList(1, 2, 3));
// the `map` process
List<Integer> doubledInts = ints.stream()
.map(i -> i * 2)
.collect(Collectors.toList());
//等效
val doubledInts = for (i <- ints) yield i * 2
_
Scala中的字符是通配符。您会看到它在几个不同的地方使用过。在这种情况下,这是一种简短的说法:"列表中的元素," ints。
PURE FUNCTIONS(纯函数)
在函数式编程Simplified中,Alvin Alexander定义了一个纯函数,如下所示:
- 函数的输出仅取决于其输入变量
- 它不会改变任何隐藏状态
- 它没有任何"后门":它不会从外界读取数据(包括控制台,Web服务,数据库,文件等),也不会向外界写入数据
纯函数示例
正如您可能想象的那样,在给定纯函数定义的情况下,scala.math._包中的此类方法就是纯函数:
- abs
- ceil
- max
- min
这些Scala String方法也是纯函数:
- isEmpty
- length
- substring
在Scala集合类的很多方法也作为纯粹的功能,包括drop,filter,和map。
不纯函数的例子
相反,以下功能不纯,因为它们违反了定义。
foreach集合类上的方法是不纯的,因为它仅用于其副作用,例如打印到STDOUT。
通常,不纯函数会执行以下一项或多项操作:
- 读取隐藏的输入,即,它们访问未显式传递为输入参数的变量和数据
- 写隐藏的输出
- 突变给定的参数
- 与外界进行某种I / O
编写纯函数
只需使用Scala的方法语法编写纯函数。这是一个纯函数,它将给定的输入值加倍:
def double(i: Int): Int = i * 2
传递函数
Scala 可以将函数创建为变量,就像创建String和Int变量一样。
此功能有很多好处,其中最常见的是,它使您可以将函数作为参数传递给其他函数。在演示了map和filter方法时,您已经在本书的前面看到了:
val nums = (1 to 10).toList
val doubles = nums.map(_ * 2)
val lessThanFive = nums.filter(_ < 5)
匿名函数传递到map和中filter。
没有空值
函数式编程就像编写一系列代数方程式一样,并且由于在代数中不使用空值,因此在FP中不使用空值。这就提出了一个有趣的问题:在Java / OOP代码中通常使用空值的情况下,您该怎么办?
Scala的解决方案是使用诸如Option / Some / None
类之类的构造。在本课中,我们将介绍这些技术。
def toInt(s: String): Int = {
try {
Integer.parseInt(s.trim)
} catch {
case e: Exception => 0
}
}
此函数的思想是,如果字符串转换为整数,则返回转换后的Int,但如果转换失败,则返回0。这可能在某些方面是可以的,但它不是真的准确。例如,该方法可能接收到"0",但它也可能接收到"foo"或"bar"或无限数量的其他字符串。这就产生了一个真正的问题:如何知道方法何时真正收到"0",或者何时收到其他东西?答案是,用这种方法,没有办法知道。
Scala解决这个问题的方法是使用三个类,即Option、Some和None。Some类和None类是ABC的子类,因此解决方案如下:
-
声明toInt返回Option类型
-
如果toInt接收到一个可以转换为Int的字符串,则将该Int包装在Some中
-
如果toInt接收到一个无法转换的字符串,它将返回一个None
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
}