0%

Dawn Dew-light Collected at Dusk 4 Scala

undefined

Scala 类型

1.png

Scala中,所有的值都是类对象,而所有的类,包括值类型,都最终继承自一个统一的根类型Any。统一类型,是Scala的又一大特点。更特别的是,Scala中还定义了几个底层类(Bottom Class),比如Null和Nothing。

  1. Null是所有引用类型的子类型,而Nothing是所有类型的子类型。Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型,但是不能赋值给值类型。

  2. Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。

  3. Unit类型用来标识过程,也就是没有明确返回值的函数。 由此可见,Unit类似于Java里的void。Unit只有一个实例,(),这个实例也没有实质的意义。

Option Some None

设计目的:避免使用null

大多数语言都有一个特殊的关键字或者对象来表示一个对象引用的是“无”,在Java,它是null。在Java 里,null 是一个关键字,不是一个对象,所以对它调用任何方法都是非法的。但是这对语言设计者来说是一件令人疑惑的选择。为什么要在程序员希望返回一个对象的时候返回一个关键字呢?

Scala鼓励在变量和函数返回值可能不会引用任何值的时候使用Option类型。在没有值的时候,使用None,这是Option的一个子类。如果有值可以引用,就使用Some来包含这个值。Some也是Option的子类。
None被声明为一个对象,而不是一个类,因为我们只需要它的一个实例。这样,它多少有点像null关键字,但它却是一个实实在在的,有方法的对象。

Option类型的值通常作为Scala集合类型(List,Map等)操作的返回类型。比如Map的get方法:

1
2
3
4
5
6
7
8
scala> val capitals = Map("France"->"Paris", "Japan"->"Tokyo", "China"->"Beijing")
capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo, China -> Beijing)

scala> capitals get "France"
res0: Option[String] = Some(Paris)

scala> capitals get "North Pole"
res1: Option[String] = None

Option有两个子类别,Some和None。当程序回传Some的时候,代表这个函式成功地给了你一个String,而你可以透过get()函数拿到那个String,如果程序返回的是None,则代表没有字符串可以给你。

在返回None,也就是没有String给你的时候,如果你还硬要调用get()来取得 String 的话,Scala一样是会抛出一个NoSuchElementException异常给你的。

我们也可以选用另外一个方法,getOrElse。这个方法在这个Option是Some的实例时返回对应的值,而在是None的实例时返回传入的参数。换句话说,传入getOrElse的参数实际上是默认返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scala> capitals get "North Pole" get
warning: there was one feature warning; re-run with -feature for details
java.util.NoSuchElementException: None.get
at scala.None$.get(Option.scala:347)
at scala.None$.get(Option.scala:345)
... 33 elided

scala> capitals get "France" get
warning: there was one feature warning; re-run with -feature for details
res3: String = Paris

scala> (capitals get "North Pole") getOrElse "Oops"
res7: String = Oops

scala> capitals get "France" getOrElse "Oops"
res8: String = Paris

通过模式匹配分离可选值,如果匹配的值是Some的话,将Some里的值抽出赋给x变量:

1
2
3
4
def showCapital(x: Option[String]) = x match {
case Some(s) => s
case None => "?"
}

在Scala里Option[T]实际上是一个容器,就像数组或是List一样,你可以把他看成是一个可能有零到一个元素的List。
当你的Option里面有东西的时候,这个List的长度是1(也就是 Some),而当你的Option里没有东西的时候,它的长度是0(也就是 None)。

Tips4For 循环

如果我们把Option当成一般的List来用,并且用一个for循环来走访这个Option的时候,如果Option是None,那这个for循环里的程序代码自然不会执行,于是我们就达到了不用检查Option是否为None这件事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scala> val map1 = Map("key1" -> "value1")
map1: scala.collection.immutable.Map[String,String] = Map(key1 -> value1)

scala> val value1 = map1.get("key1")
value1: Option[String] = Some(value1)

scala> val value2 = map1.get("key2")
value2: Option[String] = None

scala> def printContentLength(x: Option[String]) {
| for (c <- x){
| println(c.length)
| }
| }
printContentLength: (x: Option[String])Unit

scala> printContentLength(value1)
6

