scala编程知识点简介

简介

scala是一门综合了面向对象和函数式编程概念的静态类型的编程语言。
函数式编程以两大核心理念为指导:第一个理念是函数是一等的值。(可以将函数作为参数传递给其他函数,作为返回值返回它们。)第二个核心理念是程序中的操作应该将输入值映射成输出值,而不是当场修改数据。不可变数据结构是函数式编程的基石之一。函数式编程的这个核心理念的另一种表述方法是不应该有副作用。方法只能通过接受入参和返回结果这两种方式与外部环境通信。

为什么要用Scala编程?

  1. 兼容性:Scala程序会被编译成jvm字节码。可以与Java无缝互调。
  2. 精简性:Scala的类型推断。
  3. 高级抽象。
  4. 静态类型。

快速入门

  1. 所有Java的基本类型在Scala包中都有对应的类。
    scala.Boolean对应Java的boolean,scala.Float对应Java的float。
    Scala的字符串是用Java的String类实现的。特质(trait)跟Java的接口类似。
    Scala的Unit类型跟Java的void类型类似。
  2. Scala变量分为两种:val和var。
    val和Java的final变量类似,一旦初始化就不能被重新赋值。而var则不同,类似于Java的非final变量,在整个生命周期内var可以被重新赋值。
    当你用val定义一个变量时,变量本身不能被重新赋值,但是它指向的那个对象是有可能发生改变的。
  3. Scala的类型推断能力,能够推断出那些不显式指定的类型
    Scala编译器并不会推断函数参数的类型
  4. 函数式编程与指令式编程
    e.g.args.foreach(println)
  5. Scala用圆括号而不是方括号来访问数组
    数组不过是类的实例,这一点跟其他Scala实例没有本质区别。
  6. 函数式编程的一个重要理念之一是方法不能有副作用。
    一个方法唯一要做的是计算并返回一个值。
  7. Scala数组是一个拥有相同类型的对象的可变序列。
    数组可变的对象。Scala的List是不可变的。元组也是不可变的。元组可以容纳不同类型的元素。
  8. 为什么不在列表的末尾追加元素?
    因为往末尾追加元素的操作所需要时间随着列表的大小线性增加。而使用:: 在列表的前面添加元素只需要常量的时间。
  9. 如果方法名的最后一个字符是冒号,该方法的调用会发生在它的右操作元上。
  10. 访问元组中的元素为什么是_N形式?
    背后的原因是列表的apply方法永远只返回同一种类型,但是元组中的元素可以是不同类型的。字段名是从1开始而不是0开始,这是由其他同样支持静态类型的元组的语言设定的传统。
  11. 集和映射
    set和map提供了可变和不可变两种选择。
                                     scala.collection
                                         Set/Map
                                         <<trait>>
                                               |
scala.collection.immutable              scala.collection.mutable
           Set /Map                                              Set/Map
          <<trait>>                                               <<trait>>
                |                                                            |
scala.collection.immutable              scala.collection.mutable
  HashSet/HashMap                           HashSet/HashMap
  1. 识别函数式编程风格
    一个向函数式风格转变的方向是尽量不用var。
  2. 从文件读取文本行
import scala.io.Source
Source.fromFile(fileName).getLines()
# 返回一个Iterator[String]

类和对象

Scala可以对分号进行自动推断。
字段保留了对象的状态,或者说数据,而方法用这些数据来对对象执行计算。
要将某个字段声明为私有,可以在字段前面加上private这个访问修饰符。
public是Scala的默认访问级别。
Scala方法的参数的一个重要的特征是它们都是val而不是var。
推荐的风格是避免使用任何显示的return语句。在没有任何显示的return语句时,Scala方法返回的是该方法计算出的最后一个表达式的值。
Scala类不允许由静态成员。Scala提供了单例对象。关键字是object。
当单例对象跟某个类共用同一个名字时,它被称为这个类的伴生对象。同时,类又叫做这个单例对象的伴生类。类和它的伴生对象可以互相访问对方的私有成员。不能实例化。直接调用伴生对象名。单例对象在有代码首次访问时才被初始化。没有同名的伴生类的单例对象称为孤立对象。
类和单例对象的一个区别是单例对象不接受参数,而类可以。
Scala在每个Scala源码文件都隐式的引入了java.lang和scala包的成员,以及名为Predef的单例对象的所有成员。
scala提供了一个特质scala.App。

