Java プログラマー向けの Scala 入門チュートリアル

高洛峰
リリース: 2016-11-22 16:14:32
オリジナル
1971 人が閲覧しました

Java 8 には、クロージャなどのいくつかの予備的な関数型プログラミング機能のほか、新しい同時プログラミング モデルと、高階関数と遅延計算を備えたデータ コレクションである Stream があります。 Java 8 を試してみた後、まだ学ぶことがたくさんあると感じるかもしれません。はい、初めて関数型プログラミングを試してみると、Scala が知識への渇望を満たしてくれることがわかります。

Scala をインストールします

Scala の公式ダウンロード アドレスからダウンロードします: http://scala-lang.org/download/:

wget -c http://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgztar zxf scala-2.11.8.tgz
cd scala-2.11.8./bin/scala
Welcome to Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60).
Type in expressions to have them evaluated.Type :help for more information.

scala>
ログイン後にコピー

RELP

今、コマンドラインベースの Scala RELP を開始しました。インタラクションプログラミング環境。これは、Python や Ruby などの動的言語を使用する学生にとって非常に一般的なツールです。しかし、ジャワ愛好家は、初めてこれを見たとき、それがより魅力的であると感じるでしょう。不格好な IDE を起動せずに RELP でコードの実験を行うことができるので、問題を考えるときに非常に便利です。 Javaer にとって朗報です。JDK 9 には RELP 機能のサポートが組み込まれます。

Scalaでよく使われるIDE(統合開発環境)の場合、IDEA for scala pluginsとscala-ideの使用を推奨します。

Scala のパワーは、マルチコア プログラミング、関数型機能、および一部の Scala ベースのサードパーティ ライブラリとフレームワーク (Akka、Playframework、Spark、Kafka...) のサポートの向上に加えて、次の点にもあります。 Seam と Java を組み合わせた機能です。 Java 用に開発されたすべてのライブラリとフレームワークは、Scala 環境に自然に統合できます。もちろん、Scala は Spring などの Java 環境と簡単に統合することもできます。サードパーティ ライブラリのサポートが必要な場合は、Maven、Gradle、Sbt、およびその他のコンパイル環境を使用してそれを導入できます。

Scala は、関数型の特徴を備えたオブジェクト指向プログラミング言語です。Java のオブジェクト指向の特徴を継承すると同時に、Haskell などの他の言語から多くの関数型の特徴を吸収し、強化しています。

変数、基本データ型

Scala の変数は型を明示的に指定する必要はありませんが、事前に宣言する必要があります。これにより、多くの名前空間汚染の問題が回避されます。 Scala には非常に強力な自動型推定機能があり、右辺値とコンテキストに基づいて変数の型を自動的に推定できます。次のように値を直接宣言して割り当てることができます。

scala> val a = 1
a: Int = 1

scala> val b = true
b: Boolean = true

scala> val c = 1.0
c: Double = 1.0

scala> val a = 30 + "岁"
a: String = 30岁
ログイン後にコピー

Immutable

(注: 関数型プログラミングには、不変性という非常に重要な機能があります。変数の不変性に加えて、Scala は不変コレクションのセット scala.collection.immutable._ も定義します。)

Val は最後の値を表します。変数、つまり定数です。同様に、var を使用して定義された変数は共通変数であり、変更できます。ターミナルの出力からわかるように、Scala は右辺値から変数の型を自動的に推測します。 Scala は動的言語のようにコードを書くことができますが、静的言語のコンパイル時チェックを備えています。これは、Java の長く反復的な型宣言に比べて優れた改善です。