scala> printContentLength(value2)

Tips4map操作

在函数式编程中有一个核心的概念之一是转换,所以大部份支持函数式编程语言,都支持一种叫map()的动作,这个动作是可以帮你把某个容器的内容,套上一些动作之后,变成另一个新的容器。
现在我们考虑如何用Option的map方法实现length: xxx的输出形式:

先算出 Option 容器内字符串的长度
然后在长度前面加上 “length: “ 字样
最后把容器走访一次,印出容器内的东西

1
2
3
4
5
scala> value1.map(_.length).map("length: " + _).foreach(println)
length: 6

scala> value1.map("length: " + _.length).foreach(println)
length: 6

透过这样「转换」的方法,我们一样可以达成想要的效果,而且同样不用去做「是否为 None」的判断。

Scala Java Map

Scala内部Map转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def main(args: Array[String]): Unit = {
val im: Map[Int, String] = scala.collection.immutable.Map(1->"one",2->"two") //不可变map
val imh: HashMap[Int, String] = scala.collection.immutable.HashMap(1->"one",2->"two") //不可变hashmap
val mu: mutable.Map[Int, String] = scala.collection.mutable.Map(1->"one",2->"two") //可变map
println(im,manOf(im))
println(imh,manOf(imh))
println(mu,manOf(mu))

// mutable.Map 2 immutable.Map
val mui: Map[Int, String] = mu.toMap
println(mui,manOf(mui))


// immutable.Map 2 mutable.Map
val immu: mutable.Map[Int, String] = scala.collection.mutable.Map(im.toSeq: _*)
println(immu,manOf(immu))

val imhmu: mutable.Map[Int, String] = scala.collection.mutable.Map(imh.toSeq: _*)
println(imhmu,manOf(imhmu))
}

结果:

1
2
3
4
5
6
7
8
(Map(1 -> one, 2 -> two),scala.collection.immutable.Map[Int, java.lang.String])
(Map(1 -> one, 2 -> two),scala.collection.immutable.HashMap[Int, java.lang.String])
(Map(2 -> two, 1 -> one),scala.collection.mutable.Map[Int, java.lang.String])

(Map(2 -> two, 1 -> one),scala.collection.immutable.Map[Int, java.lang.String])

(Map(2 -> two, 1 -> one),scala.collection.mutable.Map[Int, java.lang.String])
(Map(2 -> two, 1 -> one),scala.collection.mutable.Map[Int, java.lang.String])

Java中 把Java Map 转化成 Scala Map

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<String, String>() {
{
put("1", "2");
}
};

scala.collection.mutable.Map<String,String> mapTest = JavaConverters.mapAsScalaMapConverter(map).asScala();
Object objTest = Map$.MODULE$.<String,String>newBuilder().$plus$plus$eq(mapTest.toSeq());
Object resultTest = ((scala.collection.mutable.Builder) objTest).result();
scala.collection.immutable.Map<String,String> resultTest2 = (scala.collection.immutable.Map)resultTest;
System.out.println(resultTest2.keySet());
}

稍微有点鸡肋,代码看不懂,笑

Scala中的双向转换

Scala提供了大量的方法来隐式转换所有主要的Java和Scala容器类型。其中提供了如下的双向类型转换:

1
2
3
4
5
6
7
8
Iterator               <=>     java.util.Iterator
Iterator <=> java.util.Enumeration
Iterable <=> java.lang.Iterable
Iterable <=> java.util.Collection
mutable.Buffer <=> java.util.List
mutable.Set <=> java.util.Set
mutable.Map <=> java.util.Map
mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap

使用这些转换很简单,只需从JavaConverters对象中import它们即可。

1
2
scala> import collection.JavaConverters._
import collection.JavaConverters._

import之后,通过扩展方法 asScala 和 asJava 就可以在Scala容器和与之对应的Java容器之间进行隐式转换了

1
2
3
4
5
6
7
8
9
10
11
scala> import collection.mutable._
import collection.mutable._

scala> val jul: java.util.List[Int] = ArrayBuffer(1, 2, 3).asJava
jul: java.util.List[Int] = [1, 2, 3]