基础类型和操作

Scala基础类型:java.lang.String, scala.Int, Long, Short, Byte, Float, Double, Char, Boolean。
字符串插值:s插值器:对内嵌的每个表达式求值,
raw插值器:不识别字符转义序列,
f插值器:可以进行格式化。
Scala提供了eq方法用于比较引用相等性的机制。
富包装类:scala.runtime.RichByte, scala.runtime.RichShort, ...
scala.collection.immutable.StringOps。

函数式对象

Scala编译器会将你在类定义体中给出的非字段或方法定义的代码编译进类的主构造方法中。
关键字this指向当前执行方法的调用对象,当被用在构造方法里的时候,指向被构造的对象实例。
Scala的辅助构造方法以def this(...)开始。在Scala中,每个辅助构造方法都必须首先调用同一个类的另一个构造方法。主构造方法就是类的单一入口。只有主构造方法可以调用超类的构造方法。

内建的控制结构

控制结构包括:if, while, do while, for, try catch finally, match case。
if表达式可以有返回值。val filename = if (!args.isEmpty) args(0) else "default.txt"
while循环没有返回值。
for循环可以有返回值。val forLineLengths = for {file <- filesHere if file.getName.endsWith(".scala") line <- fileLines(file) trimmed = line.trim if trimmed.matches(".*for.*") } yield trimmed.length
match 表达式会返回值。val friend = firstArg match { case "salt" => "pepper" case "chips" => "salsa" case "eggs" => "bacon" case _ => "huh?" } println(friend)

函数和闭包

匿名函数:(x: Int) => x + 1
部分应用的函数是一个表达式,在这个表达式中,并不给出函数需要的所有参数,而是给出部分,或完全不给。def sum(a: Int, b: Int, c: Int) = a + b + c; val a = sum _; a(1, 2, 3)
尾递归函数并不会在每次调用时构建一个新的栈帧,所有的调用都会在同一个栈帧中执行。

组合和继承

组合的意思是一个类可以包含对另一个类的引用,利用这个引用类来帮助它完成任务;而继承是超类\子类的关系。
一个包含抽象成员的类本身也要声明为抽象的,做法是在class关键字之前写上abstract修饰符。
一个方法只要没有实现,那么它就是抽象的。
继承的意思是超类的所有成员也是子类的成员,但是有两个例外。一个是超类的私有成员并不会被子类继承;二是如果子类里已经实现了相同名称和参数的成员,那么该成员不会被继承。
Scala要求在所有重写了父类具体成员的成员之前加上这个修饰符。
要调用超类的构造方法,只需将你打算传入的入参放在超类名称后的圆括号里即可。
多态的意思是多种形式。动态绑定的意思是说实际被调用的方法实现是在运行时基于对象的类决定的,而不是变量或者表达式的类型决定的。
工厂对象包含创建其他对象的方法。使用方用这些工厂方法来构造对象,而不是直接用new构建对象。

继承关系

在Scala中,每个类都继承自同一个名为Any的超类。Scala还在继承关系的底部定义了Null和Nothing,Nothing是每一个其他类的子类。

class Any() {
  final def ==(that: Any): Boolean
  final def !=(that: Any): Boolean
  def equales(that: Any): Boolean
  //
  def ##: Int  
  def hashCode:Int
  def toString: String
}

子类可以通过重写equals方法来定制==和!=的含义。
根类Any有两个子类: AnyVal和AnyRef。AnyVal是Scala中所有值类的父类,Scala提供了几个内建的值类:Byte、Short、Char、Int、Long、Float、Double、Boolean和Unit。前八个对应Java的基本类型。另外的Unit对应Java的void的类型。AnyRef是Scala所有引用类的基类。在Java平台上AnyRef事实上只是java.lang.Object的一个别名。AnyRef类定义了一个eq方法,该方法不能被重写,实现为引用相等性,还定义了一个反义的方法ne。
Null类是null引用的类型,它是每个引用类的子类。Nothing是每个其他类型的子类型。并不存在在个类型的任何值。Nothing的用途之一是给出非正常终止的信号。

