不断的学习,我们才能不断的前进
一个好的程序员是那种过单行线马路都要往两边看的人

Scala--学习笔记

1. 基础语法

Scala 是面向对象的高级语言,支持函数式编程范例,Scala代码生成在Java虚拟机(JVM)上运行的.class文件,而且可以使用java的库。

1.1. 变量类型

Scala里面有两种类型的变量:

  • val 不可变类型的变量,类似于java中final修饰的变量
  • var 可变类型的变量,并且仅在有特定原因使用它时才使用
scala> val x = 1
val x: Int = 1

scala> var s = "a string"
var s: String = a string

在创建变量时,可以不显示的声明其类型,Scala通常可以为您推断数据类型

1.2. 控制结构

1.2.1. if-else

scala 支持跟java语言类似的控制结构,但是对于 if-else只返回一个值的,可以将其用作三元运算符:

val x = if (a < b) a else b

1.2.2. match

scala 语法中的match表达式,其最基本的用法类似于Java switch语句:

// i 可以为其他类型的数据结构,包括Bool 类型
val result = i match {
    case 1 => "one"
    case 2 => "two"
    case _ => "not 1 or 2"
}
// match 是用作方法主体并针对许多不同类型进行匹配的示例:
def getClassAsString(x: Any):String = x match {
    case s: String => s + " is a String"
    case i: Int => "Int"
    case f: Float => "Float"
    case l: List[_] => "List"
    case p: Person => "Person"
    case _ => "Unknown"
}

1.2.3. try/catch

scala 中的try/catch类似于java,可以用来捕获异常:

try {
    writeToFile(text)
} catch {
    case fnfe: FileNotFoundException => println(fnfe)
    case ioe: IOException => println(ioe)
}

使用:Try,Success,Failur

//使用Try来处理异常,match来匹配异常。Failure里面包含错误的信息;通常用来处理异常。
import scala.util.{Try,Success,Failure}

def toInt(s: String): Try[Int] =Try(Integer.parseInt(s.trim))

toInt(x) match { 
    case Success(i) => println(i) 
    case Failure(s) => println(s"Failed. Reason: $s") 
}

1.2.4. for

for (i <- 0 to 10 by 2) println(i)

//将yield关键字添加到for循环中,以创建产生结果的for表达式。
//这是一个for表达式,它将序列1到5中的每个值加倍:
val x = for (i <- 1 to 5) yield i * 2

// 针对字符列表的for表达式
val fruits = List("apple", "banana", "lime", "orange")
val fruitLengths = for {
    f <- fruits
    if f.length > 4
} yield f.length

1.2.5. while/do-while

跟其他语法类似。

1.3. 修饰符

scala跟java一样有public、protected、private;

  1. private:
    但是private更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。
  2. protected:
    protected访问比java更加严格一点,因为它只允许保护成员在定义了该成员的的类的子类中被访问。
  3. 作用域保护:
    private[x] 或 protected[x]: 这里的x指代某个所属的包、类或单例对象。
    private[x],读作"这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。
    这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。

1.4. 类型层次结构

Any 是所有类型的超类型,也称为顶级类型。定义类一些通用的方法如:equals、hashCode、toString。有两个子类:AnyVal和AnyRef:
* AnyVal:表示值类型,分别是:Unit、Double、Float、Long、Int、Short、Byte、Char、Boolean。
* AnyRef:表示引用类型,所有非值类型都被定义为引用类型,每个用户自定义的类型都是AnyRef的子类型,如果scala运行在java环境中,AnyRef等价于java.lang.Obejct
scala_type

1.4.1. 类型转换

scala_type

1.4.2. Nothing 和 Null

Nothing是所有类型的子类,也称为底部类型,没有一个值是Nothing类型下的,它的用途之一是给出非正常终止的信号,如抛出异常、程序退出、或者一个无限循环(可以理解为它是一个不对值进行定义的表达式的类型,或者是一个不能正常返回的方法)。
Null 是所有引用类型的子类(AnyRef的任意子类型),它有一个单例值由关键字null所定义,Null主要是使得Scala满足和其他JVM语言的互操作性,但是几乎不应该出现在scala代码中使用。

1.4.3. 类型上届