scala> val buf: Seq[Int] = jul.asScala
buf: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 3)

scala> val m: java.util.Map[String, Int] = HashMap("abc" -> 1, "hello" -> 2).asJava
m: java.util.Map[String,Int] = {abc=1, hello=2}

还有一些Scala容器类型可以转换成对应的Java类型,但是并没有将相应的Java类型转换成Scala类型的能力,它们是:

1
2
3
4
Seq           =>    java.util.List
mutable.Seq => java.util.List
Set => java.util.Set
Map => java.util.Map

因为Java并未区分可变容器不可变容器类型,所以,虽然能将scala.immutable.List转换成java.util.List,但所有的修改操作都会抛出“UnsupportedOperationException”。参见下例:

1
2
3
4
5
6
scala> val jul = List(1, 2, 3).asJava
jul: java.util.List[Int] = [1, 2, 3]

scala> jul.add(7)
java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)

Example

完整的Java Map => Scala mutbale Map => Scala Immutable Map => Option => 模式匹配得到结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  // Java Map => mutable.Map => Scala Map
import scala.collection.JavaConversions._
val javaMap: util.Map[String, String] = new util.HashMap[String, String]()
javaMap.put("1","2")
for (m <- javaMap) {
println(m)
}

val scalaMuMap: mutable.Map[String, String] = javaMap.asScala
// Predef里定义的map类型是一个类型别名,实际类型是immutable.Map
val scalaImmuMap: Predef.Map[String, String] = scalaMuMap.toMap
val maybeString: Option[String] = scalaImmuMap.get("1")

val show = (x: Option[String]) => x match {
case Some(str) => str
case None => "?"
}

val str: String = show(maybeString)

println(str)
}

Scala mutable Map Test

1
2
3
4
5
6
7
8
9
// 测试可变Map返回值类型 确认是Option
val muMapTest: mutable.Map[String, Int] = mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)
muMapTest("Alice") = 99
println(muMapTest("Alice"))
muMapTest += ("Bob" -> 98)
println(muMapTest) // Map(Bob -> 98, Kotlin -> 30, Alice -> 99)
muMapTest -= ("Alice", "Kotlin")
println(muMapTest) // Map(Bob -> 98)
println(muMapTest.get("Bob")) // Some(98)

More 4 Scala Map Action

map1 ++ map2 (类似于Java Map的 putAll)

Scala 的 Seq

Scala的 Seq将是Java的List,Scala 的 List将是Java的 LinkedList。

Seq是一个trait,它相当于Java的接口,但相当于即将到来的防御者方法。

Scala的List是一个抽象类,由Nil和::扩展,这是List的具体实现。

所以,在Java的List是一个接口,Scala的List是一个实现。

除此之外,Scala的List是不可变的,这不是LinkedList的情况。事实上,Java没有等价的不可变集合(只读的东西只保证新的对象不能改变,但你仍然可以改变旧的,因此,“只读”一个)。
Scala的List是由编译器和库高度优化的,它是函数式编程中的基本数据类型。然而,它有限制,它不足以并行编程。这些天,Vector是一个比List更好的选择,但习惯是很难打破。
Seq是一个很好的泛化序列,所以如果你编程到接口,你应该使用它。注意,实际上有三个:collection.Seq,collection.mutable.Seq和collection.immutable.Seq,它是后一个是“默认”导入到范围。
还有GenSeq和ParSeq。后面的方法在可能的情况下并行运行,前者是Seq和ParSeq的父代,这是当代码的并行性无关紧要的合适的泛化。它们都是相对新引入的,因此人们不会使用它们。

Scala match

基本介绍

模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时, 会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下 一个分支进行判断。如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句。

Tips

  1. 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句

  2. 如果所有 case 都不匹配,又没有写 case _ 分支,那么会抛出 MatchError

  3. 每个 case 中,不用 break 语句,自动中断 case

  4. 可以在 match 中使用其它类型,而不仅仅是字符

  5. => 等价于 java swtich 的 :

  6. => 后面的代码块到下一个 case, 是作为一个整体执行,可以使用{} 扩起来,也可以不扩

守卫

