为Play初学者准备的Scala基础知识

1 前言

本文的主要目的是为了让Play Framework的初学者快速了解Scala语言,算是一篇Play Framework的入门前传吧。
使用PlayFramework可以极大的提高开发效率,但是需要注意,PlayJava入门很简单,我之前带过一个实习小姑娘,有一点编程经验,但从来没有接触过PlayJava,然而一周入门,一个月独立完成项目。但是PlayScala没那么简单,虽然后者的开发效率更高,但是由于Scala程序员匮乏,PlayScala只适合团队较小(10人以下)并且较稳定的情况下使用。其实有很多人怀疑,Scala到底能提高多少开发效率,这里有一行Scala代码,大家可以先体会一下:

Source.fromFile("D:/f.txt", "UTF-8").getLines().toList.distinct.sortBy(s => (s.charAt(0), s.length)).foreach( println _)

虽然只有一行代码,但是却做了很多事情:以UTF-8编码读取文件所有行 -> 去重 -> 按首字符排序,首字符相同按长度排序 -> 打印结果。各位脑补一下Java的实现。更多的一行代码请查看酷炫的一行代码 - Scala就是这么任性!。下面我们进入正题,先看Scala语言简介。

2 Scala简介

提到编程语言,大家的第一反应通常是面向对象编程(OOP), 然而随着硬件服务器CPU核数和个数越来越多,函数式编程(FP)语言又重新回到了人们的视线。两种编程语言都各有特点,面向对象编程符合人类对世界的认知,更容易理解;函数式编程的语法更接近人类语言,简洁高效。两种语言都让人无法取舍。而Scala将这两种编程语言完美的融合到一起,形成一门更加强大的JVM语言,同时Scala修正了Java很多不合理的设计,新增了更多高级特性,学习Scala的同时也是对Java的一次深度回顾,让你对编程语言的理解更加地深刻。与Java相比,Scala的设计更加一致:

  • 一切都是对象

    1.toDouble  //可以直接调用基本类型上的方法
    "1".toInt //将字符串转换成整型
  • 一切都是方法
    "a" * 3 //等价于: "a".*(3)
    2 - 1   //等价于: 2.-(1)
  • 一切都是表达式
    val i = if(true){ 1 } else { 0 } // i = 1

Scala拥有一套强大的类型推导系统,所以你可以像动态类型语言那样编码,降低代码冗余度,减少无意义击键次数同时,代码也显得更加清晰易懂。
另外Java和Scala对待程序员的态度也很有意思,这里只是开个玩笑,大家别太在意。Java认为他所面对的程序员是一帮小白,容易犯错误,所以想方设法的限制你,避免你犯错;而Scala则认为他所面对的程序员是一帮天才,所以尽可能的向他敞开编程语言宝库,给他更大的自由度去想象和创作。

3 基本语法规则

3.1 变量声明

val用于定义不可变变量,var用于定义可变变量,这里的"可变"指的是引用的可变性。val定义的变量类似于Java的final变量,即变量只能赋一次值:

val msg = "hello" // 等价于:val msg: String = "hello"
var i = 1         // 等价于:var   i: Int = 1
i = i + 1

变量后面的类型声明可以省略,每行代码末尾的分号";"也可以省略。

3.2 函数声明

def用于定义函数:

def max(x: Int, y: Int): Int = {
    if (x > y) { x } else { y }
}
val maxVal = max(1, 2) // 2

Scala是函数式语言,所以你可以像基本类型那样把函数赋给一个变量:

val max = (x: Int, y: Int) => {
    if (x > y) { x } else { y }
}
val maxVal = max(1, 2) // 2

等号"="右边是一个匿名函数,也就是我们常说的Lambda函数,匿名函数由参数和函数体两部分组成,中间用"=>"隔开,这里省略了max变量的类型,因为编译器可以自动推断出来,完整的写法如下:

val max: (Int, Int) => Int = (x: Int, y: Int) => {
    if (x > y) { x } else { y }
}

max的类型是(Int, Int) => Int,即接受两个Int参数,产生一个Int返回值的函数类型。

3.3 class

Scala的class定义和Java很相似:

class Counter {
    private var value = 0 //你必须初始化字段
    def increment() { value += 1} //方法默认public
    def current() = value
}

Scala的源文件中可以定义多个类,并且默认都是public,所以外界都可以看见。class的使用也很简单:

val myCounter = new Counter //或new Counter()
myCounter.increment()
println(myCounter.current)  //或myCounter.current()