def error(message: String): Nothing = {
  throw new RuntimeException(message)
}
def divide(x: Int, y: Int): Int = {
  if (y != 0) x / y
  else error("can't divide by zero")
}

特质

特质是Scala代码复用的基础单元。类可以同时混入任意数量的特质。可以使用extends或者with关键字将它混入类中。定义一个特质使用trait关键字。特质最常用的使用场景是将廋接口拓宽为富接口以及定义可叠加的修改。特质与Java的接口类似,但是功能更强大。
类与特质的区别是特质不能有任何的类参数,特质中的super的调用是动态绑定的。

包和引入

Scala的引入可以出现在任何地方。
Scala对每个程序都隐式的添加了一些引入。

import java.lang._
import scala._
import Predef._

标private的成员只在包含该定义的类或对象的内部可见,在Scala中,这个规则同样适用于内部类。
在Scala中,protected的成员只能从定义该成员的子类访问。

断言和测试

断言和测试是用来检查软件行为符合预期的两种重要手段。
在Scala中,断言的写法是对预定义方法assert的调用。如果条件condition不满足,表达式assert(condition)将抛出AssertionError。assert还有另一个版本,assert(condition, explanation),condition不满足,那么就抛出包含给定的explanation的AssertionError。
ensuring() 这个方法可以被用于任何结果类型。ensuring()是对返回结果行进行断言的方法。
用Scala写测试,有很多选择,例如:ScalaTest、specs2和ScalaCheck。

样例类和模式匹配

样例类是Scala用来对对象进行模式匹配而并不需要大量的样板代码的方式。带有case这种修饰符的类称作样例类。模式匹配包含一系列以case关键字打头的可选分支。
密封类除了在同一个文件中定义的子类之外,不能添加新的子类。只需要在类继承关系的顶部那个类的类名前面加上sealed关键字就成为了密封类。
Scala由一个名为Option的标准类型来表示可选值。这样的值可以有两种形式:Some(x),其中x是那个实际的值;或者None对象,代表没有值。将可选值解开最常见的方式是通过模式匹配。

可变对象

对于可变对象而言,方法调用或者字段访问的结果可能取决于之前这个对象被执行了那些操作。同样的操作在不同的时间会返回不同的结果。
在Scala中,每一个非私有的var成员都隐式的定义了对应的getter和setter方法。

类型参数化

泛型的意思是我们用一个泛化的类或者特质来定义许多具体的类型。在Scala中,泛型类型默认的子类型规则是不变的。在类型形参前面加上+表示子类型关系在这个参数上是协变的。加上-表示逆变的。类型参数是协变的、逆变的、不变的被称为类型参数的型变。+-被称为型变注解。用U >: T这样的语法定义了U的下界为T。用T <: U这样的语法定义了T的上界为U

抽象成员

如果类或者特质的某个成员在当前类中没有完整的定义,那么它就是抽象的。抽象成员的本意是为了让声明该成员的类的子类来实现。在Scala中,抽象成员包括抽象方法、抽象字段(val 、var)、抽象类型(type)。

trait Abstract {
  type T
  def transform(x: T): T
  val initial: T
  var current: T
}
#具体实现
class Concrete extends Abstract {
  type T = String
  def transform(x: String) = x + x
  val initial = "hi"
  var current = initial
}

创建新枚举的方式是定义一个扩展自scala.Enumeration类的对象。

object Direction extends Enumeration {
  val North = Value("North")
  val East = Value("East")
  val South = Value("South")
  val West = Value("West")
}
#引入枚举类的所有值
import Direction._
#遍历枚举的值
for (d <- Direction.values) print(d + " ")
#获取值的编号
Direction.East.id #1
#获取指定编号的值
Direction(1) #East

隐式转换与隐式参数

隐式定义指的是那些我们允许编译器插入程序以解决类型错误的定义。关键字是implicit。可以用implicit来标记任何变量、函数、类和对象定义。

implicit def intToString(x: Int) = x.toString