如果想要表达匹配某个范围的数据,那么就需要在模式匹配中增加守卫条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object MatchIfDemo01 {
def main(args: Array[String]): Unit = {
for (ch <- "+-3!") { //是对"+-3!" 遍历
var sign = 0
var digit = 0
ch match {
case '+' if ch.toString.equals("3") => sign = 1
case '-' => sign = -1
// 说明..
// 如果 case 后有 条件守卫即 if ,那么这时的 _ 不是表示默认匹配
// 表示忽略 传入 的 ch
case _ if ch.toString.equals("3") => digit = 3
case _ if (ch > 1110 || ch < 120) => println("ch > 10")
case _ => sign = 2
}
println(ch + " " + sign + " " + digit)
}
}
}

上面这段代码值得一提的是ch是‘+’的时候,第一次匹配会匹配上 第一个判断,因为没有匹配上,所以接下来会匹配第三个,没有匹配上,匹配第四个,匹配上了,直接跳出。

变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
val ch = 'U'
ch match {
case '+' => println("ok~")
// 下面 case mychar 含义是 mychar = ch 下面这个case就啥都可以匹配上.这样不好
case mychar => println("ok~" + mychar)
case _ => println ("ok~~")
}
val ch1 = '+'
//match 是一个表达式,因此可以有返回值
//返回值就是匹配到的代码块的最后一句话的值
val res1 = ch1 match {
case '+' => {
print("使用这种方式,case里的最后一行就会返回结果给变量res,这种方式好,")
ch1 + " hello "
}
// 下面 case mychar 含义是 mychar = ch
case _ => println ("ok~~")
}
println("res=" + res1)

类型匹配

可以匹配 对象的任意类型,这样做避免了使用 isInstanceOf 和 asInstanceOf 方法

类型匹配注意事项

  1. Map[String, Int] 和 Map[Int, String]是两种不同的类型,其它类推。

  2. 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错.

1
2
3
4
5
6
7
val obj = 10
obj match {
case a:Int => a
// 编译报错
// case b:Map[String, Int] => "map集合"
case _ => "啥也不是"
}
  1. 如果 case _ 出现在 match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
val a = 1
//说明 obj 实例的类型 根据 a 的值来返回
val obj1: Any = if (a == 1) 1
else if (a == 2) "2"
else if (a == 3) BigInt(3)
else if (a == 4) Map("aa" -> 1)
else if (a == 5) Map(1 -> "aa")
else if (a == 6)Array(1, 2, 3)
else if (a == 7)Array("aa", 1)
else if (a == 8)Array("aa")
//说明
//1. 根据 obj 的类型来匹配
// 返回值
val result = obj1 match {
case a: Int => a
case b: Map[String, Int] => "对象是一个字符串-数字的 Map 集合"
case c: Map[Int, String] => "对象是一个数字-字符串的 Map 集合"
case d: Array[String] => d //"对象是一个字符串数组"
case e: Array[Int] => "对象是一个数字数组"
case _: BigInt => Int.MaxValue // 隐藏了变量名
case _ => "啥也不是"
}
println(result)

匹配数组

  1. Array(0) 匹配只有一个元素且为 0 的数组。
  2. Array(x,y) 匹配数组有两个元素,并将两个元素赋值为 x 和 y。当然可以依次类推 Array(x,y,z) 匹配数组有 3 个元素的等等….
  3. Array(0,_*) 匹配数组以 0 开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
val arrs: Array[Array[Int]] =Array(Array(0), Array(1, 0), Array(0, 1, 0),
Array(1, 1, 0), Array(1, 1, 0, 1))

for (arr <- arrs ) {
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + "=" + y
case Array(0, _*) => "以 0 开头和数组"
case _ => "什么集合都不是"
}
// result = 0
// result = 1 = 0
// result = 以 0 开头和数组
// result = 什么集合都不是
// result = 什么集合都不是
println("result = " + result)
}
//给你一个数组集合,如果该数组时 Array(10,20) , 请使用默认匹配,返回 Array(20,10)
import scala.collection.mutable.ArrayBuffer
val arrs2 = Array(Array(0),Array(1, 0), Array(0, 1, 0),
Array(1, 1, 0), Array(1, 1, 0, 1))
for (arr <- arrs2 ) {
val result = arr match {
//caseArray(0) => "0"
case Array(x,y)=>ArrayBuffer(y,x) //?ArrayB(y,x)
//caseArray(0, _*) => "以 0 开头和数组"
case _ => "不处理~~"
}
println(result)
}