在Scala中,类型参数和抽象类型都可以有一个类型边界约束。这种类型边界在限制类型变量实际取值的同时还能展露类型成员的更多信息。比如像T <: A这样声明的类型上界表示类型变量T应该是类型A的子类。

1.4.4. 类型下届

类型上界 将类型限制为另一种类型的子类型,而 类型下界 将类型声明为另一种类型的超类型。 术语 B >: A 表示类型参数 B 或抽象类型 B 是类型 A 的超类型。 在大多数情况下,A将是类的类型参数,而B将是方法的类型参数。

1.4.5. 类型隐式转换

一个从类型 S 到类型 T 的隐式转换由一个函数类型S => T的隐式值来定义,或者由一个可转换成所需值的隐式方法来定义。
隐式转换在两种情况下会用到:

  • 如果一个表达式 e 的类型为 S, 并且类型 S 不符合表达式的期望类型 T。
  • 在一个类型为 S 的实例对象 e 中调用 e.m, 如果被调用的 m 并没有在类型 S 中声明。

在第一种情况下,搜索转换 c,它适用于 e,并且结果类型为 T。
在第二种情况下,搜索转换 c,它适用于 e,其结果包含名为 m 的成员。

如果不加选择地使用隐式转换可能会导致陷阱,编译器会在编译隐式转换定义时发出警告。要关闭警告,执行以下任一操作:

  • 将 scala.language.implicitConversions 导入到隐式转换定义的上下文范围内
  • 启用编译器选项 -language:implicitConversions

1.5. Option

Option有两个子类别,Some和None。例如:当程序回传Some的时候,代表这个函式成功地给了你一个String,而你可以透过get()函数拿到那个String,如果程序返回的是None,则代表没有字符串可以给你
使用option去处理返回结果为null 值的问题。
可以把Option看成是一个容器,some表示里面包含内容,None表示里面不包含内容
其他使用:
下面代码中:Stree2是可选的,所以用Option。
具体的实例化:

class Address ( 
    var street1: String, 
    var street2: Option[String], 
    var city: String, 
    var state: String, 
    var zip: String 
)
// 实例化
val santa = new Address( "1 Main Street", None, "North Pole", "Alaska", "99705)" 

1.6. 提取器对象

提取器对象是一个包含有 unapply 方法的单例对象。apply 方法就像一个构造器,接受参数然后创建一个实例对象,反之 unapply 方法接受一个实例对象然后返回最初创建它所用的参数。提取器常用在模式匹配和偏函数中。

1.7. 正则表达式匹配

import scala.util.matching.Regex

val keyValPattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r

val input: String =
  """background-color: #A03300;
    |background-image: url(img/header100.png);
    |background-position: top center;
    |background-repeat: repeat-x;
    |background-size: 2160px 108px;
    |margin: 0;
    |height: 108px;
    |width: 100%;""".stripMargin

for (patternMatch <- keyValPattern.findAllMatchIn(input))
  println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}")