通常是把一些有用的隐式转换放到一个命名为Preamble对象中,这样就可以通过import Preamble._ 来访问这些隐式转换。
Scala总共有三个地方会使用到隐式定义:从一个类型转换到一个预期的类型;对某个成员选择接收端(即字段、方法调用等)的转换;隐式参数。

#隐式转换到一个预期的参数
implicit def doubleToInt(x: Double) = x.toInt
val i: Int = 3.5
#只要你看见了有人调用了接受类中不存在的方法,那么很可能使用了隐式转换
#隐式类是一个以implicit关键字打头的类
#隐式类必须存在于另一个对象、类或、特质中
#隐式参数
class PreferredPrompt(val preference: String)
object Greeter {
  def greet(name: String)(implicit prompt: PrefereedPrompt) = {
    println("Welcome, " + name + ". The system is ready.")
    println(prompt.preference)
  }
}
object JoesPrefs {
  implicit val prompt = new PreferredPrompt("Yes, Master> ")
}
import JoesPrefs._
#需要两个参数,但是只传入了一个参数,另一个参数是隐式传入的。
Greeter.greet("Joe")
#返回结果
Welcome, Joe. The system is ready.
Yes, master

当前作用域内由多个隐式转换都满足要求时,如果可用的转换当中有某个转换严格来说比其他的更具体,那么编译器就会选择这个更具体的转换。

实现列表

package scala
abstract class List[+T] {
  def isEmpty: Boolean
  def head: T
  def tail: List[T]
}
case object Nil extends List[Nothing] {
  override def isEmpty = true
  def head: Nothing = throw new NoSuchElementException("head of empty list")
  def tail: List[Nothing] = throw new NoSuchElementException("tail of empty list")
}
final case class ::[T](hd: U, private var tl: List[U]) extends List[U] {
  def head = hd
  def tail = tl
  override def isEmpty: Boolean = false
}
package scala.collection.immutable
final class ListBuffer[T] extends Buffer[T] {
  private var start: List[T] = Nil
  private var last0: ::[T] = _
  private var exported: Boolean = false
  ...
}

重访for表达式

所有最终交出(yield)结果的for表达式都会被编译器翻译成对高阶函数map、flatMap和withFilter的调用。所有不带yield的for循环会被翻译成更小集的高阶函数:只有withFilter和foreach。

深入集合类

#集合继承关系图
Traversable
    Iterable
        Seq
           IndexedSeq
               Vector
               ResizableArray
               GenericArray
          LinearSeq
               MutableList
               List
               Stream
          Buffer
              ListBuffer
              ArrayBuffer
        Set
            SortedSet
                TreeSet
            HashSet (可变的)
            LinkedHashSet (有序的)
            HashSet (不可变的)
            BitSet
            EmptySet, Set1, Set2, Set3, Set4
        Map
            SortedMap
                TreeMap
            HashMap (可变的)
            LinkedHashMap (可变的)
            HashMap (不可变的)
            EmptyMap, Map1, Map2, Map3, Map4
  1. Traversable特质:
    抽象方法:
    def foreach[U](f: Elem => U)
    具体方法:
    ++, map, flatMap, collect, toIndexedSeq, toIterable, toStream, toArray, toList, toSeq, toSet, toMap, isEmpty, nonEmpty, size, head, last, tail, init, slice, take, drop, filter, exists, sum, min, max mkString, addString, view, ...
  2. Iterable特质:
    该特质的所有方法都是用抽象方法iterator来定义的。
    重写foreach方法:
def foreach[U](f: Elem => U): Unit = {
  val it = iterator
  while (it.hasNext) f(it.next())
}