匹配列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    //  Nil 表示空列表  0 :: 连接上一个空列表 表示List中只有0
// println(s"x : $x y: $y ") 拼接字符串,必须以s 开头, $ 取值
for (list <-Array(List(0), List(1, 0), List(88), List(0, 0, 0), List(1, 0, 0))) {
val result = list match {
case 0 :: Nil => "0" //
case x :: y :: Nil => x + " " + y //
case 0 :: tail => "0 ..." //
case x :: Nil => x
case _ => "something else"
}
// 0
// 1 0
// 88
// 0 ...
// something else
println(result)

println("==================================")

val site: immutable.Seq[String] = "Runoob" :: ("Google" :: ("Baidu" :: Nil))
val site2 = site
site.foreach(println)
val nums = 1 :: (2 :: (3 :: (4 :: Nil)))
val nums2: List[Int] = nums
nums.foreach(println)

// 二维列表
val dim = (1 :: (0 :: (0 :: Nil))) ::
(0 :: (1 :: (0 :: Nil))) ::
(0 :: (0 :: (1 :: Nil))) :: Nil

val dim2: List[List[Int]] = dim
dim.foreach(println)
}

补充一部分列表操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val reslist0: List[String] = "A"::"B"::Nil  // List(A, B)
val reslist1: List[String] = "C"+:"D"+:Nil // List(C, D)
val reslist2: List[String] = Nil:+"E":+"F" // List(E, F)
println(reslist0)
println(reslist1)
println(reslist2)

val reslist3 = reslist0 ++ reslist1 // List(A, B, C, D)
println(reslist3)

val reslist4 = reslist0 ::: reslist1 // List(A, B, C, D)
println(reslist4)

val reslist5 = reslist0 :: reslist1 // List(List(A, B), C, D)
println(reslist5)
  • :: 该方法被称为cons,意为构造,向队列的头部追加数据,创造新的列表。用法为 x::list,其中x为加入到头部的元素,无论x是列表与否,它都只将成为新生成列表的第一个元素,也就是说新生成的列表长度为list的长度+1(btw, x::list等价于list.::(x))
  • :++: 两者的区别在于:+方法用于在尾部追加元素,+:方法用于在头部追加元素,和::很类似,但是::可以用于pattern match ,而+:则不行. 关于+::+,只要记住冒号永远靠近集合类型就OK了。
  • ++ 该方法用于连接两个集合,list1++list2
  • ::: 该方法只能用于连接两个List类型的集合

匹配元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    for (pair <- Array((0, 1), (1, 0), (10, 30), (1, 1), (1, 0, 2))) {
val result = pair match { //
case (0, _) => "0 ..." //
case (y, 0) => y //
case (x, y) => (y, x) //"匹配到(x,y)" + x + " " + y
case _ => "other" //.
}
// 0 ...
// 1
// (30,10)
// (1,1)
// other
println(result)
}

对象匹配

基本介绍 对象匹配,什么才算是匹配呢?,规则如下: 1) case 中对象的 unapply 方法(对象提取器)返回 Some 集合则为匹配成功 2) 返回 None 集合则为匹配失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  // 对象匹配
val number: Double = Square(5.0)
number match {
case Square(n) => println("匹配成功 n=" + n)
case _ => println("nothing matched")
}

Object Square{
// 说明
// unapply 是对象提取器
// 接受 z:Double 类型
// 返回类型是 Optionp[Double]
// 返回值是 Some(math.sqrt(z)) 返回 z 的开平方的值,并放到Some(x)
def unapply(z:Double): Option[Double] = {
println("unapple 被调用 z 是=" + z)
//Some(z) unapple 被调用 z 是=25.0 匹配成功 n=25.0
Some(Math.sqrt(z))
// None
}
def apply(z:Double):Double = z * z
}

上面代码中的unapply方法就是模式匹配中调用的方法。这个例子可以深刻的理解模式匹配和unapply的关系。