Scala中如果对象方法或类的构造器没有参数,则括号"()"可以省略。

3.4 object

Scala没有静态方法和静态字段,而是提供了object对象,也就是Java中的单例对象,即全局只有一个实例。

object Accounts {
    private var lastNumber = 0
    def newUniqueNumber() = { lastNumber += 1; lastNumber }
}

因为Accounts是一个单例对象,可以直接使用而无需初始化:

val uniqueNumber = Accounts.newUniqueNumber

object的另一个用法是作为类的伴生对象, 类似于Java类上的静态方法,只不过Scala将Java类上的静态功能全交给object实现了。object作为伴生对象时必须和类在同一个源文件中定义,并且可以相互访问私有属性。

3.5 apply方法

如果某个对象obj上定义了apply方法,则我们可以这样调用:

obj(arg1, ... , argn)

是的,你猜对了,伴生对象上的apply方法立马就派上用场了,例如List类有一个同名的伴生对象List,那么你可以这样初始化一个列表:

val list = List("a", "b", "c")

想想下面的Java版本,是不是感觉幸福感油然而生:

List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");

3.6 块表达式

在Scala中一切都是表达式,如果表达式含有多条语句,则使用大括号"{}"括起来,形成一个块表达式,块表达式的最后一条语句的值作为整个块的返回值。

val r = {
    val i = 1
    val j = 2
    i + j
} // r = 3

4 case class和模式匹配

在Scala中接触到新概念不要害怕,了解之后你会发现它帮你解决了很多实际问题,就如我们这里要聊的case class和模式匹配。定义一个case class的代码如下:

case class Currency(value: Double, unit: String)

当你定义了一个case class之后,编译器会自动帮你做如下事情:

  • 自动创建伴生对象
  • 为该类添加toString,hashCode和euqals方法,用于模式匹配时的结构化比较
  • 为该类添加copy方法,用于快速拷贝对象

好了,下面我们来看一下模式匹配的威力:

abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
val amount = Currency(100.0, "EUR")
val amountStr =
    amount match {
        case Dollar(v) => "$" + v
        case Currency(v, u) => "I got " + v + u
        case _ => ""
    }

在Scala中,类、函数、方法和object可以像变量一样在任何地方定义。

Scala中默认使用的类都是不可变的,所以如果你想改变value的值需要借助copy方法:

val newAmound = amount.copy(value = 1000.0)

Scala中的模式匹配还可以实现更复杂的匹配,详见"Programming in Scala, 3nd Edition"。如果说Java中的switch是一把手枪,那么Scala中的模式匹配是一架当之无愧的战头机。

5 map和flatMap

可能有很多人就是因为这两个方法才迷恋上Scala的。map和flatMap是两个高阶函数,所谓高阶函数就是接受函数作为参数的函数。这两个方法各自接受一个一元函数(即只有一个参数的函数,类型为:(A) => B),利用这个一元函数,你可以对数据流中的每一个元素进行一些操作或转换,最终得到一个全新的数据流。
map方法接受的一元函数类型为:(A) => B:

List(1, 2, 3).map((i: Int) => { i + 1 }) // List(2, 3, 4)

也可以简写如下两种形式:

List(1, 2, 3).map(i => i + 1 )
List(1, 2, 3).map(_ + 1 )

你可以把第2种形式中的下划线理解成每个元素的占位符,其实这只是编译器的语法糖,编译后的结果和前两种写法相同。使用这个语法糖的前提是下划线"_"在函数体内只能出现一次。

在上面的例子里,map方法接受的一元函数类型是:(Int) => Int,元素的类型没有发生改变,我们可以尝试改变元素类型:

List(1, 2, 3).map(i => i.toString * i) // List(1, 22, 333)

这次传入的一元函数类型是: (Int) => String,将原List从List[Int]类型转换成了List[String]类型,完成一次数据流类型转换。

flatMap方法接受的一元函数类型为:(A) => List[B],我们发现该一元函数返回的类型也是一个List,flatMap方法会自动将由每个元素A转换成的小List[B]展平成一个大的List[B],这也是flatMap中的"flat"所要表达的意思:

List(1, 2, 3).flatMap(i => List(i, i)) // List(1, 1, 2, 2, 3, 3)

这里我们只在List上演示了map和flatMap的基本用法,Scala中所有的容器类型(例如Option, Either, Future, Set, ...)都内置了这两个方法。除了map和flatMap,Scala的容器类型上还有很多类似的方法,例如filter, find, sortBy等等,详见"Programming in Scala, 3nd Edition"。