位于Iterable之下有三个特质: Seq, Set, Map.

  1. Seq特质:
    方法:
    apply, length, indexOf, +:, :+, updated, sorted, sortWith, sortBy, reverse, startsWith, endsWith, contains, diff, union, ...
    buffer:
    方法:
    +=, ++=, +=:, ++=:, insert, -=, remove, ...
  2. Set特质:
    是没有重复元素的Iterable。
    不可变集:
    方法:
    contains, apply, +, ++, -, --, union, diff, ...
    可变集:
    方法:
    +=, ++=, add, -=, --=, remove, ...
  3. Map特质:
    是有键值对组成的Iterable。
    不可变映射:
    方法:
    apply, get, getOrElse, contains, +, ++, updated, -, --, keys, keySet, values, ...
    可变映射:
    方法:
    +=, ++=, put, -=, --=, remove, ...
  4. List:
    是有限的不可变序列
  5. Stream:
    其元素是惰性计算的。可以无限长。
    val str = 1 #:: 2 #:: 3 #:: Stream.empty
  6. Vector:
    是对头部之外的元素也提供高效访问的集合类型。不可变。
    val vec = scala.collection.immutable.Vector.empty
    val vec2 = vec :+ 1 :+ 2
    val vec3 = 100 +: vec2
  7. Stack:
    不可变:
    val stack = scala.collection.immutable.Stack.empty
    val hasOne = stack.push(1)
    hasOne.top
    hasOne.pop
    可变:
    val stack = scala.collection.mutable.Stack[Int]
    stack.push(1)
    stack.top
    stack.pop
  8. Queue:
    不可变:
    val empty = scala.collection.immutable.Queue[Int]()
    val has1 = empty.enqueue(1)
    val has123 = has1.enqueue(List(2, 3))
    val (element, has123) = has123.dequeue
    可变:
    val queue = scala.collection.mutable.Queue[String]
    queue += "a"
    queue.dequeue
  9. Range:
    1 to 3
    5 to 14 by 3
    1 until 3
  10. BitSet:
    不可变:
    val bits = scala.collection.immutable.BitSet.empty
    可变:
    val bits = scala.collection.mutable.BitSet.empty
    bits += 1
  11. ListMap:
    val map = scala.collection.immutable.ListMap(1 -> "one")
    map(1)
  12. ArrayBuffer:
    val buf = scala.collection.mutable.ArrayBuffer.empty
    buf += 1
  13. ListBuffer:
    val buf = scala.collection.mutable.ListBuffer.empty[Int]
    buf += 1
  14. StringBuilder:
    val buf = new StringBuilder
    buf += 'a'
  15. Array:
    和Java的数组一一对应。支持泛型。

Scala集合框架

提取器

提取器是拥有名为unapply的成员方法的对象。
正则表达式:import scala.util.matching.Regex
val regex = new Regex("""(-)?(\d+)(\.\d*)?""")
原生字符串是由一对三个连续双引号括起来的字符序列。
val regex = """(-)?(\d+)(\.\d*)?""".r
regex findFirstIn str
regex findAllIn str
regex findPrefixOf str

注解

Scala在内部将注解表示为仅仅是对某个注解类的构造方法的调用。
(将@换成new,就能得到一个合法的创建实例的表达式)
import annotation._
注解的用处:

  1. 自动生成文档,就像Scaladoc那样。
  2. 格式化代码。
  3. 检查常见的代码问题。
  4. 类型检查。
// 声明该方法已经过时了。
@deprecated("use newShinyMethod() instead")
def bigMistaks() = // ...
// 声明不检查
(e: @unchecked) match {
  // 没有全面覆盖的case 。。。
}
// 用在并发编程中。
@volatile
//二进制序列化
@serializable
//不应该被序列化的字段
@transient
//自动为字段生成get和set方法
@scala.reflect.BeanProperty

使用XML

XML是一种半结构化数据。

import scala.xml.Elem
import scala.xml.NodeSeq
import scala.xml.Node
import scala.xml.XML

对象相等性

class Point (val x: Int, val y: Int) {
  // 计算hash值  
  override def hashCode = (x, y).##
  override def equals (other: Any) = other match {
    case that: Point => 
      (that canEqual this) &&
      (this.x == that.x) && (this.y == that.y)
    case _ => false 
  }
  def canEqual(other: Any) = other.isInstanceOf[Point]
 }

结合Scala和Java

Scala的实现方式是将代码翻译成标准的Java字节码。
Java并没有单例对象的确切对应,不过他的确有静态方法。Scala对单例对象的翻译采用了静态和实例方法相结合的方式。
编写任何特质都会创建一个同名的Java接口。
所有Scala方法都被翻译成没有声明任何抛出异常的Java方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容