写给Java程序员的Scala入门教程
aaanly
8年前
<p style="text-align:start">之前因为Spark的引入,写了一篇<a href="/misc/goto?guid=4959675701912526318" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;">《写给Python程序员的Scala入门教程》</a>。那篇文章简单对比了Scala与Python的异同,并介绍了一些Scala的常用编程技巧。今天这篇文章将面向广大的Java程序员,带领Javaer进入函数式编程的世界。</p> <p style="text-align:start">Java 8拥有了一些初步的函数式编程能力:闭包等,还有新的并发编程模型及Stream这个带高阶函数和延迟计算的数据集合。在尝试了Java 8以后,也许会觉得意犹未尽。是的,你会发现Scala能满足你在初步尝试函数式编程后那求知的欲望。</p> <h2 style="text-align:start">安装Scala</h2> <p style="text-align:start">到Scala官方下载地址下载:<a href="/misc/goto?guid=4959675702010971130" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;">http://scala-lang.org/download/</a>:</p> <pre> wget -c http:<span style="color:rgb(153, 153, 153)">//downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgz</span> tar zxf <span style="color:rgb(204, 153, 204)">scala</span>-2.11.8.tgz <span style="color:rgb(204, 153, 204)">cd</span> <span style="color:rgb(204, 153, 204)">scala</span>-2.11.8 ./bin/<span style="color:rgb(204, 153, 204)">scala</span> Welcome to <span style="color:rgb(204, 153, 204)">Scala</span> <span style="color:rgb(204, 153, 204)">version</span> 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60). <span style="color:rgb(204, 153, 204)">Type</span> <span style="color:rgb(204, 153, 204)">in</span> expressions to have them evaluated. <span style="color:rgb(204, 153, 204)">Type</span> :<span style="color:rgb(204, 153, 204)">help</span> <span style="color:rgb(204, 153, 204)">for</span> <span style="color:rgb(204, 153, 204)">more</span> information. <span style="color:rgb(204, 153, 204)">scala</span>> </pre> <p style="text-align:start"><strong>RELP</strong></p> <p style="text-align:start">刚才我们已经启动了Scala RELP,它是一个基于命令行的交互式编程环境。对于有着Python、Ruby等动态语言的同学来说,这是一个很常用和工具。但Javaer们第一次见到会觉得比较神奇。我们可以在RELP中做一些代码尝试而不用启动笨拙的IDE,这在我们思考问题时非常的方便。对于Javaer有一个好消息,JDK 9开始将内建支持RELP功能。</p> <p style="text-align:start">对于Scala常用的IDE(集成开发环境),推荐使用<a href="/misc/goto?guid=4958859042026617529" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;">IDEA for scala plugins</a>和<a href="/misc/goto?guid=4958320055059003998" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;">scala-ide</a>。</p> <p style="text-align:start">Scala的强大,除了它自身对多核编程更好的支持、函数式特性及一些基于Scala的第3方库和框架(如:Akka、Playframework、Spark、Kafka……),还在于它可以无缝与Java结合。所有为Java开发的库、框架都可以自然的融入Scala环境。当然,Scala也可以很方便的Java环境集成,比如:<a href="/misc/goto?guid=4958848076647597077" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;"><strong>Spring</strong></a>。若你需要第3方库的支持,可以使用<a href="/misc/goto?guid=4958326408677261325" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;"><strong>Maven</strong></a>、<a href="/misc/goto?guid=4958970396567517858" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;"><strong>Gradle</strong></a>、<a href="/misc/goto?guid=4958546339009769843" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;"><strong>Sbt</strong></a>等编译环境来引入。</p> <p style="text-align:start">Scala是一个面向对象的函数式特性编程语言,它继承了Java的面向对特性,同时又从<a href="/misc/goto?guid=4958859555878174778" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;"><code>Haskell</code></a>等其它语言那里吸收了很多函数式特性并做了增强。</p> <h2 style="text-align:start">变量、基础数据类型</h2> <p style="text-align:start">Scala中变量不需要显示指定类型,但需要提前声明。这可以避免很多命名空间污染问题。Scala有一个很强大的类型自动推导功能,它可以根据右值及上下文自动推导出变量的类型。你可以通过如下方式来直接声明并赋值。</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> a = <span style="color:rgb(249, 145, 87)">1</span> a: Int = <span style="color:rgb(249, 145, 87)">1</span> scala> <span style="color:rgb(204, 153, 204)">val</span> b = <span style="color:rgb(249, 145, 87)">true</span> b: Boolean = <span style="color:rgb(249, 145, 87)">true</span> scala> <span style="color:rgb(204, 153, 204)">val</span> c = <span style="color:rgb(249, 145, 87)">1.0</span> c: Double = <span style="color:rgb(249, 145, 87)">1.0</span> scala> <span style="color:rgb(204, 153, 204)">val</span> a = <span style="color:rgb(249, 145, 87)">30</span> + <span style="color:rgb(153, 204, 153)">"岁"</span> a: String = <span style="color:rgb(249, 145, 87)">30</span>岁 </pre> <p style="text-align:start"><strong>Immutable</strong></p> <p style="text-align:start">(注:函数式编程有一个很重要的特性:不可变性。Scala中除了变量的不可变性,它还定义了一套不可变集合<code>scala.collection.immutable._</code>。)</p> <p style="text-align:start"><code>val</code>代表这是一个final variable,它是一个常量。定义后就不可以改变,相应的,使用<code>var</code>定义的就是平常所见的变量了,是可以改变的。从终端的打印可以看出,Scala从右值自动推导出了变量的类型。Scala可以如动态语言似的编写代码,但又有静态语言的编译时检查。这对于Java中冗长、重复的类型声明来说是一种很好的进步。</p> <p style="text-align:start">(注:在<code>RELP</code>中,<code>val</code>变量是可以重新赋值的,这是`RELP`的特性。在平常的代码中是不可以的。)</p> <p style="text-align:start"><strong>基础数据类型</strong></p> <p style="text-align:start">Scala中基础数据类型有:Byte、Short、Int、Long、Float、Double,Boolean,Char、String。和Java不同的是,Scala中没在区分原生类型和装箱类型,如:<code>int</code>和<code>Integer</code>。它统一抽象成<code>Int</code>类型,这样在Scala中所有类型都是对象了。编译器在编译时将自动决定使用原生类型还是装箱类型。</p> <p style="text-align:start"><strong>字符串</strong></p> <p style="text-align:start">Scala中的字符串有3种。</p> <ul> <li>分别是普通字符串,它的特性和Java字符串一至。</li> <li>连线3个双引号在Scala中也有特殊含义,它代表被包裹的内容是原始字符串,可以不需要字符转码。这一特性在定义正则表达式时很有优势。</li> <li>还有一种被称为“字符串插值”的字符串,他可以直接引用上下文中的变量,并把结果插入字符串中。</li> </ul> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> c2 = '杨' c2: Char = 杨 scala> <span style="color:rgb(204, 153, 204)">val</span> s1 = <span style="color:rgb(153, 204, 153)">"重庆誉存企业信用管理有限公司"</span> s1: String = 重庆誉存企业信用管理有限公司 scala> <span style="color:rgb(204, 153, 204)">val</span> s2 = s<span style="color:rgb(153, 204, 153)">"重庆誉存企业信用管理有限公司${c2}景"</span> s2: String = 重庆誉存企业信用管理有限公司 scala> <span style="color:rgb(204, 153, 204)">val</span> s3 = s<span style="color:rgb(153, 204, 153)">"""重庆誉存企业信用管理有限公司"工程师"\n${c2}景是江津人"""</span> s3: String = 重庆誉存企业信用管理有限公司<span style="color:rgb(153, 204, 153)">"工程师"</span> 杨景是江津人 </pre> <h2 style="text-align:start">运算符和命名</h2> <p style="text-align:start">Scala中的运算符其实是定义在对象上的方法(函数),你看到的诸如:<code>3 + 2</code>其实是这样子的:<code>3.+(2)</code>。<code>+</code>符号是定义在<code>Int</code>对象上的一个方法。支持和Java一至的运算符(方法):</p> <p style="text-align:start">(注:在Scala中,方法前的<code>.</code>号和方法两边的小括号在不引起歧义的情况下是可以省略的。这样我们就可以定义出很优美的<a href="/misc/goto?guid=4959638412616651270" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;"><code>DSL</code></a>)</p> <ul> <li><code>==</code>、<code>!=</code>:比较运算</li> <li><code>!</code>、<code>|</code>、<code>&</code>、<code>^</code>:逻辑运算</li> <li><code>>></code>、<code><<</code>:位运算</li> </ul> <p style="text-align:start"><strong><em>注意</em></strong></p> <p style="text-align:start">在Scala中,修正了(算更符合一般人的常规理解吧)<code>==</code>和<code>!=</code>运算符的含义。在Scala中,<code>==</code>和<code>!=</code>是执行对象的值比较,相当于Java中的<code>equals</code>方法(实际上编译器在编译时也是这么做的)。而对象的引用比较需要使用<code>eq</code>和<code>ne</code>两个方法来实现。</p> <h2 style="text-align:start">控制语句(表达式)</h2> <p style="text-align:start">Scala中支持<code>if</code>、<code>while</code>、<code>for comprehension</code>(for表达式)、<code>match case</code>(模式匹配)四大主要控制语句。Scala不支持<code>switch</code>和<code>? :</code>两种控制语句,但它的<code>if</code>和<code>match case</code>会有更好的实现。</p> <p style="text-align:start"><strong><code>if</code></strong></p> <p style="text-align:start">Scala支持<code>if</code>语句,其基本使用和<code>Java</code>、<code>Python</code>中的一样。但不同的时,它是有返回值的。</p> <p style="text-align:start">(注:Scala是函数式语言,函数式语言还有一大特性就是:表达式。函数式语言中所有语句都是基于“表达式”的,而“表达式”的一个特性就是它会有一个值。所有像<code>Java</code>中的<code>? :</code>3目运算符可以使用<code>if</code>语句来代替)。</p> <pre> scala> <span style="color:rgb(204, 153, 204)">if</span> (<span style="color:rgb(249, 145, 87)">true</span>) <span style="color:rgb(153, 204, 153)">"真"</span> <span style="color:rgb(204, 153, 204)">else</span> <span style="color:rgb(153, 204, 153)">"假"</span> res0: String = 真 scala> <span style="color:rgb(204, 153, 204)">val</span> f = <span style="color:rgb(204, 153, 204)">if</span> (<span style="color:rgb(249, 145, 87)">false</span>) <span style="color:rgb(153, 204, 153)">"真"</span> <span style="color:rgb(204, 153, 204)">else</span> <span style="color:rgb(153, 204, 153)">"假"</span> f: String = 假 scala> <span style="color:rgb(204, 153, 204)">val</span> unit = <span style="color:rgb(204, 153, 204)">if</span> (<span style="color:rgb(249, 145, 87)">false</span>) <span style="color:rgb(153, 204, 153)">"真"</span> unit: Any = () scala> <span style="color:rgb(204, 153, 204)">val</span> unit2 = <span style="color:rgb(204, 153, 204)">if</span> (<span style="color:rgb(249, 145, 87)">true</span>) <span style="color:rgb(153, 204, 153)">"真"</span> unit2: Any = 真 </pre> <p style="text-align:start"><strong><em>可以看到,<code>if</code>语句也是有返回值的,将表达式的结果赋给变量,编译器也能正常推导出变量的类型。</em></strong><code>unit</code>和<code>unit2</code>变量的类型是<code>Any</code>,这是因为<code>else</code>语句的缺失,Scala编译器就按最大化类型来推导,而<code>Any</code>类型是Scala中的根类型。<code>()</code>在Scala中是<code>Unit</code>类型的实例,可以看做是<code>Java</code>中的<code>Void</code>。</p> <p style="text-align:start"><strong><code>while</code></strong></p> <p style="text-align:start">Scala中的<code>while</code>循环语句:</p> <pre> <span style="color:rgb(204, 153, 204)">while</span> (条件) { 语句块 } </pre> <p style="text-align:start"><strong><code>for comprehension</code></strong></p> <p style="text-align:start">Scala中也有<code>for</code>表达式,但它和<code>Java</code>中的<code>for</code>不太一样,它具有更强大的特性。通常的<code>for</code>语句如下:</p> <pre> <span style="color:rgb(204, 153, 204)">for</span> (变量 <- 集合) { 语句块 } </pre> <p style="text-align:start">Scala中<code>for</code>表达式除了上面那样的常规用法,它还可以使用<code>yield</code>关键字将集合映射为另一个集合:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> list = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) list: List[Int] = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> list2 = <span style="color:rgb(204, 153, 204)">for</span> (item <- list) <span style="color:rgb(204, 153, 204)">yield</span> item + <span style="color:rgb(249, 145, 87)">1</span> list2: List[Int] = List(<span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">6</span>) </pre> <p style="text-align:start">还可以在表达式中使用<code>if</code>判断:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> list3 = <span style="color:rgb(204, 153, 204)">for</span> (item <- list <span style="color:rgb(204, 153, 204)">if</span> item % <span style="color:rgb(249, 145, 87)">2</span> == <span style="color:rgb(249, 145, 87)">0</span>) <span style="color:rgb(204, 153, 204)">yield</span> item list3: List[Int] = List(<span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">4</span>) </pre> <p style="text-align:start">还可以做<code>flatMap</code>操作,解析2维列表并将结果摊平(将2维列表拉平为一维列表):</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> llist = List(List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>), List(<span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">6</span>), List(<span style="color:rgb(249, 145, 87)">7</span>, <span style="color:rgb(249, 145, 87)">8</span>, <span style="color:rgb(249, 145, 87)">9</span>)) llist: List[List[Int]] = List(List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>), List(<span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">6</span>), List(<span style="color:rgb(249, 145, 87)">7</span>, <span style="color:rgb(249, 145, 87)">8</span>, <span style="color:rgb(249, 145, 87)">9</span>)) scala> <span style="color:rgb(204, 153, 204)">for</span> { | l <- llist | item <- l <span style="color:rgb(204, 153, 204)">if</span> item % <span style="color:rgb(249, 145, 87)">2</span> == <span style="color:rgb(249, 145, 87)">0</span> | } <span style="color:rgb(204, 153, 204)">yield</span> item res3: List[Int] = List(<span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">6</span>, <span style="color:rgb(249, 145, 87)">8</span>) </pre> <p style="text-align:start">看到了,Scala中<code>for comprehension</code>的特性是很强大的。Scala的整个集合库都支持这一特性,包括:<code>Seq</code>、<code>Map</code>、<code>Set</code>、<code>Array</code>……</p> <p style="text-align:start">Scala没有C-Like语言里的<code>for (int i = 0; i < 10; i++)</code>语法,但<code>Range</code>(范围这个概念),可以基于它来实现循环迭代功能。在Scala中的使用方式如下:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">for</span> (i <- (<span style="color:rgb(249, 145, 87)">0</span> until <span style="color:rgb(249, 145, 87)">10</span>)) { | println(i) | } <span style="color:rgb(249, 145, 87)">0</span> <span style="color:rgb(249, 145, 87)">1</span> <span style="color:rgb(249, 145, 87)">2</span> <span style="color:rgb(249, 145, 87)">3</span> <span style="color:rgb(249, 145, 87)">4</span> <span style="color:rgb(249, 145, 87)">5</span> <span style="color:rgb(249, 145, 87)">6</span> <span style="color:rgb(249, 145, 87)">7</span> <span style="color:rgb(249, 145, 87)">8</span> <span style="color:rgb(249, 145, 87)">9</span> </pre> <p style="text-align:start">Scala中还有一个<code>to</code>方法:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">for</span> (i <- (<span style="color:rgb(249, 145, 87)">0</span> to <span style="color:rgb(249, 145, 87)">10</span>)) print(<span style="color:rgb(153, 204, 153)">" "</span> + i) <span style="color:rgb(249, 145, 87)">0</span> <span style="color:rgb(249, 145, 87)">1</span> <span style="color:rgb(249, 145, 87)">2</span> <span style="color:rgb(249, 145, 87)">3</span> <span style="color:rgb(249, 145, 87)">4</span> <span style="color:rgb(249, 145, 87)">5</span> <span style="color:rgb(249, 145, 87)">6</span> <span style="color:rgb(249, 145, 87)">7</span> <span style="color:rgb(249, 145, 87)">8</span> <span style="color:rgb(249, 145, 87)">9</span> <span style="color:rgb(249, 145, 87)">10</span> </pre> <p style="text-align:start">你还可以控制每次步进的步长,只需要简单的使用<code>by</code>方法即可:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">for</span> (i <- <span style="color:rgb(249, 145, 87)">0</span> to <span style="color:rgb(249, 145, 87)">10</span> by <span style="color:rgb(249, 145, 87)">2</span>) print(<span style="color:rgb(153, 204, 153)">" "</span> + i) <span style="color:rgb(249, 145, 87)">0</span> <span style="color:rgb(249, 145, 87)">2</span> <span style="color:rgb(249, 145, 87)">4</span> <span style="color:rgb(249, 145, 87)">6</span> <span style="color:rgb(249, 145, 87)">8</span> <span style="color:rgb(249, 145, 87)">10</span> </pre> <p style="text-align:start"><strong>match case</strong></p> <p style="text-align:start">模式匹配,是函数式语言很强大的一个特性。它比命令式语言里的<code>switch</code>更好用,表达性更强。</p> <pre> scala> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">level</span>(</span>s: Int) = s <span style="color:rgb(204, 153, 204)">match</span> { | <span style="color:rgb(204, 153, 204)">case</span> n <span style="color:rgb(204, 153, 204)">if</span> n >= <span style="color:rgb(249, 145, 87)">90</span> => <span style="color:rgb(153, 204, 153)">"优秀"</span> | <span style="color:rgb(204, 153, 204)">case</span> n <span style="color:rgb(204, 153, 204)">if</span> n >= <span style="color:rgb(249, 145, 87)">80</span> => <span style="color:rgb(153, 204, 153)">"良好"</span> | <span style="color:rgb(204, 153, 204)">case</span> n <span style="color:rgb(204, 153, 204)">if</span> n >= <span style="color:rgb(249, 145, 87)">70</span> => <span style="color:rgb(153, 204, 153)">"良"</span> | <span style="color:rgb(204, 153, 204)">case</span> n <span style="color:rgb(204, 153, 204)">if</span> n >= <span style="color:rgb(249, 145, 87)">60</span> => <span style="color:rgb(153, 204, 153)">"及格"</span> | <span style="color:rgb(204, 153, 204)">case</span> _ => <span style="color:rgb(153, 204, 153)">"差"</span> | } level: (s: Int)String scala> level(<span style="color:rgb(249, 145, 87)">51</span>) res28: String = 差 scala> level(<span style="color:rgb(249, 145, 87)">93</span>) res29: String = 优秀 scala> level(<span style="color:rgb(249, 145, 87)">80</span>) res30: String = 良好 </pre> <p style="text-align:start">可以看到,模式匹配可以实现<code>switch</code>相似的功能。但与<code>switch</code>需要使用<code>break</code>明确告知终止之后的判断不同,Scala中的<code>match case</code>是默认<strong>break</strong>的。只要其中一个<code>case</code>语句匹配,就终止之后的所以比较。且对应<code>case</code>语句的表达式值将作为整个<code>match case</code>表达式的值返回。</p> <p style="text-align:start">Scala中的模式匹配还有类型匹配、数据抽取、谓词判断等其它有用的功能。这里只做简单介绍,之后会单独一个章节来做较详细的解读。</p> <h2 style="text-align:start">集合</h2> <p style="text-align:start">在<code>java.util</code>包下有丰富的集合库。Scala除了可以使用Java定义的集合库外,它还自己定义了一套功能强大、特性丰富的<code>scala.collection</code>集合库API。</p> <p style="text-align:start">在Scala中,常用的集合类型有:<code>List</code>、<code>Set</code>、<code>Map</code>、<code>Tuple</code>、<code>Vector</code>等。</p> <p style="text-align:start"><strong>List</strong></p> <p style="text-align:start">Scala中<code>List</code>是一个不可变列表集合,它很精妙的使用递归结构定义了一个列表集合。</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> list = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) list: List[Int] = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) </pre> <p style="text-align:start">除了之前使用<code>List</code>object来定义一个列表,还可以使用如下方式:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> list = <span style="color:rgb(249, 145, 87)">1</span> :: <span style="color:rgb(249, 145, 87)">2</span> :: <span style="color:rgb(249, 145, 87)">3</span> :: <span style="color:rgb(249, 145, 87)">4</span> :: <span style="color:rgb(249, 145, 87)">5</span> :: Nil list: List[Int] = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) </pre> <p style="text-align:start"><code>List</code>采用前缀操作的方式(所有操作都在列表顶端(开头))进行,<code>::</code>操作符的作用是将一个元素和列表连接起来,并把元素放在列表的开头。这样<code>List</code>的操作就可以定义成一个递归操作。添加一个元素就是把元素加到列表的开头,List只需要更改下头指针,而删除一个元素就是把List的头指针指向列表中的第2个元素。这样,<code>List</code>的实现就非常的高效,它也不需要对内存做任何的转移操作。<code>List</code>有很多常用的方法:</p> <pre> scala> list.indexOf(<span style="color:rgb(249, 145, 87)">3</span>) res6: Int = <span style="color:rgb(249, 145, 87)">2</span> scala> <span style="color:rgb(249, 145, 87)">0</span> :: list res8: List[Int] = List(<span style="color:rgb(249, 145, 87)">0</span>, <span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) scala> list.reverse res9: List[Int] = List(<span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">1</span>) scala> list.filter(item => item == <span style="color:rgb(249, 145, 87)">3</span>) res11: List[Int] = List(<span style="color:rgb(249, 145, 87)">3</span>) scala> list res12: List[Int] = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> list2 = List(<span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">6</span>, <span style="color:rgb(249, 145, 87)">7</span>, <span style="color:rgb(249, 145, 87)">8</span>, <span style="color:rgb(249, 145, 87)">9</span>) list2: List[Int] = List(<span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">6</span>, <span style="color:rgb(249, 145, 87)">7</span>, <span style="color:rgb(249, 145, 87)">8</span>, <span style="color:rgb(249, 145, 87)">9</span>) scala> list.intersect(list2) res13: List[Int] = List(<span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) scala> list.union(list2) res14: List[Int] = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">6</span>, <span style="color:rgb(249, 145, 87)">7</span>, <span style="color:rgb(249, 145, 87)">8</span>, <span style="color:rgb(249, 145, 87)">9</span>) scala> list.diff(list2) res15: List[Int] = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>) </pre> <p style="text-align:start">Scala中默认都是<strong>Immutable collection</strong>,在集合上定义的操作都不会更改集合本身,而是生成一个新的集合。这与Java集合是一个根本的区别,Java集合默认都是可变的。</p> <p style="text-align:start"><strong>Tuple</strong></p> <p style="text-align:start">Scala中也支持<strong>Tuple</strong>(元组)这种集合,但最多只支持22个元素(事实上Scala中定义了<code>Tuple0</code>、<code>Tuple1</code>……<code>Tuple22</code>这样22个<code>TupleX</code>类,实现方式与<code>C++ Boost</code>库中的<code>Tuple</code>类似)。和大多数语言的Tuple类似(比如:Python),Scala也采用小括号来定义元组。</p> <pre> scala> val tuple1 = (1, 2, 3) tuple1: (Int, Int, Int) = (1,2,3) scala> tuple1._2 res17: Int = 2 scala> val tuple2 = Tuple2("杨", " ) tuple2: (String, String) = (杨,景) </pre> <p style="text-align:start">可以使用<code>xxx._[X]</code>的形式来引用<code>Tuple</code>中某一个具体元素,其<code>_[X]</code>下标是从1开始的,一直到22(若有定义这么多)。</p> <p style="text-align:start"><strong>Set</strong></p> <p style="text-align:start"><code>Set</code>是一个不重复且无序的集合,初始化一个<code>Set</code>需要使用<code>Set</code>对象:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> set = Set(<span style="color:rgb(153, 204, 153)">"Scala"</span>, <span style="color:rgb(153, 204, 153)">"Java"</span>, <span style="color:rgb(153, 204, 153)">"C++"</span>, <span style="color:rgb(153, 204, 153)">"Javascript"</span>, <span style="color:rgb(153, 204, 153)">"C#"</span>, <span style="color:rgb(153, 204, 153)">"Python"</span>, <span style="color:rgb(153, 204, 153)">"PHP"</span>) set: scala.collection.immutable.Set[String] = Set(Scala, C#, Python, Javascript, PHP, C++, Java) scala> set + <span style="color:rgb(153, 204, 153)">"Go"</span> res21: scala.collection.immutable.Set[String] = Set(Scala, C#, Go, Python, Javascript, PHP, C++, Java) scala> set filterNot (item => item == <span style="color:rgb(153, 204, 153)">"PHP"</span>) res22: scala.collection.immutable.Set[String] = Set(Scala, C#, Python, Javascript, C++, Java) </pre> <p style="text-align:start"><strong>Map</strong></p> <p style="text-align:start">Scala中的<code>Map</code>默认是一个<strong>HashMap</strong>,其特性与Java版的<code>HashMap</code>基本一至,除了它是<code>Immutable</code>的:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> map = Map(<span style="color:rgb(153, 204, 153)">"a"</span> -> <span style="color:rgb(153, 204, 153)">"A"</span>, <span style="color:rgb(153, 204, 153)">"b"</span> -> <span style="color:rgb(153, 204, 153)">"B"</span>) map: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B) scala> <span style="color:rgb(204, 153, 204)">val</span> map2 = Map((<span style="color:rgb(153, 204, 153)">"b"</span>, <span style="color:rgb(153, 204, 153)">"B"</span>), (<span style="color:rgb(153, 204, 153)">"c"</span>, <span style="color:rgb(153, 204, 153)">"C"</span>)) map2: scala.collection.immutable.Map[String,String] = Map(b -> B, c -> C) </pre> <p style="text-align:start">Scala中定义<code>Map</code>时,传入的每个<code>Entry</code>(<strong>K</strong>、<strong>V</strong>对)其实就是一个<code>Tuple2</code>(有两个元素的元组),而<code>-></code>是定义<code>Tuple2</code>的一种便捷方式。</p> <pre> scala> map + (<span style="color:rgb(153, 204, 153)">"z"</span> -> <span style="color:rgb(153, 204, 153)">"Z"</span>) res23: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B, z -> Z) scala> map.filterNot(entry => entry._1 == <span style="color:rgb(153, 204, 153)">"a"</span>) res24: scala.collection.immutable.Map[String,String] = Map(b -> B) scala> <span style="color:rgb(204, 153, 204)">val</span> map3 = map - <span style="color:rgb(153, 204, 153)">"a"</span> map3: scala.collection.immutable.Map[String,String] = Map(b -> B) scala> map res25: scala.collection.immutable.Map[String,String] = Map(a -> A, b -> B) </pre> <p style="text-align:start">Scala的immutable collection并没有添加和删除元素的操作,其定义<code>+</code>(<code>List</code>使用<code>::</code>在头部添加)操作都是生成一个新的集合,而要删除一个元素一般使用 <code>-</code> 操作直接将<strong>Key</strong>从<code>map</code>中减掉即可。</p> <p style="text-align:start">(注:Scala中也<code>scala.collection.mutable._</code>集合,它定义了不可变集合的相应可变集合版本。一般情况下,除非一此性能优先的操作(其实Scala集合采用了共享存储的优化,生成一个新集合并不会生成所有元素的复本,它将会和老的集合共享大元素。因为Scala中变量默认都是不可变的),推荐还是采用不可变集合。因为它更直观、线程安全,你可以确定你的变量不会在其它地方被不小心的更改。)</p> <h2 style="text-align:start">Class</h2> <p style="text-align:start">Scala里也有<code>class</code>关键字,不过它定义类的方式与Java有些区别。Scala中,类默认是<strong>public</strong>的,且类属性和方法默认也是<strong>public</strong>的。Scala中,每个类都有一个<strong>“主构造函数”</strong>,主构造函数类似函数参数一样写在类名后的小括号中。因为Scala没有像Java那样的“构造函数”,所以属性变量都会在类被创建后初始化。所以当你需要在构造函数里初始化某些属性或资源时,写在类中的属性变量就相当于构造初始化了。</p> <p style="text-align:start">在Scala中定义类非常简单:</p> <pre> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Person</span>(</span>name: String, <span style="color:rgb(204, 153, 204)">val</span> age: Int) { <span style="color:rgb(204, 153, 204)">override</span> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">toString</span>(</span>): String = s<span style="color:rgb(153, 204, 153)">"姓名:$name, 年龄: $age"</span> } </pre> <p style="text-align:start">默认,Scala主构造函数定义的属性是<strong>private</strong>的,可以显示指定:<code>val</code>或<code>var</code>来使其可见性为:<strong>public</strong>。</p> <p style="text-align:start">Scala中覆写一个方法必需添加:<code>override</code>关键字,这对于Java来说可以是一个修正。当标记了<code>override</code>关键字的方法在编译时,若编译器未能在父类中找到可覆写的方法时会报错。而在Java中,你只能通过<code>@Override</code>注解来实现类似功能,它的问题是它只是一个可选项,且编译器只提供警告。这样你还是很容易写出错误的“覆写”方法,你以后覆写了父类函数,但其实很有可能你是实现了一个新的方法,从而引入难以察觉的BUG。</p> <p style="text-align:start">实例化一个类的方式和Java一样,也是使用<code>new</code>关键字。</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> me = <span style="color:rgb(204, 153, 204)">new</span> Person(<span style="color:rgb(153, 204, 153)">"杨景"</span>, <span style="color:rgb(249, 145, 87)">30</span>) me: Person = 姓名:杨景, 年龄: <span style="color:rgb(249, 145, 87)">30</span> scala> println(me) 姓名:杨景, 年龄: <span style="color:rgb(249, 145, 87)">30</span> scala> me.name <console>:<span style="color:rgb(249, 145, 87)">20</span>: error: value name is not a member of Person me.name ^ scala> me.age res11: Int = <span style="color:rgb(249, 145, 87)">30</span> </pre> <p style="text-align:start"><strong>case class(样本类)</strong></p> <p style="text-align:start"><code>case class</code>是Scala中学用的一个特性,像<code>Kotlin</code>这样的语言也学习并引入了类似特性(在<code>Kotlin</code>中叫做:<code>data class</code>)。<code>case class</code>具有如下特性:</p> <ol> <li>不需要使用<code>new</code>关键词创建,直接使用类名即可</li> <li>默认变量都是<strong>public final</strong>的,不可变的。当然也可以显示指定<code>var</code>、<code>private</code>等特性,但一般不推荐这样用</li> <li>自动实现了:<code>equals</code>、<code>hashcode</code>、<code>toString</code>等函数</li> <li>自动实现了:<code>Serializable</code>接口,默认是可序列化的</li> <li>可应用到<strong>match case</strong>(模式匹配)中</li> <li>自带一个<code>copy</code>方法,可以方便的根据某个<code>case class</code>实例来生成一个新的实例</li> <li>……</li> </ol> <p style="text-align:start">这里给出一个<code>case class</code>的使用样例:</p> <pre> scala> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">trait</span> <span style="color:rgb(153, 153, 153)">Person</span></span> defined <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">trait</span> <span style="color:rgb(153, 153, 153)">Person</span></span> scala> <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Man</span>(</span>name: String, age: Int) <span style="color:rgb(204, 153, 204)">extends</span> Person defined <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Man</span></span> scala> <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Woman</span>(</span>name: String, age: Int) <span style="color:rgb(204, 153, 204)">extends</span> Person defined <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Woman</span></span> scala> <span style="color:rgb(204, 153, 204)">val</span> man = Man(<span style="color:rgb(153, 204, 153)">"杨景"</span>, <span style="color:rgb(249, 145, 87)">30</span>) man: Man = Man(杨景,<span style="color:rgb(249, 145, 87)">30</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> woman = Woman(<span style="color:rgb(153, 204, 153)">"女人"</span>, <span style="color:rgb(249, 145, 87)">23</span>) woman: Woman = Woman(女人,<span style="color:rgb(249, 145, 87)">23</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> manNextYear = man.copy(age = <span style="color:rgb(249, 145, 87)">31</span>) manNextYear: Man = Man(杨景,<span style="color:rgb(249, 145, 87)">31</span>) </pre> <h2 style="text-align:start">object</h2> <p style="text-align:start">Scala有一种不同于Java的特殊类型,<strong>Singleton Objects</strong>。</p> <pre> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">object</span> <span style="color:rgb(153, 153, 153)">Blah</span> {</span> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">sum</span>(</span>l: List[Int]): Int = l.sum } </pre> <p style="text-align:start">在Scala中,没有Java里的<strong>static</strong>静态变量和静态作用域的概念,取而代之的是:<strong>object</strong>。它除了可以实现Java里<strong>static</strong>的功能,它同时还是一个线程安全的单例类。</p> <p style="text-align:start"><strong>伴身对象</strong></p> <p style="text-align:start">大多数的<code>object</code>都不是独立的,通常它都会与一个同名的<code>class</code>定义在一起。这样的<code>object</code>称为<strong>伴身对象</strong>。</p> <pre> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">IntPair</span>(</span><span style="color:rgb(204, 153, 204)">val</span> x: Int, <span style="color:rgb(204, 153, 204)">val</span> y: Int) <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">object</span> <span style="color:rgb(153, 153, 153)">IntPair</span> {</span> <span style="color:rgb(204, 153, 204)">import</span> math.Ordering <span style="color:rgb(204, 153, 204)">implicit</span> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">ipord</span>:</span> Ordering[IntPair] = Ordering.by(ip => (ip.x, ip.y)) } </pre> <p style="text-align:start"><strong><em>注意</em></strong></p> <p style="text-align:start"><strong>伴身对象</strong>必需和它关联的类定义定义在同一个<strong>.scala</strong>文件。</p> <p style="text-align:start">伴身对象和它相关的类之间可以相互访问受保护的成员。在Java程序中,很多时候会把<strong>static</strong>成员设置成<strong>private</strong>的,在Scala中需要这样实现此特性:</p> <pre> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">X</span> {</span> <span style="color:rgb(204, 153, 204)">import</span> X._ <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">blah</span> =</span> foo } <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">object</span> <span style="color:rgb(153, 153, 153)">X</span> {</span> <span style="color:rgb(204, 153, 204)">private</span> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">foo</span> =</span> <span style="color:rgb(249, 145, 87)">42</span> } </pre> <h2 style="text-align:start">函数</h2> <p style="text-align:start">在Scala中,函数是一等公民。函数可以像类型一样被赋值给一个变量,也可以做为一个函数的参数被传入,甚至还可以做为函数的返回值返回。</p> <p style="text-align:start">从Java 8开始,Java也具备了部分函数式编程特性。其Lamdba函数允许将一个函数做值赋给变量、做为方法参数、做为函数返回值。</p> <p style="text-align:start">在Scala中,使用<code>def</code>关键ygnk来定义一个函数方法:</p> <pre> scala> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">calc</span>(</span>n1: Int, n2: Int): (Int, Int) = { | (n1 + n2, n1 * n2) | } calc: (n1: Int, n2: Int)(Int, Int) scala> <span style="color:rgb(204, 153, 204)">val</span> (add, sub) = calc(<span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">1</span>) add: Int = <span style="color:rgb(249, 145, 87)">6</span> sub: Int = <span style="color:rgb(249, 145, 87)">5</span> </pre> <p style="text-align:start">这里定义了一个函数:<code>calc</code>,它有两个参数:<code>n1</code>和<code>n2</code>,其类型为:<code>Int</code>。<code>cala</code>函数的返回值类型是一个有两个元素的元组,在Scala中可以简写为:<code>(Int, Int)</code>。在Scala中,代码段的最后一句将做为函数返回值,所以这里不需要显示的写<code>return</code>关键字。</p> <p style="text-align:start">而<code>val (add, sub) = calc(5, 1)</code>一句,是Scala中的抽取功能。它直接把<code>calc</code>函数返回的一个<code>Tuple2</code>值赋给了<code>add</code>他<code>sub</code>两个变量。</p> <p style="text-align:start">函数可以赋给变量:</p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> calcVar = calc _ calcVar: (Int, Int) => (Int, Int) = <function2> scala> calcVar(<span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>) res4: (Int, Int) = (<span style="color:rgb(249, 145, 87)">5</span>,<span style="color:rgb(249, 145, 87)">6</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> sum: (Int, Int) => Int = (x, y) => x + y sum: (Int, Int) => Int = <function2> scala> sum(<span style="color:rgb(249, 145, 87)">5</span>, <span style="color:rgb(249, 145, 87)">7</span>) res5: Int = <span style="color:rgb(249, 145, 87)">12</span> </pre> <p style="text-align:start">在Scala中,有两种定义函数的方式:</p> <ol> <li>将一个现成的函数/方法赋值给一个变量,如:<code>val calcVar = calc _</code>。下划线在此处的含意是将函数赋给了变量,函数本身的参数将在变量被调用时再传入。</li> <li>直接定义函数并同时赋给变量,如:<code>val sum: (Int, Int) => Int = (x, y) => x + y</code>,在冒号之后,等号之前部分:<code>(Int, Int) => Int</code>是函数签名,代表<code>sum</code>这个函数值接收两个Int类型参数并返回一个Int类型参数。等号之后部分是函数体,在函数函数时,<code>x</code>、<code>y</code>参数类型及返回值类型在此可以省略。</li> </ol> <p style="text-align:start"><strong>一个函数示例:自动资源管理</strong></p> <p style="text-align:start">在我们的日常代码中,资源回收是一个很常见的操作。在Java 7之前,我们必需写很多的<code>try { ... } finally { xxx.close() }</code>这样的样版代码来手动回收资源。Java 7开始,提供了<strong>try with close</strong>这样的自动资源回收功能。Scala并不能使用Java 7新加的<strong>try with close</strong>资源自动回收功能,但Scala中有很方便的方式实现类似功能:</p> <pre> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">using</span>[</span>T <: AutoCloseable, R](res: T)(func: T => R): R = { <span style="color:rgb(204, 153, 204)">try</span> { func(res) } <span style="color:rgb(204, 153, 204)">finally</span> { <span style="color:rgb(204, 153, 204)">if</span> (res != <span style="color:rgb(249, 145, 87)">null</span>) res.close() } } <span style="color:rgb(204, 153, 204)">val</span> allLine = using(Files.newBufferedReader(Paths.get(<span style="color:rgb(153, 204, 153)">"/etc/hosts"</span>))) { reader => @tailrec <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">readAll</span>(</span>buffer: StringBuilder, line: String): String = { <span style="color:rgb(204, 153, 204)">if</span> (line == <span style="color:rgb(249, 145, 87)">null</span>) buffer.toString <span style="color:rgb(204, 153, 204)">else</span> { buffer.append(line).append('\n') readAll(buffer, reader.readLine()) } } readAll(<span style="color:rgb(204, 153, 204)">new</span> StringBuilder(), reader.readLine()) } println(allLine) </pre> <p style="text-align:start"><code>using</code>是我们定义的一个自动化资源管帮助函数,它接爱两个参数化类型参数,一个是实现了<code>AutoCloseable</code>接口的资源类,一个是形如:<code>T => R</code>的函数值。<code>func</code>是由用户定义的对<code>res</code>进行操作的函数代码体,它将被传给<code>using</code>函数并由<code>using</code>代执行。而<code>res</code>这个资源将在<code>using</code>执行完成返回前调用<code>finally</code>代码块执行<code>.close</code>方法来清理打开的资源。</p> <p style="text-align:start">这个:<code>T <: AutoCloseable</code>范型参数限制了<strong>T</strong>类型必需为<code>AutoCloseable</code>类型或其子类。<code>R</code>范型指定<code>using</code>函数的返回值类型将在实际调用时被自动参数化推导出来。我们在<strong>Scala Console</strong>中参看<code>allLine</code>变量的类型可以看到 <code>allLine</code>将被正确的赋予<strong>String</strong>类型,因为我们传给<code>using</code>函数参数<code>func</code>的函数值返回类型就为<strong>String</strong>:</p> <pre> scala> :<span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">type</span> <span style="color:rgb(153, 153, 153)">allLine</span></span> String </pre> <p style="text-align:start">在<code>readAll</code>函数的定义处,有两个特别的地方:</p> <ol> <li>这个函数定义在了其它函数代码体内部</li> <li>它有一个<code>@tailrec</code>注解</li> </ol> <p style="text-align:start">在Scala中,因为函数是第一类的,它可以被赋值给一个变量。所以Scala中的<code>def</code>定义函数可以等价<code>val func = (x: Int, y: Int) => x + y</code>这个的函数字面量定义函数形式。所以,既然通过变量定义的函数可以放在其它函数代码体内,通过<code>def</code>定义的函数也一样可以放在其它代码体内,这和<strong>Javascript</strong>很像。</p> <p style="text-align:start"><code>@tailrec</code>注解的含义是这个函数是尾递归函数,编译器在编译时将对其优化成相应的<strong>while</strong>循环。若一个函数不是尾递归的,加上此注解在编译时将报错。</p> <h2 style="text-align:start">模式匹配(match case)</h2> <p style="text-align:start">模式匹配是函数式编程里面很强大的一个特性。</p> <p style="text-align:start">之前已经见识过了模式匹配的简单使用方式,可以用它替代:<strong>if else</strong>、<strong>switch</strong>这样的分支判断。除了这些简单的功能,模式匹配还有一系列强大、易用的特性。</p> <p style="text-align:start"><strong>match 中的值、变量和类型</strong></p> <pre> scala> <span style="color:rgb(204, 153, 204)">for</span> { | x <- Seq(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">false</span>, <span style="color:rgb(249, 145, 87)">2.7</span>, <span style="color:rgb(153, 204, 153)">"one"</span>, 'four, <span style="color:rgb(204, 153, 204)">new</span> java.util.Date(), <span style="color:rgb(204, 153, 204)">new</span> RuntimeException(<span style="color:rgb(153, 204, 153)">"运行时异常"</span>)) | } { | <span style="color:rgb(204, 153, 204)">val</span> str = x <span style="color:rgb(204, 153, 204)">match</span> { | <span style="color:rgb(204, 153, 204)">case</span> d: Double => s<span style="color:rgb(153, 204, 153)">"double: $d"</span> | <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(249, 145, 87)">false</span> => <span style="color:rgb(153, 204, 153)">"boolean false"</span> | <span style="color:rgb(204, 153, 204)">case</span> d: java.util.Date => s<span style="color:rgb(153, 204, 153)">"java.util.Date: $d"</span> | <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(249, 145, 87)">1</span> => <span style="color:rgb(153, 204, 153)">"int 1"</span> | <span style="color:rgb(204, 153, 204)">case</span> s: String => s<span style="color:rgb(153, 204, 153)">"string: $s"</span> | <span style="color:rgb(204, 153, 204)">case</span> symbol: Symbol => s<span style="color:rgb(153, 204, 153)">"symbol: $symbol"</span> | <span style="color:rgb(204, 153, 204)">case</span> unexpected => s<span style="color:rgb(153, 204, 153)">"unexpected value: $unexpected"</span> | } | println(str) | } int <span style="color:rgb(249, 145, 87)">1</span> boolean <span style="color:rgb(249, 145, 87)">false</span> double: <span style="color:rgb(249, 145, 87)">2.7</span> string: one symbol: 'four java.util.Date: Sun Jul <span style="color:rgb(249, 145, 87)">24</span> <span style="color:rgb(249, 145, 87)">16</span>:<span style="color:rgb(249, 145, 87)">51</span>:<span style="color:rgb(249, 145, 87)">20</span> CST <span style="color:rgb(249, 145, 87)">2016</span> unexpected value: java.lang.RuntimeException: 运行时异常 </pre> <p style="text-align:start">上面小试牛刀校验变量类型的同时完成类型转换功能。在Java中,你肯定写过或见过如下的代码:</p> <pre> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">public</span> <span style="color:rgb(204, 153, 204)">void</span> <span style="color:rgb(153, 204, 153)">receive</span><span style="color:rgb(249, 145, 87)">(message: Object)</span> </span>{ <span style="color:rgb(204, 153, 204)">if</span> (message isInstanceOf String) { String strMsg = (String) message; .... } <span style="color:rgb(204, 153, 204)">else</span> <span style="color:rgb(204, 153, 204)">if</span> (message isInstanceOf java.util.Date) { java.util.Date dateMsg = (java.util.Date) message; .... } .... } </pre> <p style="text-align:start">对于这样的代码,真是辣眼睛啊~~~。</p> <p style="text-align:start"><strong>序列的匹配</strong></p> <pre> scala> <span style="color:rgb(204, 153, 204)">val</span> nonEmptySeq = Seq(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> emptySeq = Seq.empty[Int] scala> <span style="color:rgb(204, 153, 204)">val</span> emptyList = Nil scala> <span style="color:rgb(204, 153, 204)">val</span> nonEmptyList = List(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> nonEmptyVector = Vector(<span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(249, 145, 87)">3</span>, <span style="color:rgb(249, 145, 87)">4</span>, <span style="color:rgb(249, 145, 87)">5</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> emptyVector = Vector.empty[Int] scala> <span style="color:rgb(204, 153, 204)">val</span> nonEmptyMap = Map(<span style="color:rgb(153, 204, 153)">"one"</span> -> <span style="color:rgb(249, 145, 87)">1</span>, <span style="color:rgb(153, 204, 153)">"two"</span> -> <span style="color:rgb(249, 145, 87)">2</span>, <span style="color:rgb(153, 204, 153)">"three"</span> -> <span style="color:rgb(249, 145, 87)">3</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> emptyMap = Map.empty[String, Int] scala> <span style="color:rgb(102, 153, 204)"><span style="color:rgb(204, 153, 204)">def</span> <span style="color:rgb(153, 204, 153)">seqToString</span>[</span>T](seq: Seq[T]): String = seq <span style="color:rgb(204, 153, 204)">match</span> { | <span style="color:rgb(204, 153, 204)">case</span> head +: tail => s<span style="color:rgb(153, 204, 153)">"$head +: "</span> + seqToString(tail) | <span style="color:rgb(204, 153, 204)">case</span> Nil => <span style="color:rgb(153, 204, 153)">"Nil"</span> | } scala> <span style="color:rgb(204, 153, 204)">for</span> (seq <- Seq( | nonEmptySeq, emptySeq, nonEmptyList, emptyList, | nonEmptyVector, emptyVector, nonEmptyMap.toSeq, emptyMap.toSeq)) { | println(seqToString(seq)) | } <span style="color:rgb(249, 145, 87)">1</span> +: <span style="color:rgb(249, 145, 87)">2</span> +: <span style="color:rgb(249, 145, 87)">3</span> +: <span style="color:rgb(249, 145, 87)">4</span> +: <span style="color:rgb(249, 145, 87)">5</span> +: Nil Nil <span style="color:rgb(249, 145, 87)">1</span> +: <span style="color:rgb(249, 145, 87)">2</span> +: <span style="color:rgb(249, 145, 87)">3</span> +: <span style="color:rgb(249, 145, 87)">4</span> +: <span style="color:rgb(249, 145, 87)">5</span> +: Nil Nil <span style="color:rgb(249, 145, 87)">1</span> +: <span style="color:rgb(249, 145, 87)">2</span> +: <span style="color:rgb(249, 145, 87)">3</span> +: <span style="color:rgb(249, 145, 87)">4</span> +: <span style="color:rgb(249, 145, 87)">5</span> +: Nil Nil (one,<span style="color:rgb(249, 145, 87)">1</span>) +: (two,<span style="color:rgb(249, 145, 87)">2</span>) +: (three,<span style="color:rgb(249, 145, 87)">3</span>) +: Nil Nil </pre> <p style="text-align:start">模式匹配能很方便的抽取序列的元素,<code>seqToString</code>使用了模式匹配以递归的方式来将序列转换成字符串。<code>case head +: tail</code>将序列抽取成“头部”和“非头部剩下”两部分,<code>head</code>将保存序列第一个元素,<code>tail</code>保存序列剩下部分。而<code>case Nil</code>将匹配一个空序列。</p> <p style="text-align:start"><strong>case class的匹配</strong></p> <pre> scala> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">trait</span> <span style="color:rgb(153, 153, 153)">Person</span></span> scala> <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Man</span>(</span>name: String, age: Int) <span style="color:rgb(204, 153, 204)">extends</span> Person scala> <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Woman</span>(</span>name: String, age: Int) <span style="color:rgb(204, 153, 204)">extends</span> Person scala> <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(153, 204, 153)"><span style="color:rgb(204, 153, 204)">class</span> <span style="color:rgb(153, 153, 153)">Boy</span>(</span>name: String, age: Int) <span style="color:rgb(204, 153, 204)">extends</span> Person scala> <span style="color:rgb(204, 153, 204)">val</span> father = Man(<span style="color:rgb(153, 204, 153)">"父亲"</span>, <span style="color:rgb(249, 145, 87)">33</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> mather = Woman(<span style="color:rgb(153, 204, 153)">"母亲"</span>, <span style="color:rgb(249, 145, 87)">30</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> son = Man(<span style="color:rgb(153, 204, 153)">"儿子"</span>, <span style="color:rgb(249, 145, 87)">7</span>) scala> <span style="color:rgb(204, 153, 204)">val</span> daughter = Woman(<span style="color:rgb(153, 204, 153)">"女儿"</span>, <span style="color:rgb(249, 145, 87)">3</span>) scala> <span style="color:rgb(204, 153, 204)">for</span> (person <- Seq[Person](father, mather, son, daughter)) { | person <span style="color:rgb(204, 153, 204)">match</span> { | <span style="color:rgb(204, 153, 204)">case</span> Man(<span style="color:rgb(153, 204, 153)">"父亲"</span>, age) => println(s<span style="color:rgb(153, 204, 153)">"父亲今年${age}岁"</span>) | <span style="color:rgb(204, 153, 204)">case</span> man: Man <span style="color:rgb(204, 153, 204)">if</span> man.age < <span style="color:rgb(249, 145, 87)">10</span> => println(s<span style="color:rgb(153, 204, 153)">"man is $man"</span>) | <span style="color:rgb(204, 153, 204)">case</span> Woman(name, <span style="color:rgb(249, 145, 87)">30</span>) => println(s<span style="color:rgb(153, 204, 153)">"${name}今年有30岁"</span>) | <span style="color:rgb(204, 153, 204)">case</span> Woman(name, age) => println(s<span style="color:rgb(153, 204, 153)">"${name}今年有${age}岁"</span>) | } | } 父亲今年<span style="color:rgb(249, 145, 87)">33</span>岁 母亲今年有<span style="color:rgb(249, 145, 87)">30</span>岁 man is Man(儿子,<span style="color:rgb(249, 145, 87)">7</span>) 女儿今年有<span style="color:rgb(249, 145, 87)">3</span>岁 </pre> <p style="text-align:start">在模式匹配中对<code>case class</code>进行<strong>解构</strong>操作,可以直接提取出感兴趣的字段并赋给变量。同时,模式匹配中还可以使用<strong>guard</strong>语句,给匹配判断添加一个<code>if</code>表达式做条件判断。</p> <h2 style="text-align:start">并发</h2> <p style="text-align:start">Scala是对多核和并发编程的支付做得非常好,它的Future类型提供了执行异步操作的高级封装。</p> <p style="text-align:start">Future对象完成构建工作以后,控制权便会立刻返还给调用者,这时结果还不可以立刻可用。Future实例是一个句柄,它指向最终可用的结果值。不论操作成功与否,在future操作执行完成前,代码都可以继续执行而不被阻塞。Scala提供了多种方法用于处理future。</p> <pre> scala> :paste <span style="color:rgb(153, 153, 153)">// Entering paste mode (ctrl-D to finish)</span> <span style="color:rgb(204, 153, 204)">import</span> scala.concurrent.duration.Duration <span style="color:rgb(204, 153, 204)">import</span> scala.concurrent.{Await, Future} <span style="color:rgb(204, 153, 204)">import</span> scala.concurrent.ExecutionContext.Implicits.global <span style="color:rgb(204, 153, 204)">val</span> futures = (<span style="color:rgb(249, 145, 87)">0</span> until <span style="color:rgb(249, 145, 87)">10</span>).map { i => Future { <span style="color:rgb(204, 153, 204)">val</span> s = i.toString print(s) s } } <span style="color:rgb(204, 153, 204)">val</span> future = Future.reduce(futures)((x, y) => x + y) <span style="color:rgb(204, 153, 204)">val</span> result = Await.result(future, Duration.Inf) <span style="color:rgb(153, 153, 153)">// Exiting paste mode, now interpreting.</span> <span style="color:rgb(249, 145, 87)">0132564789</span> scala> <span style="color:rgb(204, 153, 204)">val</span> result = Await.result(future, Duration.Inf) result: String = <span style="color:rgb(249, 145, 87)">0123456789</span> </pre> <p style="text-align:start">上面代码创建了10个<code>Future</code>对象,<code>Future.apply</code>方法有两个参数列表。第一个参数列表包含一个需要并发执行的命名方法体(by-name body);而第二个参数列表包含了隐式的<code>ExecutionContext</code>对象,可以简单的把它看作一个线程池对象,它决定了这个任务将在哪个异步(线程)执行器中执行。<code>futures</code>对象的类型为<code>IndexedSeq[Future[String]]</code>。本示例中使用<code>Future.reduce</code>把一个<code>futures</code>的<code>IndexedSeq[Future[String]]</code>类型压缩成单独的<code>Future[String]</code>类型对象。<code>Await.result</code>用来阻塞代码并获取结果,输入的<code>Duration.Inf</code>用于设置超时时间,这里是无限制。</p> <p style="text-align:start">这里可以看到,在<code>Future</code>代码内部的<code>println</code>语句打印输出是无序的,但最终获取的<code>result</code>结果却是有序的。这是因为虽然每个<code>Future</code>都是在线程中无序执行,但<code>Future.reduce</code>方法将按传入的序列顺序合并结果。</p> <p style="text-align:start">除了使用<code>Await.result</code>阻塞代码获取结果,我们还可以使用事件回调的方式异步获取结果。<code>Future</code>对象提供了几个方法通过回调将执行的结果返还给调用者,常用的有:</p> <ol> <li>onComplete: PartialFunction[Try[T], Unit]:当任务执行完成后调用,无论成功还是失败</li> <li>onSuccess: PartialFunction[T, Unit]:当任务成功执行完成后调用</li> <li>onFailure: PartialFunction[Throwable, Unit]:当任务执行失败(异常)时调用</li> </ol> <pre> <span style="color:rgb(204, 153, 204)">import</span> scala.concurrent.Future <span style="color:rgb(204, 153, 204)">import</span> scala.util.{Failure, Success} <span style="color:rgb(204, 153, 204)">import</span> scala.concurrent.ExecutionContext.Implicits.global <span style="color:rgb(204, 153, 204)">val</span> futures = (<span style="color:rgb(249, 145, 87)">1</span> to <span style="color:rgb(249, 145, 87)">2</span>) map { <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(249, 145, 87)">1</span> => Future.successful(<span style="color:rgb(153, 204, 153)">"1是奇数"</span>) <span style="color:rgb(204, 153, 204)">case</span> <span style="color:rgb(249, 145, 87)">2</span> => Future.failed(<span style="color:rgb(204, 153, 204)">new</span> RuntimeException(<span style="color:rgb(153, 204, 153)">"2不是奇数"</span>)) } futures.foreach(_.onComplete { <span style="color:rgb(204, 153, 204)">case</span> Success(i) => println(i) <span style="color:rgb(204, 153, 204)">case</span> Failure(t) => println(t) }) Thread.sleep(<span style="color:rgb(249, 145, 87)">2000</span>) </pre> <p style="text-align:start"><code>futures.onComplete</code>方法是一个偏函数,它的参数是:<code>Try[String]</code>。<code>Try</code>有两个子类,成功是返回<code>Success[String]</code>,失败时返回<code>Failure[Throwable]</code>,可以通过模式匹配的方式获取这个结果。</p> <h2 style="text-align:start">总结</h2> <p style="text-align:start">本篇文章简单的介绍了Scala的语言特性,本文并不只限于Java程序员,任何有编程经验的程序员都可以看。现在你应该对Scala有了一个基础的认识,并可以写一些简单的代码了。我在博客中分享了一些《Scala实战(系列)》文章,介绍更<strong>函数式</strong>的写法及与实际工程中结合的例子。也欢迎对Scala感兴趣的同学与我联系经,一起交流、学习。</p> <p style="text-align:start"><em>原文链接:<a href="/misc/goto?guid=4959675702405521709" style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 18px; vertical-align: baseline; color: rgb(59, 141, 189); text-decoration: none;">http://www.yangbajing.me/2016/07/24/写给java程序员的scala入门教程/</a></em></p>