6 常用类介绍

6.1 String

在Scala中,String更加方便好用:

//原始字符串一对三引号`"""`括起来,可包含多行字符串,内容不需要转义
"""Welcome here.
   Type "HELP" for help!"""

//类型转换
"100.0".toDouble

//判断字符串相等直接用"==",而不需要使用equals方法
val s1 = new String("a")
s1 == "a" // true

//字符串去重
"aabbcc".distinct // "abc"

//取前n个字符,如果n大于字符串长度返回原字符串
"abcd".take(10) // "abcd"

//字符串排序
"bcad".sorted // "abcd"

//过滤特定字符
"bcad".filter(_ != ‘a‘) // "bcd"

//字符串插值, 以s开头的字符串内部可以直接插入变量,方便字符串构造
val i = 100
s"i=${i}" // "i=100"

Scala中没有受检异常(checked exception),所以你没有必要声明受检异常,如果真的发生异常,则会在运行时抛出。

6.2 Option

Scala用Option类型表示一个值是否存在,用来避免Java的NullPointerException。它有两个子类:Some和None。Some类型表示值存在,None类型则表示值不存在。
常用操作:

val opt: Option[String] = Some("hello")
//判断是否为None
opt.isEmpty // false
//如果为None,则返回默认值"default",否则返回opt持有的值
opt.getOrElse("default")
//如果为None则返回"DEFAULT",否则将字符转为大写
opt.fold("DEFAULT"){ value => value.toUpperCase } // "HELLO"
//功能同上
opt match {
  case Some(v) => v.toUpperCase
  case None => "DEFAULT"
}

6.3 List

在Scala中,List要么是Nil(空列表),要么就是由head和tail组成的递归结构。 head是首元素,tail是剩下的List。所以你可以这样构建List:

val list = 1 :: Nil // 等价于:val list = List(1)

连续的两个冒号"::"就像是胶水,将List的head和tail粘在一起。
常用操作:

val list = List(1, 3, 2)
//获取第1个元素
list.headOption.getOrElse(0) // 1
//查找
list.find(_ % 2 == 0).getOrElse(0) // 2
//过滤
list.filter(_ % 2 == 1) // List(1, 3)
//排序
list.sorted // List(1, 2, 3)
//最小值/最大值/求和
list.min // 1
list.max // 3
list.sum // 6
//转化成字符串
list.mkString(",") // "1, 3, 2"

Scala提供的List基本可以实现SQL查询的所有功能,这也是Spark为什么基于Scala开发的原因。更多功能请参考官方文档

在Scala中默认的集合类例如List,Set,Map,Tuple等都是不可变的,所以调用其修改方法会返回一个新的实例。如果要使用可变集合,请使用scala.collection.mutable包下相应的类。不可变类型在编写并发代码时很有用。

6.4 Tuple

Tuple(元组)Tuple可以容纳不同类型的元素,最简单的形态是二元组,即由两个元素构成的Tuple, 可以使用_1, _2等方法访问其元素:

val t = ("a", 1) // 等价于:val t: Tuple2[String, Int] = ("a", 1)
t._1 // "a"
t._2 // 1

也可以使用模式匹配利用Tuple同时初始化一组变量:

val t = ("a", 1)
val (v1, v2) = t
v1 // "a"
v2 // 1

6.5 Map

Map其实是二元组的集合:

val map = Map("a" -> 1, "b" -> 2)

"->"其实是String类型上的方法,返回一个二元组:

"a" -> 1 //等价于: ("a", 1)

所以你也可以这样构建Map:

val map = Map(("a", 1), ("b", 2))

常用操作:

val map = Map("a" -> 1, "b" -> 2)
//读取
map("a") // 1
//写入或添加键值
map("a") = 0
//删除键值
map - "a" // Map(b -> 2)

7 控制结构

7.1 if

if语句同样是表达式,拥有返回值:

val i = 1
val r = if(i > 0){ 1 } else { 0 } // r = 1

7.2 for

Scala中for语句功能比Java要丰富很多,你可以使用for遍历一个List:

val list = List(1, 2, 3)
for(i <- list){
    println(i)
}

你也可以使用模式匹配遍历一个Map:

val map = Map(("a", 1), ("b", 2))
for((k, v) <- map){
  println(k + ": " + v)
}