对象匹配的特殊情况

  1. 当 case 后面的对象提取器方法的参数为多个,则会默认调用 def unapplySeq() 方法

  2. 如果 unapplySeq 返回是 Some,获取其中的值,判断得到的 sequence 中的元素的个数是否是三个 如果是三个,则把三个元素分别取出,赋值给 first,second 和 third

  3. 其它的规则不变.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
object MatchObjectDemo2 {
def main(args: Array[String]): Unit = {
val namesString = "Alice,Bob,Thomas" //字符串
//说明
namesString match {
// 当 执行 case Names(first, second, third)
// 1. 会调用 unapplySeq(str),把 "Alice,Bob,Thomas" 传入给 str
// 2. 如果 返回的是 Some("Alice","Bob","Thomas"),分别给 (first, second, third)
// 注意,这里的返回的值unapplySeq的个数需要和 case(first, second, third)要一样
// 3. 如果返回的 None ,表示匹配失败
case Names(first, second, third) => {
println("the string contains three people's names")
// 打印字符串
println(s"$first $second $third")
}
case _ => println("nothing matched")
}
}
}
//object
object Names {
//当构造器是多个参数时,就会触发这个对象提取器
def unapplySeq(str: String): Option[Seq[String]] = {
if (str.contains(",")) Some(str.split(","))
else None
}
}

变量声明中的模式

1
2
3
4
5
6
7
8
// 变量声明中的模式匹配
val (x,y,z) = (1,2,"hello")
println("x=" + x)
val (q,r) = BigInt(10) /% 3 // 说明 q = BigInt(10) / 3 r = BigInt(10) % 3
println(q,r)
val arr = Array(1,7,2,9)
val Array(first,second, _*) = arr // 提取出arr的其哪两个元素
println(first,second)

for表达式中的模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, v) <- map) {
println(k + " -> " + v) // 出来三个 key-value ("A"->1), ("B"->0), ("C"->3)
}
//说明 : 只遍历出 value =0 的 key-value ,其它的过滤掉
println("--------------(k, 0) <- map-------------------")
for ((k, 0) <- map) {
println(k + " --> " + 0)
}
//说明, 这个就是上面代码的另外写法, 只是下面的用法灵活和强大
println("--------------(k, v) <- map if v == 0-------------------")
for ((k, v) <- map if v >= 1) {
println(k + " ---> " + v)
}

样例类

  1. 样例类仍然是类
  1. 样例类用 case 关键字进行声明。

  2. 样例类是为 模式匹配而优化的类

  3. 构造器中的每一个参数都成为 val——除非它被显式地声明为 var(不建议这样做)

  4. 在样例类对应的伴生对象中提供 apply 方法让你不用 new 关键字就能构造出相应的对象

  5. 提供 unapply 方法让模式匹配可以工作

  6. 将自动生成 toString 、equals 、hashCode 和 和 copy 方法(有点类似模板类,直接给生成,供程序员使用) 8) 除上述外,样例类和其他类完全一样。你可以添加方法和字段,扩展它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object CaseClassDemo02 {
def main(args: Array[String]): Unit = {
//该案例的作用就是体验使用样例类方式进行对象匹配简洁性
for (amt <- Array(Dollar2(1000.0), Currency2(1000.0, "RMB"), NoAmount2)) {
val result = amt match {
//说明
case Dollar2(v) => "$" + v // $1000.0
//说明
case Currency2(v, u) => v + " " + u // 1000.0 RMB
case NoAmount2 => "" // ""
}
println(amt + ": " + result)
}
}
}
abstract class Amount2
case class Dollar2(value: Double) extends Amount2 //样例类
case class Currency2(value: Double, unit: String) extends Amount2 //样例类
case object NoAmount2 extends Amount2 //样例类

语句的中置表达式

1
2
3
4
List(1,3,5,9) match {
case first :: second :: rest => println(first + " " + second + " " + rest.length + " " + rest)
case _ => println("匹配不到...")
}

结果:1 3 2 List(5, 9)

什么是中置表达式?1 + 2,这就是一个中置表达式。如果 unapply 方法产出一个元组,你可以在 case 语句中使用中置表示法。比如可以匹配一个 List 序列