// 结果
key: background-color value: #A03300
key: background-image value: url(img
key: background-position value: top center
key: background-repeat value: repeat-x
key: background-size value: 2160px 108px
key: margin value: 0
key: height value: 108px
key: width value: 100

2. 类

Scala 类名用双驼峰命名; Scala的继承跟java类似;注意以下方面:重写一个非抽象方法必须使用override修饰符;只有主构造函数才可以往基类的构造器里面写参数;在子类重写超类的抽象方法时,不需要override关键字。
scala只允许继承一个父类(extend关键字),但是运行有多个混入(with)。

2.1. class

注意,scala不需要创建“get”和“set”方法来访问类中的字段。

class Person(var firstName: String, var lastName: String) {
    def printFullName() = println(s"$firstName $lastName")
}

类似于java中的普通类

2.2. Object

在 Scala 中,是没有 static 这个东西的,但是它也为我们提供了单例模式的实现方法,那就是使用关键字 object
在 Java 中 static 成员对应于 Scala 中的伴生对象的普通成员。在 Java 代码中调用伴生对象时,伴生对象的成员会被定义成伴生类中的 static 成员。这称为静态转发。这种行为发生在当你自己没有定义一个伴生类时。

Scala 中使用单例模式时,除了定义的类之外,还要定义一个同名的 object 对象,它和类的区别是,object对象不能带参数

当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员

// 私有构造方法
class Marker private(val color:String) {
  println("创建" + this)
  override def toString(): String = "颜色标记:"+ color 
}
// 伴生对象,与类名字相同,可以访问类的私有属性和方法
object Marker{
    private val markers: Map[String, Marker] = Map(
      "red" -> new Marker("red"),
      "blue" -> new Marker("blue"),
      "green" -> new Marker("green")
    ) 
    def apply(color:String) = {
      if(markers.contains(color)) markers(color) else null
    }
    def getMarker(color:String) = { 
      if(markers.contains(color)) markers(color) else null
    }
    def main(args: Array[String]) { 
        println(Marker("red"))  
        // 单例函数调用,省略了.(点)符号  
        println(Marker getMarker "blue")  
    }
}

2.3. trait

Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大,与接口不同的是它还可以定义属性和方法的实现。可以实现多继承Trait。

trait Equal {
  def isEqual(x: Any): Boolean
  def isNotEqual(x: Any): Boolean = !isEqual(x)

Trait构造顺序:

  • 调用超类的构造器
  • 特征构造器在超类构造器之后、类构造器之前执行;
  • 特征由左到右被构造;
  • 每个特征当中,父特征先被构造;
  • 如果多个特征共有一个父特征,父特征不会被重复构造
  • 所有特征被构造完毕,子类被构造

2.4. case

用case关键字来定义案例类; 当一个类被定义成为case类后,Scala会自动帮你创建一个伴生对象并帮你实现了apply, unapply,setter, getter 和toString,equals,copy和hashCode等方法。案例类非常适合用于不可变的数据。Case class可序列化,case class 的构造参数默认是public val类型。

实例化案例类时不需要用new关键字,因为有一个默认的apply方法来实现方法的创建;案例类的参数是val,是通过值进行比较的。
使用场景:

  • 使用枚举的时候
  • 为要在其他对象之间传递的“消息”创建容器时
    案例类可以使用模式匹配。

2.4.1. 模式匹配

当不同类型的对象需要调用不同方法时,仅匹配类型的模式很有用, 如下代码中的goIdle可以根据不同类型的Device有不同的表现:

abstract class Device
case class Phone(model:String) extends Device{
    def screenOFF="Turning screen off"
}
case class Computer(model:String) extends Device{
    def screenSaverOnF="Turning screen save on..."
}
def goIdle(device:Device)=device match {
    case p: Phone => p.screenOFF
    case c: Computer => c.screenSaverOnF
}

2.5. sealed

密封类:这意味着其所有的子类都必须定义在相同的文件中,从而保证所有的子类都是已知的。
sealed关键字可以修饰trait 和 class 类。

这对模式匹配很有用,因为不需要写一个匹配其他任意情况的case。
如果程序员在编写 match { case } 代码时没有枚举到所有类型的话,编译器会报告“没有匹配尽所有情况(match is not exhaustive)”的警告。

3. 方法和函数

scala可以不必声明方法的返回类型: 没有返回值就写Unit

def sum(a: Int, b: Int): Int = a + b
def sum(a: Int, b: Int) = a + b

3.1. 默认参数,可变参数:

默认参数:在参数类型后写=value,来给默认值。
可变参数:通过在参数类型之后放一个*号来设置可变参数。

// 默认参数
def sayHello(name: String, firstName: String = "hous"): Unit = {
    println(name + "和" + firstName + "是好朋友")
}

// 可变参数
def sum(nums: Int*) = {
    var res = 0
    for (num <- nums) res += num
    res
}
// 15=sum(1, 2, 3, 4, 5) = sum(1 to 5:_*) 
// :_* 为特殊语法,把参数变成序列

3.2. 传值、传名调用:

scala在解析参数时有两种方式,
传值调用:先计算参数表达式的值,再应用到函数内部;
传名调用:将未计算的参数表达式直接应用到函数内部;通过=>来设置传名调用:

object Test {
   def main(args: Array[String]) {
        delayed(time());
   }

   def time() = {
      println("获取时间,单位为纳秒")
      System.nanoTime
   }
   def delayed( t: => Long ) = {
      println("在 delayed 方法内")
      println("参数: " + t)
      t
   }
}

传名参数的优点是,如果它们在函数体中未被使用,则不会对它们进行求值。
另一方面,传值参数的优点是它们仅被计算一次

3.3. 偏应用函数:

Scala 偏应用函数是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数;通过绑定第一个 date 参数,第二个参数使用下划线(_)替换缺失的参数列表,并把这个新的函数值的索引的赋给变量

import java.util.Date

object Test {
   def main(args: Array[String]) {
      val date = new Date
      val logWithDateBound = log(date, _ : String)

      logWithDateBound("message1" )
      Thread.sleep(1000)
      logWithDateBound("message2" )
      Thread.sleep(1000)
      logWithDateBound("message3" )
   }

   def log(date: Date, message: String)  = {
     println(date + "----" + message)
   }
}

3.4. 高阶函数:

就是操作其他函数的函数,Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。

object Test {
   def main(args: Array[String]) {
      println( apply( layout, 10) )
   }
   // 函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v
   def apply(f: Int => String, v: Int) = f(v)
   def layout[A](x: A) = "[" + x.toString() + "]"

}

3.5. 匿名函数:

var mul = (x: Int, y: Int) => x*y

3.6. Scala函数柯里化:

柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数

object Test {
   def main(args: Array[String]) {
      val str1:String = "Hello, "
      val str2:String = "Scala!"
      println( "str1 + str2 = " +  strcat(str1)(str2) )
   }

   def strcat(s1: String)(s2: String) = {
      s1 + s2
   }
}

3.7. 隐式参数

方法可以具有 隐式参数列表,由参数列表开头的 implicit关键字标记。 如果参数列表中的参数没有像往常一样传递, Scala 将查看它是否可以获得正确类型的隐式值,如果可以,则自动传递。
Scala 将查找这些参数的位置分为两类:

  1. Scala 在调用包含有隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数 (无前缀)。然后,它在所有伴生对象中查找与隐式候选类型相关的有隐式标记的成员。
  2. Scala 在调用包含有隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数 (无前缀)。
    如下面代码;根据传入的是Int的List,还是String的List来sum。
// 抽象类
abstract class Monoid[A] { 
    def add(x: A, y: A): A 
    def unit: A 
}
object ImplicitTest { 
    implicit val stringMonoid: Monoid[String] =new Monoid[String] {  
        def add(x: String, y: String): String = x concat y 
        def unit: String = ""
    }
    implicit val intMonoid: Monoid[Int] = new Monoid[Int] { 
        def add(x: Int, y: Int): Int = x + y 
        def unit: Int = 0 
    }
    def sum[A] (xs:List[A])( m: Monoid[A]): A=
        if (xs.isEmpty) m. unit 
        else m.add(xs.head, sum(xs.tail)) 
        
    def main(args: Array[String]): Unit {
        println(sum(List(1, 2, 3))) // uses IntMonoid implicitly 
        print1n(sum(List('a','b','c'))) // uses StringMonoid implicit
    }
}

3.8. 多态方法

Scala 中的方法可以按类型和值进行参数化。 语法和泛型类类似。 类型参数括在方括号中,而值参数括在圆括号中。
方法 listOfDuplicates 具有类型参数 A值参数x和length。 值 x 是 A 类型。 如果length < 1,我们返回一个空列表。 否则我们将 x 添加到递归调用返回的重复列表中。可以隐式的提供类型参数,也可以显示的提供。

def listOfDup1icates[A] (x: A, length : Int) : List[A] ={
    if (length < 1) 
        Nil 
    else 
        x::listOfDup1icates(x,length-1)
}

print1n(listOfDup1icates[Int](3, 4)) // List(3, 3, 3,3)
println(listOfDup1icates( "La" , 8)) // List(La, La, La, La, La, La, la)

Nil 表示空的list
:: 表示追加,将左侧的元素添加到右侧的列表中

4. 线程

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

val a = Future { Thread.sleep(10*1000); 42 }
val b = a.map(_ * 2)

b
res1: scala.concurrent.Future[Int] = Future(Success(84))

可以看到b的值是封装在Success里面的,然后又封装在Future里面;
Future里面的值是Try的一个实例

a.onComplete { 
	case Success(value) => println(s"Got the callback, value = $value") 
    case Failure(e) => e.printStackTrace 
}

Future还有其他的方法:onComplete、onSuccess、onFailure
也可以使用集合类中的方法:filter、foreach、map

5. 型变

型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。 Scala支持泛型类的类型参数的型变注释,允许它们是协变的逆变的,或在没有使用注释的情况下是不变的。 在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。

class Foo[+A] // 协变类
class Bar[-A] // 逆变类
class Baz[A]  // 不变类

5.1 协变 +

使用注释 +A,可以使一个泛型类的类型参数 A 成为协变。 对于某些类 class List[+A],使 A 成为协变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 List[A] 就是 List[B] 的子类型。 这允许我们使用泛型来创建非常有用和直观的子类型关系。

5.1.1. Demo

scala里面有协变类:

  1. 泛型类:
sealed abstract class List[+A]

其中类型参数 A 是协变的

  1. 泛型类的类型参数:
abstract class Animal {
  def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
  1. 测试
object CovarianceTest extends App {
  // 注意这里只能传入List[Animal] 的子类
  def printAnimalNames(animals: List[Animal]): Unit = {
    animals.foreach { animal =>
      println(animal.name)
    }
  }
  val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
  val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
  printAnimalNames(cats)
  // Whiskers
  // Tom
  printAnimalNames(dogs)
  // Fido
  // Rex
}

printAnimalNames 传入的参数只能是List[Animal]的子类,所以如果不是协变的,即Cat,Dog是Animal的子类,但是List[A] 不是 List[Animal]的子类,就会报编译错误

5.2. 逆变 -

通过使用注释 -A,可以使一个泛型类的类型参数 A 成为逆变。 与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 class Writer[-A] ,使 A 逆变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 Writer[B] 是 Writer[A] 的子类型。

5.2.1. Demo

  1. 泛型类
abstract class Printer[-A] {
  def print(value: A): Unit
}

class AnimalPrinter extends Printer[Animal] {
  def print(animal: Animal): Unit =
    println("The animal's name is: " + animal.name)
}

class CatPrinter extends Printer[Cat] {
  def print(cat: Cat): Unit =
    println("The cat's name is: " + cat.name)
}
  1. 泛型类的类型参数:
    使用上面的Cat、Animal
  2. 测试

object ContravarianceTest extends App {
  val myCat: Cat = Cat("Boots")

  def printMyCat(printer: Printer[Cat]): Unit = {
    printer.print(myCat)
  }

  val catPrinter: Printer[Cat] = new CatPrinter
  val animalPrinter: Printer[Animal] = new AnimalPrinter

  printMyCat(catPrinter)
  printMyCat(animalPrinter)
}

// 输出结果:
The cat's name is: Boots
The animal's name is: Boots

如果 Printer[Cat] 知道如何在控制台打印出任意 Cat,并且 Printer[Animal] 知道如何在控制台打印出任意 Animal,那么 Printer[Animal] 也应该知道如何打印出 Cat 就是合理的。

反向关系不适用,因为 Printer[Cat] 并不知道如何在控制台打印出任意 Animal。 因此,如果我们愿意,我们应该能够用 Printer[Animal] 替换 Printer[Cat],而使 Printer[A] 逆变允许我们做到这一点。

5.3. 不变

默认情况下,Scala中的泛型类是不变的。 这意味着它们既不是协变的也不是逆变的。

6. 集合类

Scala 集合类系统地区分了可变的不可变的集合。
可变集合可以在适当的地方被更新或扩展。这意味着你可以修改,添加,移除一个集合的元素。
而不可变集合类,,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合。
所有的集合类都在以下包中:

import scala.collection  // 可变和不可变集合都有
import scala.collection.mutable //可变集合
import scala.collection.immutable  // 不可变集合
import scala.collection.generic

默认情况下,Scala 一直采用不可变集合类。例如,如果你仅写了Set 而没有任何加前缀也没有从其它地方导入Set,你会得到一个不可变的set。为了得到可变的集合类,需要显式的导入包。
1. scala.collection集合类
scala-collections-diagram
2. scala.collection.immutable
不可变集合类
scala-collections-immutable-diagram
3. scala.collection.mutable
可变集合类
scala-collections-mutable-diagram

参考文档

官方文档
Scala文档
型变官方文档
集合类官方文档


目录