如果循环体以yield开始,for语句会返回一个新的集合:

val newList1 = for(i <- List(1, 2, 3)) yield i * 2 // List(2, 4, 6)
val newList2 =
    for{
       i <- List(1, 2)
       j <- List(3, 4)
    } yield i + j //List(4, 5, 5, 6)

如果有多个集合需要遍历,则for语句后面的圆括号"()"要换成大括号"{}"。

8 Future和Promise

Future和Promise是Scala提供的最吸引人的特性之一,借助Future和Promise你可以轻松地编写完全异步非阻塞的代码,这在多处理器时代显得格外重要。

8.1 Future

Future用于获取异步任务的返回结果。Future有两种状态:完成(completed)和未完成(not completed)。处于完成状态的Future可能包含两种情况的信息,一种是异步任务执行成功了,Future中包含异步任务执行成功的返回结果;另一种是异步任务执行失败了,Future中包含了相应的Exception信息。Future的独特之处在于它的值只能被写入一次,之后就会变为一个不可变值,其中包含成功或失败信息。你可以在Future上注册一个回调函数,以便在任务执行完成后得到通知:

import scala.concurrent.ExecutionContext.Implicits.global
val f = Future{ 1 + 2 }
f.onComplete{ t =>
  t match{
    case Success(v) => println("success: " + v)
    case Failure(t) => println("failed: " + t.getMessage)
  }
}
//等待任务结束
Await.ready(f, 10 seconds)

onComplete方法接受一个一元函数,类型为:Try[T] => U。Try类型和Option类型很像,也有两个子类SuccessFailure,前者表示任务执行成功,后者表示任务执行失败。

第1行import语句导入了一个隐式的ExecutionContext,你可以把它理解成是一个线程池,Future类在需要时会自动使用其上的线程。在Scala中你不需要直接和线程打交道。

由于Future也是一个容器类,所以可以使用for语句取回它的值:

val f = Future{ 1 + 2 }
for(v <- f) {
    println(v) // 3
}

也可以使用map方法对任务结果进行转换:

val f1 = Future{ 1 + 2 }
val f2 = f1.map(v => v % 2)
for(v <- f2) {
    println(v) // 1
}

利用for语句可以等待多个Future的返回结果:

val f1 = Future{ 1 + 2 }
val f2 = Future{ 3 + 4 }
for{
    v1 <- f1
    v2 <- f2
} {
    println(v1 + v2) // 10
}

结合yield可以返回一个新的Future:

val f1 = Future{ 1 + 2 }
val f2 = Future{ 3 + 4 }
val f3 =
    for{
        v1 <- f1
        v2 <- f2
    } yield {
        v1 + v2
    }

8.2 Promise

有时我们需要精细地控制Future的完成时机和返回结果,也就是说我们需要一个控制Future的开关,没错,这个开关就是Promise。每个Promise实例都会有一个唯一的Future与之相关联:

val p = Promise[Int]()
val f = p.future
for(v <- f) { println(v) }

//3秒钟之后返回3
Thread.sleep(3000)
p.success(3)

//等待任务结束
Await.ready(f, 10 seconds)

9 小结

Scala在刚入门的时候确实有点难度,各种奇怪的语法、符号漫天飞,看的云里雾里。但是在你入门之后会发现,这些奇怪的地方其实是合理的,是一种有意的设计。例如允许方法名包含特殊符号,你可以写出下面的代码:

"a" * 3 // "aaa"
val map = Map("a" -> 1, "b" -> 2)

"*"和"->"其实是字符串上的两个方法,允许符号作为方法名使得代码直观易懂。由于Scala赋予程序员对代码很高的控制力,如果滥用就会导致天书般的代码,这需要团队内部进行协调,控制代码的复杂度。Scala之父Martin Odersky也曾经表示会在2016简化Scala语言,降低初学者的门槛。到时会有更多的人加入这个社区,一起分享编程的乐趣。

10 参考

  • "Programming in Scala, 3nd Edition"
  • "快学Scala"

11 附录

11.1 开发工具推荐

IntelliJ IDEA + Scala插件

11.2 转载声明

转载请注明作者joymufeng

时间: 2024-11-05 22:58:00

为Play初学者准备的Scala基础知识的相关文章

Scala学习(1)——Scala基础知识