密封类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
密封类
1.可以避免滥用继承
2.用在模式匹配
*/
sealed abstract class Furniture
case class Couch() extends Furniture
case class Chair() extends Furniture

class SealedDemo {
def findPlaceTosit(furniture: Furniture):String=furniture match{
case a:Couch=>"lie on the couch"
case b:Chair=>"site on the chair"
}
}
object test{
def main(args: Array[String]): Unit = {
val chair=Chair()
val sea=new SealedDemo()
println(sea.findPlaceTosit(chair))
}
}

Match Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    // Example 商品捆绑打折出售
// 1) 商品捆绑可以是单个商品,也可以是多个商品
// 2) 打折时按照折扣 x 元进行设计
// 3) 能够统计出所有捆绑商品打折后的最终价格

//设计的样例类
abstract class Item //
case class Book(description: String, price: Double) extends Item
//Bundle discount: Double 折扣 , item: Item* ,可变参数
case class Bundle(description: String, discount: Double, item: Item*) extends Item

// 打折案例 120
val sale = Bundle("书籍",10,Book("漫画",40),Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))

// 1- 使用case语句,得到"漫画"
val demo = sale match {
case Bundle(_, _, Book(desc, _), _*) => desc
}
println(demo)// 漫画

// 2- 通过@表示法将嵌套的值绑定到变量。 _* 绑定剩余 Item 到 rest
val demo2 = sale match {
// 如果我们进行对象匹配时,不想接受某些值,则使用_忽略即可, _* 代表所有
case Bundle(_, _, art @ Book(_,_),rest @ _*) => (art, rest)
}
println(demo2) //(Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))))

val demo3 = sale match {
case Bundle(_,_,art3 @ Book(_, _),rest3) => (art3, rest3)
}
println(demo3) //(Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))

def price(it:Item):Double = {
it match {
case Book(_,p) => p
case Bundle(_,disc,its @ _*) => its.map(price).sum - disc
}
}

println("price=" + price(sale)) // 120.0

模式匹配和if else 的区别

模式匹配其实本质上是提供一个方便的解构 (Destructuring) 数据结构的方式,以 scala 为例, pattern matching 其实用到了 scala 中提取器的功能, 提取器其实就是类中的 unapply () 方法。

其实 if else 只是 pattern matching 中的一个典型的用法,但并非它的全部。

同时, pattern matching 允许你解耦两个并不真正属于彼此的东西,也使得你的代码更易于测试。

apply() & unapply()

  • apply方法被称作注入方法,在类的伴生对象中做一个初始化操作

  • apply方法的参数列表不需要跟构造器的参数列表统一

  • apply方法是定义在伴生对象中的

当Scala中类或者对象有一个主要用途的时候,apply方法就是很好的语法糖。

apply方法 => 类似 constructor4Java

unapply方法完全相反,unapply接受一个对象,从对象中提取出相应的值。

unapply方法主要用于模式匹配中。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Money(val value: Double, val country: String) {}

object Money {
def apply(value: Double, country: String) : Money = new Money(value, country)

def unapply(money: Money): Option[(Double, String)] = {
if(money == null) {
None
} else {
Some(money.value, money.country)
}
}
}
1
2
3
4
5
6
7
def testUnapply() = {
val money = Money(10.1, "RMB")
money match {
case Money(num, "RMB") => println("RMB: " + num)
case _ => println("Not RMB!")
}
}

为什么只能在object对象定义main方法才能作为入口?

类比java中的main方法的修饰词public static得知main方法是static(静态的),在scala中静态方法或者属性是在object修改的对象中的,object修饰的对象是singleton,用于保存静态成员等。

break & continue

1
import scala.util.control.Breaks.{break, breakable}

break:

1
2
3
4
5
6
7
8
9
breakable(
for(i<-0 until 10) {
println(i)
if(i==5){
break()
}
}
)
// 0,1,2,3,4,5

continue:

1
2
3
4
5
6
7
8
9
for(i<-0 until 10){
breakable{
if(i==3||i==6) {
break
}
println(i)
}
}
//0,1,2,3,5,7,8,9