(注: RELP では val 変数を再割り当てできます。これは RELP` の機能です。これは通常のコードでは不可能です。)

基本データ型

Scala の基本データ型は次のとおりです: Byte、Short、Int 、Long、Float、Double、Boolean、Char、String。 Java とは異なり、Scala はネイティブ型とボックス型 (int や Integer など) を区別しません。 Int 型に統一・抽象化されているため、Scala ではすべての型がオブジェクトとなります。コンパイラは、コンパイル時にネイティブ型を使用するかボックス化された型を使用するかを自動的に決定します。

文字列

Scala には 3 種類の文字列があります。

はそれぞれ通常の文字列であり、その性質はJavaの文字列と同じです。

Scala では、3 つの二重引用符を接続することにも特別な意味があります。これは、ラップされたコンテンツがオリジナルの文字列であり、文字のトランスコーディングを必要としないことを意味します。この機能は、正規表現を定義するときに非常に便利です。

「文字列補間」と呼ばれるタイプの文字列もあります。これは、コンテキスト内の変数を直接参照し、結果を文字列に挿入できます。

scala> val c2 = '杨'
c2: Char = 杨

scala> val s1 = "重庆誉存企业信用管理有限公司"
s1: String = 重庆誉存企业信用管理有限公司

scala> val s2 = s"重庆誉存企业信用管理有限公司${c2}景"
s2: String = 重庆誉存企业信用管理有限公司

scala> val s3 = s"""重庆誉存企业信用管理有限公司"工程师"\n${c2}景是江津人"""
s3: String =
重庆誉存企业信用管理有限公司"工程师"
杨景是江津人
ログイン後にコピー

Operators と命名

Scala の Operator は、実際にはオブジェクトに対して定義されたメソッド (関数) です: 3 + 2 は、実際には次のようになります: 3.+(2)。 + 記号は、Int オブジェクトで定義されたメソッドです。 Java と同じ演算子 (メソッド) をサポートします:

(注: Scala では、メソッドの前の . 記号とメソッドの両側のかっこは、曖昧さを生じさせることなく省略できます。この方法で、多くの Beautiful DSL を定義できます。 )

==, !=: 比較演算

!, |, &, ^: 論理演算

>>, <<: ビット演算

note

在Scala中,修正了(算更符合一般人的常规理解吧)==和!=运算符的含义。在Scala中,==和!=是执行对象的值比较,相当于Java中的equals方法(实际上编译器在编译时也是这么做的)。而对象的引用比较需要使用eq和ne两个方法来实现。

控制语句(表达式)

Scala中支持if、while、for comprehension(for表达式)、match case(模式匹配)四大主要控制语句。Scala不支持switch和? :两种控制语句,但它的if和match case会有更好的实现。

if

Scala支持if语句,其基本使用和Java、Python中的一样。但不同的时,它是有返回值的。

(注:Scala是函数式语言,函数式语言还有一大特性就是:表达式。函数式语言中所有语句都是基于“表达式”的,而“表达式”的一个特性就是它会有一个值。所有像Java中的? :3目运算符可以使用if语句来代替)。

scala> if (true) "真" else "假"
res0: String = 真

scala> val f = if (false) "真" else "假"
f: String = 假

scala> val unit = if (false) "真"
unit: Any = ()

scala> val unit2 = if (true) "真" 
unit2: Any = 真
ログイン後にコピー

可以看到,if语句也是有返回值的,将表达式的结果赋给变量,编译器也能正常推导出变量的类型。unit和unit2变量的类型是Any,这是因为else语句的缺失,Scala编译器就按最大化类型来推导,而Any类型是Scala中的根类型。()在Scala中是Unit类型的实例,可以看做是Java中的Void。

while

Scala中的while循环语句:

while (条件) {
  语句块
}
ログイン後にコピー

for comprehension

Scala中也有for表达式,但它和Java中的for不太一样,它具有更强大的特性。通常的for语句如下:

for (变量 <- 集合) {
  语句块
}
ログイン後にコピー

Scala中for表达式除了上面那样的常规用法,它还可以使用yield关键字将集合映射为另一个集合:

scala> val list = List(1, 2, 3, 4, 5)list: List[Int] = List(1, 2, 3, 4, 5)

scala> val list2 = for (item <- list) yield item + 1list2: List[Int] = List(2, 3, 4, 5, 6)
ログイン後にコピー

还可以在表达式中使用if判断:

scala> val list3 = for (item <- list if item % 2 == 0) yield item
list3: List[Int] = List(2, 4)
ログイン後にコピー

还可以做flatMap操作,解析2维列表并将结果摊平(将2维列表拉平为一维列表):

scala> val llist = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
llist: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))

scala> for {
     |   l <- llist
     |   item <- l if item % 2 == 0
     | } yield item
res3: List[Int] = List(2, 4, 6, 8)
ログイン後にコピー

看到了,Scala中for comprehension的特性是很强大的。Scala的整个集合库都支持这一特性,包括:Seq、Map、Set、Array……

Scala没有C-Like语言里的for (int i = 0; i < 10; i++)语法,但Range(范围这个概念),可以基于它来实现循环迭代功能。在Scala中的使用方式如下:

scala> for (i <- (0 until 10)) {
     |   println(i)
     | }
0
1
2
3
4
5
6
7
8
9
ログイン後にコピー

Scala中还有一个to方法:

scala> for (i <- (0 to 10))
 print(" " + i) 0 1 2 3 4 5 6 7 8 9 10
ログイン後にコピー

match case

模式匹配,是函数式语言很强大的一个特性。它比命令式语言里的switch更好用,表达性更强。

scala> def level(s: Int) = s match {
     |   case n if n >= 90 => "优秀"
     |   case n if n >= 80 => "良好"
     |   case n if n >= 70 => "良"
     |   case n if n >= 60 => "及格"
     |   case _ => "差"
     | }level: (s: Int)Stringscala> level(51)res28: String = 差

scala> level(93)res29: String = 优秀

scala> level(80)res30: String = 良好
ログイン後にコピー

可以看到,模式匹配可以实现switch相似的功能。但与switch需要使用break明确告知终止之后的判断不同,Scala中的match case是默认break的。只要其中一个case语句匹配,就终止之后的所以比较。且对应case语句的表达式值将作为整个match case表达式的值返回。

Scala中的模式匹配还有类型匹配、数据抽取、谓词判断等其它有用的功能。这里只做简单介绍,之后会单独一个章节来做较详细的解读。

集合

在java.util包下有丰富的集合库。Scala除了可以使用Java定义的集合库外,它还自己定义了一套功能强大、特性丰富的scala.collection集合库API。

在Scala中,常用的集合类型有:List、Set、Map、Tuple、Vector等。

List

Scala中List是一个不可变列表集合,它很精妙的使用递归结构定义了一个列表集合。

scala> val list = List(1, 2, 3, 4, 5)
list: List[Int] = List(1, 2, 3, 4, 5)
ログイン後にコピー

除了之前使用Listobject来定义一个列表,还可以使用如下方式:

scala> val list = 1 :: 2 :: 3 :: 4 :: 5 ::
 Nillist: List[Int] = List(1, 2, 3, 4, 5)
ログイン後にコピー

List采用前缀操作的方式(所有操作都在列表顶端(开头))进行,::操作符的作用是将一个元素和列表连接起来,并把元素放在列表的开头。这样List的操作就可以定义成一个递归操作。添加一个元素就是把元素加到列表的开头,List只需要更改下头指针,而删除一个元素就是把List的头指针指向列表中的第2个元素。这样,List的实现就非常的高效,它也不需要对内存做任何的转移操作。List有很多常用的方法:

scala> list.indexOf(3)
res6: Int = 2scala> 0 :: listres8: List[Int] = List(0, 1, 2, 3, 4, 5)

scala> list.reverse
res9: List[Int] = List(5, 4, 3, 2, 1)

scala> list.filter(item => item == 3)
res11: List[Int] = List(3)

scala> listres12: List[Int] = List(1, 2, 3, 4, 5)

scala> val list2 = List(4, 5, 6, 7, 8, 9)
list2: List[Int] = List(4, 5, 6, 7, 8, 9)

scala> list.intersect(list2)
res13: List[Int] = List(4, 5)

scala> list.union(list2)
res14: List[Int] = List(1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9)

scala> list.diff(list2)
res15: List[Int] = List(1, 2, 3)
ログイン後にコピー

Scala中默认都是Immutable collection,在集合上定义的操作都不会更改集合本身,而是生成一个新的集合。这与Java集合是一个根本的区别,Java集合默认都是可变的。

Tuple

Scala中也支持Tuple(元组)这种集合,但最多只支持22个元素(事实上Scala中定义了Tuple0、Tuple1……Tuple22这样22个TupleX类,实现方式与C++ Boost库中的Tuple类似)。和大多数语言的Tuple类似(比如:Python),Scala也采用小括号来定义元组。

scala> val tuple1 = (1, 2, 3)tuple1: (Int, Int, Int) = (1,2,3)

scala> tuple1._2res17: Int = 2scala> val tuple2 = Tuple2("杨", " )
tuple2: (String, String) = (杨,景)
ログイン後にコピー

可以使用xxx._[X]的形式来引用Tuple中某一个具体元素,其_[X]下标是从1开始的,一直到22(若有定义这么多)。

Set

Set是一个不重复且无序的集合,初始化一个Set需要使用Set对象:

scala> val set = Set("Scala", "Java", "C++", "Javascript", "C#", "Python", "PHP") 
set: scala.collection.immutable.Set[String] = Set(Scala, C#, Python, Javascript, PHP, C++, Java)

scala> set + "Go"
res21: scala.collection.immutable.Set[String] = Set(Scala, C#, Go, Python, Javascript, PHP, C++, Java)

scala> set filterNot (item => item == "PHP")
res22: scala.collection.immutable.Set[String] = Set(Scala, C#, Python, Javascript, C++, Java)
ログイン後にコピー

Map

Scala中的Map默认是一个HashMap,其特性与Java版的HashMap基本一至,除了它是Immutable的:

scala> val map = Map("a" -> "A", "b" -> "B")
map: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B)

scala> val map2 = Map(("b", "B"), ("c", "C"))
map2: scala.collection.immutable.Map[String,String] = Map(b -> B, c -> C)
ログイン後にコピー

Scala中定义Map时,传入的每个Entry(K、V对)其实就是一个Tuple2(有两个元素的元组),而->是定义Tuple2的一种便捷方式。

scala> map + ("z" -> "Z")
res23: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B, z -> Z)

scala> map.filterNot(entry => entry._1 == "a")
res24: scala.collection.immutable.Map[String,String] = Map(b -> B)

scala> val map3 = map - "a"
map3: scala.collection.immutable.Map[String,String] = Map(b -> B)

scala> map
res25: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B)
ログイン後にコピー

Scala的immutable collection并没有添加和删除元素的操作,其定义+(List使用::在头部添加)操作都是生成一个新的集合,而要删除一个元素一般使用 - 操作直接将Key从map中减掉即可。

(注:Scala中也scala.collection.mutable._集合,它定义了不可变集合的相应可变集合版本。一般情况下,除非一此性能优先的操作(其实Scala集合采用了共享存储的优化,生成一个新集合并不会生成所有元素的复本,它将会和老的集合共享大元素。因为Scala中变量默认都是不可变的),推荐还是采用不可变集合。因为它更直观、线程安全,你可以确定你的变量不会在其它地方被不小心的更改。)

Class

Scala里也有class关键字,不过它定义类的方式与Java有些区别。Scala中,类默认是public的,且类属性和方法默认也是public的。Scala中,每个类都有一个“主构造函数”,主构造函数类似函数参数一样写在类名后的小括号中。因为Scala没有像Java那样的“构造函数”,所以属性变量都会在类被创建后初始化。所以当你需要在构造函数里初始化某些属性或资源时,写在类中的属性变量就相当于构造初始化了。

在Scala中定义类非常简单:

class Person(name: String, val age: Int) {
  override def toString(): String = s"姓名:$name, 年龄: $age"
}
ログイン後にコピー

默认,Scala主构造函数定义的属性是private的,可以显示指定:val或var来使其可见性为:public。

Scala中覆写一个方法必需添加:override关键字,这对于Java来说可以是一个修正。当标记了override关键字的方法在编译时,若编译器未能在父类中找到可覆写的方法时会报错。而在Java中,你只能通过@Override注解来实现类似功能,它的问题是它只是一个可选项,且编译器只提供警告。这样你还是很容易写出错误的“覆写”方法,你以后覆写了父类函数,但其实很有可能你是实现了一个新的方法,从而引入难以察觉的BUG。

实例化一个类的方式和Java一样,也是使用new关键字。

scala> val me = new Person("杨景", 30)
me: Person = 姓名:杨景, 年龄: 30scala> println(me)
姓名:杨景, 年龄: 30scala> me.name
<console>:20: error: value name is not a member of Person
       me.name
          ^

scala> me.ageres11: Int = 30
ログイン後にコピー

case class(样本类)

case class是Scala中学用的一个特性,像Kotlin这样的语言也学习并引入了类似特性(在Kotlin中叫做:data class)。case class具有如下特性:

不需要使用new关键词创建,直接使用类名即可

默认变量都是public final的,不可变的。当然也可以显示指定var、private等特性,但一般不推荐这样用

自动实现了:equals、hashcode、toString等函数

自动实现了:Serializable接口,默认是可序列化的

可应用到match case(模式匹配)中

自带一个copy方法,可以方便的根据某个case class实例来生成一个新的实例

……

这里给出一个case class的使用样例:

scala> trait Person
defined trait Person

scala> case class Man(name: String, age: Int) extends Person
defined class Man

scala> case class Woman(name: String, age: Int) extends Person
defined class Woman

scala> val man = Man("杨景", 30)
man: Man = Man(杨景,30)

scala> val woman = Woman("女人", 23)
woman: Woman = Woman(女人,23)

scala> val manNextYear = man.copy(age = 31)
manNextYear: Man = Man(杨景,31)
ログイン後にコピー

object

Scala有一种不同于Java的特殊类型,Singleton Objects。

object Blah {  def sum
(l: List[Int]): Int = l.sum
}
ログイン後にコピー

在Scala中,没有Java里的static静态变量和静态作用域的概念,取而代之的是:object。它除了可以实现Java里static的功能,它同时还是一个线程安全的单例类。

伴身对象

大多数的object都不是独立的,通常它都会与一个同名的class定义在一起。这样的object称为伴身对象。

class IntPair(val x: Int, val y: Int)

object IntPair {
  import math.Ordering
  implicit def ipord: Ordering[IntPair] =
    Ordering.by(ip => (ip.x, ip.y))
}
ログイン後にコピー

注意

伴身对象必需和它关联的类定义定义在同一个.scala文件。

伴身对象和它相关的类之间可以相互访问受保护的成员。在Java程序中,很多时候会把static成员设置成private的,在Scala中需要这样实现此特性:

class X {
  import X._
  def blah = foo
}
object X {
  private def foo = 42
}
ログイン後にコピー

函数

在Scala中,函数是一等公民。函数可以像类型一样被赋值给一个变量,也可以做为一个函数的参数被传入,甚至还可以做为函数的返回值返回。

从Java 8开始,Java也具备了部分函数式编程特性。其Lamdba函数允许将一个函数做值赋给变量、做为方法参数、做为函数返回值。

在Scala中,使用def关键ygnk来定义一个函数方法:

scala> def calc(n1: Int, n2: Int): (Int, Int) = {
     |   (n1 + n2, n1 * n2)
     | }
calc: (n1: Int, n2: Int)(Int, Int)

scala> val (add, sub) = calc(5, 1)
add: Int = 6
sub: Int = 5
ログイン後にコピー

这里定义了一个函数:calc,它有两个参数:n1和n2,其类型为:Int。cala函数的返回值类型是一个有两个元素的元组,在Scala中可以简写为:(Int, Int)。在Scala中,代码段的最后一句将做为函数返回值,所以这里不需要显示的写return关键字。

而val (add, sub) = calc(5, 1)一句,是Scala中的抽取功能。它直接把calc函数返回的一个Tuple2值赋给了add他sub两个变量。

函数可以赋给变量:

scala> val calcVar = calc _
calcVar: (Int, Int) => (Int, Int) = <function2>

scala> calcVar(2, 3)
res4: (Int, Int) = (5,6)

scala> val sum: (Int, Int) => Int = (x, y) => x + y
sum: (Int, Int) => Int = <function2>

scala> sum(5, 7)
res5: Int = 12
ログイン後にコピー

在Scala中,有两种定义函数的方式:

将一个现成的函数/方法赋值给一个变量,如:val calcVar = calc _。下划线在此处的含意是将函数赋给了变量,函数本身的参数将在变量被调用时再传入。

直接定义函数并同时赋给变量,如:val sum: (Int, Int) => Int = (x, y) => x + y,在冒号之后,等号之前部分:(Int, Int) => Int是函数签名,代表sum这个函数值接收两个Int类型参数并返回一个Int类型参数。等号之后部分是函数体,在函数函数时,x、y参数类型及返回值类型在此可以省略。

一个函数示例:自动资源管理

在我们的日常代码中,资源回收是一个很常见的操作。在Java 7之前,我们必需写很多的try { ... } finally { xxx.close() }这样的样版代码来手动回收资源。Java 7开始,提供了try with close这样的自动资源回收功能。Scala并不能使用Java 7新加的try with close资源自动回收功能,但Scala中有很方便的方式实现类似功能:

def using[T <: AutoCloseable, R](res: T)(func: T => R): R = {  try {
    func(res)
  } finally {    if (res != null)
      res.close()
  }
}val allLine = using(Files.newBufferedReader(Paths.get("/etc/hosts"))) { reader =>  @tailrec
  def readAll(buffer: StringBuilder, line: String): String = {    if (line == null) buffer.toString    else {
      buffer.append(line).append(&#39;\n&#39;)
      readAll(buffer, reader.readLine())
    }
  }

  readAll(new StringBuilder(), reader.readLine())
}

println(allLine)
ログイン後にコピー

using是我们定义的一个自动化资源管帮助函数,它接爱两个参数化类型参数,一个是实现了AutoCloseable接口的资源类,一个是形如:T => R的函数值。func是由用户定义的对res进行操作的函数代码体,它将被传给using函数并由using代执行。而res这个资源将在using执行完成返回前调用finally代码块执行.close方法来清理打开的资源。

这个:T <: AutoCloseable范型参数限制了T类型必需为AutoCloseable类型或其子类。R范型指定using函数的返回值类型将在实际调用时被自动参数化推导出来。我们在Scala Console中参看allLine变量的类型可以看到 allLine将被正确的赋予String类型,因为我们传给using函数参数func的函数值返回类型就为String:

scala> :type allLineString
ログイン後にコピー

在readAll函数的定义处,有两个特别的地方:

这个函数定义在了其它函数代码体内部

它有一个@tailrec注解

在Scala中,因为函数是第一类的,它可以被赋值给一个变量。所以Scala中的def定义函数可以等价val func = (x: Int, y: Int) => x + y这个的函数字面量定义函数形式。所以,既然通过变量定义的函数可以放在其它函数代码体内,通过def定义的函数也一样可以放在其它代码体内,这和Javascript很像。

@tailrec注解的含义是这个函数是尾递归函数,编译器在编译时将对其优化成相应的while循环。若一个函数不是尾递归的,加上此注解在编译时将报错。

模式匹配(match case)

模式匹配是函数式编程里面很强大的一个特性。

之前已经见识过了模式匹配的简单使用方式,可以用它替代:if else、switch这样的分支判断。除了这些简单的功能,模式匹配还有一系列强大、易用的特性。

match 中的值、变量和类型

scala> for {
     |   x <- Seq(1, false, 2.7, "one", &#39;four, new java.util.Date(), new RuntimeException("运行时异常"))
     | } {
     |   val str = x match {
     |     case d: Double => s"double: $d"
     |     case false => "boolean false"
     |     case d: java.util.Date => s"java.util.Date: $d"
     |     case 1 => "int 1"
     |     case s: String => s"string: $s"
     |     case symbol: Symbol => s"symbol: $symbol"
     |     case unexpected => s"unexpected value: $unexpected"
     |   }
     |   println(str)
     | }
int 1
boolean false
double: 2.7
string: one
symbol: &#39;four
java.util.Date: Sun Jul 24 16:51:20 CST 2016
unexpected value: java.lang.RuntimeException: 运行时异常
ログイン後にコピー

上面小试牛刀校验变量类型的同时完成类型转换功能。在Java中,你肯定写过或见过如下的代码:

public void receive(message: Object) {    
if (message isInstanceOf String) {     
  String strMsg = (String) message;
        ....
    } else if (message isInstanceOf java.util.Date) {
        java.util.Date dateMsg = (java.util.Date) message;
        ....
    } ....
}
ログイン後にコピー

对于这样的代码,真是辣眼睛啊~~~。

序列的匹配

scala> val nonEmptySeq = Seq(1, 2, 3, 4, 5)

scala> val emptySeq = Seq.empty[Int]

scala> val emptyList = Nil

scala> val nonEmptyList = List(1, 2, 3, 4, 5)

scala> val nonEmptyVector = Vector(1, 2, 3, 4, 5)

scala> val emptyVector = Vector.empty[Int]

scala> val nonEmptyMap = Map("one" -> 1, "two" -> 2, "three" -> 3)

scala> val emptyMap = Map.empty[String, Int]

scala> def seqToString[T](seq: Seq[T]): String = seq match {
     |   case head +: tail => s"$head +: " + seqToString(tail)
     |   case Nil => "Nil"
     | }

scala> for (seq <- Seq(
     |   nonEmptySeq, emptySeq, nonEmptyList, emptyList,
     |   nonEmptyVector, emptyVector, nonEmptyMap.toSeq, emptyMap.toSeq)) {
     |   println(seqToString(seq))
     | }
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
(one,1) +: (two,2) +: (three,3) +: Nil
Nil
ログイン後にコピー

模式匹配能很方便的抽取序列的元素,seqToString使用了模式匹配以递归的方式来将序列转换成字符串。case head +: tail将序列抽取成“头部”和“非头部剩下”两部分,head将保存序列第一个元素,tail保存序列剩下部分。而case Nil将匹配一个空序列。

case class的匹配

scala> trait Person

scala> case class Man(name: String, age: Int) extends Person

scala> case class Woman(name: String, age: Int) extends Person

scala> case class Boy(name: String, age: Int) extends Person

scala> val father = Man("父亲", 33)

scala> val mather = Woman("母亲", 30)

scala> val son = Man("儿子", 7)

scala> val daughter = Woman("女儿", 3)

scala> for (person <- Seq[Person](father, mather, son, daughter)) {
     |   person match {
     |     case Man("父亲", age) => println(s"父亲今年${age}岁")
     |     case man: Man if man.age < 10 => println(s"man is $man")
     |     case Woman(name, 30) => println(s"${name}今年有30岁")
     |     case Woman(name, age) => println(s"${name}今年有${age}岁")
     |   }
     | }
父亲今年33岁
母亲今年有30岁
man is Man(儿子,7)
女儿今年有3岁
ログイン後にコピー

在模式匹配中对case class进行解构操作,可以直接提取出感兴趣的字段并赋给变量。同时,模式匹配中还可以使用guard语句,给匹配判断添加一个if表达式做条件判断。

并发

Scala是对多核和并发编程的支付做得非常好,它的Future类型提供了执行异步操作的高级封装。

Future对象完成构建工作以后,控制权便会立刻返还给调用者,这时结果还不可以立刻可用。Future实例是一个句柄,它指向最终可用的结果值。不论操作成功与否,在future操作执行完成前,代码都可以继续执行而不被阻塞。Scala提供了多种方法用于处理future。

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global

val futures = (0 until 10).map { i =>
  Future {
    val s = i.toString
    print(s)
    s
  }
}

val future = Future.reduce(futures)((x, y) => x + y)

val result = Await.result(future, Duration.Inf)

// Exiting paste mode, now interpreting.

0132564789

scala> val result = Await.result(future, Duration.Inf)
result: String = 0123456789
ログイン後にコピー

上面代码创建了10个Future对象,Future.apply方法有两个参数列表。第一个参数列表包含一个需要并发执行的命名方法体(by-name body);而第二个参数列表包含了隐式的ExecutionContext对象,可以简单的把它看作一个线程池对象,它决定了这个任务将在哪个异步(线程)执行器中执行。futures对象的类型为IndexedSeq[Future[String]]。本示例中使用Future.reduce把一个futures的IndexedSeq[Future[String]]类型压缩成单独的Future[String]类型对象。Await.result用来阻塞代码并获取结果,输入的Duration.Inf用于设置超时时间,这里是无限制。

这里可以看到,在Future代码内部的println语句打印输出是无序的,但最终获取的result结果却是有序的。这是因为虽然每个Future都是在线程中无序执行,但Future.reduce方法将按传入的序列顺序合并结果。

除了使用Await.result阻塞代码获取结果,我们还可以使用事件回调的方式异步获取结果。Future对象提供了几个方法通过回调将执行的结果返还给调用者,常用的有:

onComplete: PartialFunction[Try[T], Unit]:当任务执行完成后调用,无论成功还是失败

onSuccess: PartialFunction[T, Unit]:当任务成功执行完成后调用

onFailure: PartialFunction[Throwable, Unit]:当任务执行失败(异常)时调用

import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global

val futures = (1 to 2) map {
  case 1 => Future.successful("1是奇数")
  case 2 => Future.failed(new RuntimeException("2不是奇数"))
}

futures.foreach(_.onComplete {
  case Success(i) => println(i)
  case Failure(t) => println(t)
})

Thread.sleep(2000)
ログイン後にコピー

futures.onComplete方法是一个偏函数,它的参数是:Try[String]。Try有两个子类,成功是返回Success[String],失败时返回Failure[Throwable],可以通过模式匹配的方式获取这个结果。

总结

本篇文章简单的介绍了Scala的语言特性,本文并不只限于Java程序员,任何有编程经验的程序员都可以看。现在你应该对Scala有了一个基础的认识,并可以写一些简单的代码了。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!