本文要解决的问题: Spark主要是由Scala语言编写而成的,所以要真正深入了解Spark,必须要熟悉Scala,在此结合阅读<Scala编程>这本书的情况,对Scala语言做一个基本的总结. Scala的优势 (1)简洁 类型推断 函数创建的文法支持 (2)Java互操作性 可重用Java库 可重用Java工具 没有性能惩罚 Scala工作机制 编译成Java字节码 可在任何标准JVM上运行,甚至是一些不规范的JVM上 Scala编译器是Java编译器的作者写的 启动解释器 输入Scala

scala 基础知识总结

在最开始处引入 log 相关的 包 import org.apache.log4j.{Logger,Level} 在需要屏蔽日志输出的地方加上这两行代码 // 屏蔽不必要的日志显示在终端上 Logger.getLogger("org.apache.spark").setLevel(Level.ERROR) Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF) // scala io Case

UI设计初学者教程:色彩基础知识

编辑:千锋UI设计 初学设计都会先认识三原色,通常我们说的三原色指的是颜料三原色:红.黄.蓝:其实三原色还有色光三原色:红.绿.蓝.我们通常说的红黄蓝就是减色法三原色,而红绿蓝是加色法三原色.可能这么说有点蒙,简单来说就是CMYK(印刷色)和RGB(屏幕色)的区别. 初学设计都会先认识三原色,通常我们说的三原色指的是颜料三原色:红.黄.蓝:其实三原色还有色光三原色:红.绿.蓝.我们通常说的红黄蓝就是减色法三原色,而红绿蓝是加色法三原色.可能这么说有点蒙,简单来说就是CMYK(印刷色)和RGB(屏

【转】Scala基础知识

原文地址.续 课程内容: 关于这节课 表达式 值 函数 类 继承 特质 类型 apply方法 单例对象 函数即对象 包 模式匹配 样本类 try-catch-finally 关于这节课 最初的几个星期将涵盖基本语法和概念,然后我们将通过更多的练习展开这些内容. 有一些例子是以解释器交互的形式给出的,另一些则是以源文件的形式给出的. 安装一个解释器,可以使探索问题空间变得更容易. 为什么选择 Scala? ·表达能力     ·函数是一等公民     ·闭包 ·简洁     ·类型推断     ·

[Scala] Scala基础知识

Object An object is a type of class that can have no more than one instance, known in object-oriented design as a singleton. Instead of creating an instance with a new keyword, just access the object directly by name. Objects provide similar "static&

Scala基础知识

1.scala的变量分为可变变量和不可变变量 不可变变量: val hello = "helloworld" 可变变量的定义方法 var str2 = "我是kw!" 不可变变量相当于java中的final关键字修饰的数据,可变变量相当于java中的变量,对于scala语言而言,更希望使用的val的数据. 2.数据类型的位置,变量在前数据类型在后 val Str3:String ="hello" print(Str3) 数据在定义的时候,需要给他

3.Scala基础知识

一.基本数据类型和变量 1.基本数据类型 java中每一个数据类型都是一个类: scala没有自己定义String类型,String类型是从java.lang.String照搬的. 字面量(literal) 2.变量 同一个环境中,可以重复使用同样一个变量名,只会记录最后一次用的那个类型. 二.输入输出 1.输入 2.输出 3.读写文件 (1)写入文件 文件保存在Scala启动目录 (2)读取文件 三.控制结构 1.if-else 2.while和do-while 3.for循环 四.数据结构

Scala笔记整理(一):scala基本知识

[TOC] Scala简介 Scala是一门多范式(multi-paradigm)的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性. Scala运行在Java虚拟机上,并兼容现有的Java程序. Scala源代码被编译成Java字节码,所以它可以运行于JVM之上,并可以调用现有的Java类库. 函数编程范式更适合用于Map/Reduce和大数据模型,它摒弃了数据与状态的计算模型,着眼于函数本身,而非执行的过程的数据和状态的处理.函数范式逻辑清晰.简单,非常适合用于处理基于不变数据的

【RL-TCPnet网络教程】第35章 FTP文件传输协议基础知识

第35章      FTP文件传输协议基础知识 本章节为大家讲解FTP(File Transfer Protocol,文件传输协议)的基础知识,方便后面章节的实战操作. (本章的知识点主要整理自网络) 35.1  初学者重要提示 35.2  FTP基础知识参考资料 35.3  FTP基础知识点 35.4  总结 35.1  初学者重要提示 FTP文件传输协议在实际项目中有比较重要的实用价值,需要初学者对FTP的基础知识也有个认识. 35.2  FTP基础知识参考资料 大家可以从以下地址获得FTP