Scala入门

30 Mar 2021

Scala基础

Scala语言特点

Scala是一门以 JVM 为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语
言。
Scala源代码会被编译成Java字节码,然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言 的无缝互操作的。

基础语法

基础语法规则:

常用类型与字面量

Scala和Java一样,有8种数值类型 Byte、Short、Int、Long、Float、Double、Char、Boolean 类型;
和 Java 不同的是 ,这些类型都是类,有自己的属性和方法。
Scala并不刻意的区分基本类型和引用类型。
String 直接引用 Java.lang.String 中的类型,String在需要时能隐式转换为StringOps,因此不需要任何 额外的转换,String就可以使用StringOps中的方法。
每一种数据类型都有对应的Rich类型,如RichInt、RichChar等,为基本类型提供了更多的有用操作。

Scala中,所有的类,包括值类型和引用类型,都最终继承自一个统一的根类型Any。 Scala中定义了以下三个底层类:

值与变量&自动类型推断

Scala当中的声明变量可以使用以下两种方式:

-- val定义的变量不可更改变量的类型编译器可以进行自动类型推断 
val name = "zhangsan"  
-- 必要时可以指定数据类型   
var name: String = null  
-- 可以将多个值或变量放在一起声明  
val x, y = 100;  
var name, message: String = null  

操作符

Scala的算术操作符、位操作符与 Java中的效果一样的。
需要特别注意一点:Scala中的操作符都是方法

a + b 等价 a.+(b)  
1 to 10 等价 1.to(10)  

书写时推荐使用:a + b 、1 to 10这种代码风格。
Scala 没有提供 ++、– 操作符,但是可以使用+=、-=

块表达式和赋值语句

{} 块包含一系列表达式,其结果也是一个表达式,块中最后一个表达式的值就是块的值。
赋值语句返回Unit类型,代表没有值;

val x1 = 1  
val y1 = 1  
val x2 = 0  
val y2 = 0  
val distance = {  
    val dx = x1 - x2  
    val dy = y1 - y2  
    math.sqrt(dx*dx + dy*dy)  
}  
-- 赋值语句的值是Unit类型不要把它们串接在一起x的值是什么?   
var y = 0  
val x = y = 1  
  
// x:Unit = ()  

输入和输出

通过readLine 从控制台读取一行输入。(scala.io.StdIn.readLine())
如果要读取数字、Boolean或者字符,可以用readInt、readDouble、readByte、readShort、 readLong、readFloat、readBoolean或者readChar。
print、println、printf 可以将结果输出到屏幕;

print("Please input your name: ")  
val name = scala.io.StdIn.readLine()  
print("Please input your age: ")  
val age = scala.io.StdIn.readInt()  
  
println("name:" + name + ", age:" + age)  
  
println(s"name $name, age : $age")  
  
printf("name: %s, age: %d", name, age)  
  
/**  
 * Please input your name: APRIL  
 * Please input your age: 189  
 * name:APRIL, age:189  
 * name APRIL, age : 189  
 * name: APRIL, age: 189  
 */  

字符串插值器

Scala 提供了三种字符串插值器:

//s 插值器,对内嵌的每个表达式求值,对求值结果调用toString,替换掉字面量中的那些表达式  
val subject = "Scala"  
val message = s"Hello, $subject"  
println(message)  
  
val arr = (1 to 10).toArray  
val str2 = s"arr.length = ${arr.length}"  
println(str2)  
println(s"9 * 9 = ${9 * 9}")  
/**  
 * Hello, Scala  
 * arr.length = 10  
 * 9 * 9 = 81  
 */  
  
//f 插值器,它除s插值器的功能外,还能进行格式化输出,在变量后用%指定输出格式,使用 java.util.Formatter中给出的语法  
val year = 2021  
val month = 8  
val day = 15  
println(s"$year - $month%02d - $day")  
println(f"$year - $month%02d - $day")  
/**  
 * 2021 - 8%02d - 15  
 * 2021 - 08 - 15  
 */  
  
//raw 插值器,按照字符串原样进行输出  
println("a\nb\tc")  
println(raw"a\nb\tc")  
println("""a\nb\tc""")  
println(  
  """  
    |a\nb\tc  
    |hello world  
    |this is a message from the  
    |universe  
    |""".stripMargin)  

对象相等性

Java 中可以 == 来比较基本类型和引用类型:

println(1==1) //true  
println(1!=2)  //true  
println(1==2)   //false  
  
val flag = List(1,2,3)==List(4, 5, 6)  
println(flag)  //false  
  
println(List(1, 2, 3) != Array(1, 2, 3))  //true  
  
println(2==2.0)  //true  
  
println(List(1,2,3)=="Scala")  //false  

控制结构和函数

if 表达式

val num=20  
  
// if else 有返回值, 同python  
// alt + enter 自动添加数据类型  
val name: String = if (num > 20) "kaclyu" else "lisi"  
println(name)  
  
// 如果返回的类型不一致就返回公共的父类  
val name2: Any = if (num > 20) "kaclyu" else 30  
println(name2)  
  
//  缺省 else 语句;s1/s2的返回类型不同  
val s1 = if (x > 0) 1 等价 val s1 = if (x > 0) 1 else ()  
val s2 = if (x > 0) "positive" 等价  val s2 = if (x > 0) "positive" else ()  

for 表达式

Scala中,for循环语法结构:for (i <- 表达式 / 集合),让变量 i遍历<-右边的表达式/集合的所有值。
Scala为for循环提供了很多的特性,这些特性被称之为 for守卫式 或 for推导式。

 for (i <- 1 to 10) {  
//      println(i)  
    }  
  
    for (i <- 1 until 10) {  
//      println(i)  
    }  
  
    //多重循环。条件之间使用分号分隔  
    /**  
     * 从右往左,一层层遍历, 每次添加一个 ; 就加一层  
     */  
    for (i <- 1 to 4; j <- 2 to 10) {  
//      println(i, j)  
    }  
    for (i <- 1 to 4; j <- 2 to 5; k <- 1 to 3) {  
//            println(i, j, k)  
    }  
  
    // 使用变量  
    for (i <- 1 to 3; j=4-i) {  
//      println(i, j)  
    }  
  
//    守卫语句。增加 if 条件语句, 添加过滤条件,只遍历满足条件的值  
    for (i <- 1 to 10; j <- 3 to 5 if i==j) {  
//      println(i, j)  
    }  
  
    // 使用 yield 接收返回的结果,这种形式被称为for推导式  
    // yield中的处理,留待后面使用, 相当于map处理  
    val result: IndexedSeq[Int] = for (i <- 1 to 10) yield i*2  
    val result2: IndexedSeq[Int] = (1 to 10).map(_*2)  
//    result.foreach(println(_))  
//    result2.foreach(println(_))  
  
    // 使用大括号将生成器、守卫、定义包含在其中;并以换行的方式来隔开它们  
    println("======================九九乘法表==========================")  
    for {i <- 1 to 9  
      j <- 1 to i} {  
      printf(s"$i * $j = ${i*j}\t")  
      if (i==j) println()  
    }  
  
    val message = "sparkscala"  
    for (c <- message) print(c + " ")  

while 表达式

Scala提供了与 Java 类似的while和do…while循环。while语句的本身没有任何返回值类型,即while语
句的返回结果是Unit类型的 () 。 Scala内置控制结构特地去掉了 break 和 continue。
特殊情况下如果需要终止循环,可以有以下三种方式:

var num=1  
while (num < 10) {  
  println(s"num = $num")  
  num += 1  
}  
  
do {  
  println(s"num = $num")  
  num += 1  
} while (num < 10)  
  
var flag = true  
while (flag) {  
  println(num)  
  num+=1  
  if (num==10) flag = false  
}  
  
for (i <- 1 to 10) {  
  if (i==5) return  
  println(i)  
}  
  
import scala.util.control.Breaks._  
  
var res = 0  
breakable{  
  for (i <- 1 to 10) {  
    if (i == 5) break  
    res += i  
  }  
}  
  
println(res)  

函数

object FuctionDemo {  
  
  /**  
   * 函数体中最后一句为返回值的话,可以将return 去掉  
   * @param num1  
   * @param num2  
   * @return  
   */  
  def add(num1: Int, num2: Int)  = {  
    num1+num2  
  }  
  
  
  // 递归函数的返回类型必须声明  
  def facorial(num: Int): Long = {  
    if (num <= 1) 1 else num * facorial(num - 1)  
  }  
  
  def fibonacci(n:Int):Long={  
    if (n <= 2) 1 else fibonacci(n-1) + fibonacci(n-2)  
  }  
  
  // 如果一个函数没有返回值,其返回类型为Unit , 并且 “=” 号可以去掉,这样的函数被称为过程;  
//  def getSum(x:Int, y:Int) = println(x+y)  
  def getSum(x:Int, y:Int) {  
    println(x+y)  
  }  
  
  def add2(num1: Int=10, num2: Int=20)  = {  
    num1+num2  
  }  
  
//  变长参数只能出现在参数列表的尾部,只能有一个  
  def addSum(nums:Int*)={  
    nums.sum  
  }  
  
  def main(args: Array[String]): Unit = {  
    println(add(2, 3))  
  
    println(facorial(3))  
  
    println(fibonacci(6))  
  
    getSum(2,4)  
  
    println(add2())  
    // 缺省赋值,按顺序传递  
    println(add2(30))  
    // 指定需要赋值的key,不需要按顺序传递  
    println(add2(num2 = 30))  
  
  
    println(addSum(1,2,3,4))  
  
    //告诉编译器这个参数被当做参数序列处理。使用 parameter: _* 的形式  
    println(addSum(1 to 10:_*))  
  }  
  
}  
  

懒值

当 val 被声明为lazy时(var不能声明为lazy),它的初始化将被推迟,直到首次对此取值,适用于初始化
开销较大的场景。

// 文件不存在时,不会报错。因为语句此时并没有被执行  
lazy val file1 = scala.io.Source.fromFile("src/test.scala")  
  
println("Okay")  
  
// 先打印OK!,才报错  
println(file1.getLines().size)  

文件操作

def readTextFile:Unit = {  
  val file: BufferedSource = scala.io.Source.fromFile("src/text.txt")  
  val lines: Iterator[String] = file.getLines()  
  lines.foreach(println(_))  
  file.close()  
}  
  
def readFromUrl {  
  val source: BufferedSource = scala.io.Source.fromURL("https://www.baidu.com")  
  println("source.codec " + source.codec)  
  println(source.mkString)  
  
  source.close()  
}  
  
//Scala没有内建的对写入文件的支持。要写入文本文件,可使用 java.io.PrintWriter  
  
def writeTextFile: Unit = {  
  val writer = new PrintWriter("src/text.txt")  
  
  (1 to 10).foreach(writer.println(_))  
  writer.flush()  
  
  writer.close()  
}  
  
def main(args: Array[String]): Unit = {  
    readTextFile  
    readFromUrl  
    writeTextFile  
}  

数组和元组

数组定义

数组遍历

// 使用until,基于下标访问使用增强for循环进行数组遍历 for (i <- 0 until nums.length)  
    println(nums(i))  
// 使用to,基于下标访问使用增强for循环进行数组遍历 for (i <- 0 to nums.length-1)  
    println(nums(i))  
// 使用增强for循环遍历数组元素 for (elem <- nums)  
    println(elem)  

常见算法

在Scala中对数组进行转换非常简单方便,这些转换动作不会修改原始数组,而是产生一个全新的数
组。

// 将数组中偶数元素加倍,奇数元素丢弃  
    val arr = (1 to 10).toArray  
  
    val result1 = for (elem <- arr if elem%2==0) yield elem*2  
    result1.foreach(println(_))  
    println("-----------------------")  
    arr.filter(_%2==0).map(_*2).foreach(println(_))  
  
    println("------------------------")  
    println(arr.head)       //第一个元素  
    println(arr.last)       //最后一个元素  
    // 去掉第一个元素的数组  
    println(arr.tail.toBuffer)  
    // 去掉最后一个元素的数组  
    println(arr.init.toBuffer)  
  
    // sum  
    println(arr.sum)  
    // 最大值  
    println(arr.max)  
    // 最小值  
    println(arr.min)  
  
    val num2 = Array(2, 1, 4, 3)  
    // 排序  
    println(num2.sorted.toBuffer)  
    // 求数组的累计积  
    println(num2.product)  
  
    val nums3 = Array(1, 2,3, 4, 3, 2, 1)  
    // 逐个处理,变形元素  
    println(nums3.map(_*2).toBuffer)  
    // 根据运算符 累计处理  
    println(nums3.reduce(_ + _))  
    // 去重  
    println(nums3.distinct.toBuffer)  
    // 数组长度  
    println(nums3.length)  
    // 数组长度  
    println(nums3.size)  
    // 数组下标组成数组  
    println(nums3.indices)  
  
    // 根据给定的 拼接符号 生成string  
    println(nums3.mkString(" & "))  
    // 根据给定的首, 拼接符号, 尾 生成string  
    println(nums3.mkString("[", ", ", "]"))  
  
    // 计算满足条件的元素数量  
    println(nums3.count(_ % 2 == 0))  
  
    // 只保留满足条件的元素  
    println(nums3.filter(_ > 3).toBuffer)  
    // 过掉掉满足条件的元素  
    println(nums3.filterNot(_ % 2 == 0).toBuffer)  
  
    // 从左开始取指定数量的元素  
    println(nums3.take(3).toBuffer)  
    // 从右开始取指定数量的元素  
    println(nums3.takeRight(2).toBuffer)  
    // 从左开始取元素,直到条件不满足  
    println(nums3.takeWhile(_ < 4).toBuffer)  
  
  
    // 从做开始丢弃给定数量的元素  
    println(nums3.drop(2).toBuffer)  
    // 从右边开始丢弃给定数量的元素  
    println(nums3.dropRight(3).toBuffer)  
    // 从第一个元素开始丢弃,直到条件不满足  
    println(nums3.dropWhile(_ < 4).toBuffer)  
  
    // = (take(n), drop(n))  
    val result3 = nums3.splitAt(3)  
    println(result3._1.toBuffer, result3._2.toBuffer)  
    println(nums3.slice(2, 4).toBuffer)  
  
    val array1 = Array("A", "B", "C")  
//    val array1 = Array("A", "B", "C", "D", "E")  
    val array2 = Array(1, 2, 3, 4)  
    // 两个数组zip,长度等于 min(arr1.size,arr2.size)  
    val tuples: Array[(String, Int)] = array1.zip(array2)  
    println(tuples.toBuffer)  
  
    // 两个数组zip,遇到空的元素之后给的默认值填充  
    val z2 = array1.zipAll(array2, "*", 0)  
    println(z2.toBuffer)  
  
    // 元素和下标zip  
    val index: Array[(String, Int)] = array1.zipWithIndex  
    println(index.toBuffer)  
  
    // 展开tuple数组2  
    val (l1, l2) = index.unzip  
    println(l1.toBuffer)  
    println(l2.toBuffer)  
  
    // 展开tuple数组3  
    val unzip: (Array[Int], Array[String], Array[Char]) = Array((1, "one", '1'), (2, "two", '2'), (3, "three", '3')).unzip3  
  
    // :+ +: ++  
    val number1 = (1 to 4).toArray  
    val number2 = (5 to 8).toArray  
  
    // 在数组前面插入元素  
    val number3 = 10 +: number1  
    // 在数组末端追加元素  
    val number4 = number2 :+ 9  
    // 数组拼接  
    val number5 = number3 ++ number4  
  
    println(number1.toBuffer)  
    println(number2.toBuffer)  
    println(number3.toBuffer)  
    println(number4.toBuffer)  
    println(number5.toBuffer)  
  
    val sortNums = Array(1,2,10,3,4,2)  
    // 从小到大排序  
    println(sortNums.sorted.toBuffer)  
    // 从小到大排序  
    println(sortNums.sorted.reverse.toBuffer)  
    // 从大到小排序  
    println(sortNums.sortWith(_ > _).toBuffer)  
    // 从小到大排序  
    println(sortNums.sortWith(_ < _).toBuffer)  
    // 对复制结构,指定字段进行排序  
    println(sortNums.sortBy(_*2).toBuffer)  

多维数组

通过Array的ofDim方法来定义一个多维的数组,多少行,多少列,都是自己说了算。

//创建一个3行4列的二维数组  
val dim = Array.ofDim[Double](3,4)   
dim(1)(1) = 11.11  
for (i <- 0 to 2; j <- 0 to 3) {  
    print(dim(i)(j) + "  ")  
    if (j == 3) println()  
}  

元组及操作

Tuple,元组。Map是键值对的集合。对偶是元组的最简单形态; 元组是不同类型的值的集合,元组中的元素可以是不同的数据类型,元组在Scala中的应用非常广泛。

  def main(args: Array[String]): Unit = {  
//    val a = (1,2,3 ,4,1,2,3 ,4,1,2,3 ,4,1,2,3 ,4,1,2,3 ,4,1,2,3 ,4,1,2,3 ,4,1,2,3 ,4,1,2,3 ,4)  
//    Error:(5, 13) too many elements for tuple: 36, allowed: 22  
    val a: (Int, Double, String, Char) = (1, 1.2, "ad", 'd')  
    val b: (Int, Double, String, Char) = Tuple4(1, 1.2, "ad", 'd')  
    println(a==b)  //true  
    println(a._3)  //获取第三个元素  
    println(a.productElement(2)) //获取第三个元素  
  
    // 解包tuple中的值  
    val (t1, t2, t3, t4), t5 = a  
    println(s"t1 = $t1, t2 = $t2, t3 = $t3, t4=$t4, t5=$t5")  
  
    val (b1, _, _, b4), b5 = a  
    println(s"b1 = $b1, b4=$b4, t5=$b5")  
  
    // 遍历元素的两种方法  
    for (i <- a.productIterator) {  
      print(i + " ")  
    }  
    println()  
  
    b.productIterator.foreach(println(_))  

类与对象class&object

类和无参构造器

//如果没有定义构造器,类会有一个默认的无参构造器;  
class Person{  
  // 声明字段必须进行初始化,scala编译器会根据初始化的数据类型自动推断类型  
  // 字段类型可以省略  
  var name = "lagou"  
  
  // _ 表示一个占位符,scala会根据数据类型赋予相应的初始值  
  var nickName:String = _    // null  
  var numInt: Int = _   // 0  
  var numDouble: Double = _   // 0  
  var boolean: Boolean = _    // false  
  
  // 用 val 修饰的变量不能使用占位符  
//  val test: Int = _  
  val num=30  
  var age = 20  
  
  
  // 如果赋值为 null,需要添加数据类型,不然会推断为 Null 类型  
  var address: Null = null  
  
  // 私有字段可以内部访问,也可以被伴生对象访问  
  private var hobby="旅游"  
  
  // 对象私有字段,只能在当前类访问  
  private [this] val cardInfo="10010"  
  
  def hello(message: String): Unit = {  
    println(s"$message, $cardInfo")  
  }  
  
  def addNum(num1: Int, num2: Int): Int = {  
    num1 + num2  
  }  
}  
  
object ClassDemo {  
  def main(args: Array[String]): Unit = {  
    val person = new Person()  
  
    // 小括号可以省略  
    val person2 = new Person  
    println(person.name, person.nickName, person.numInt, person.numDouble, person.boolean)  
  
    //给类的属性赋值  
    person.age = 50  
  
    //注意:如果使用对象的属性加上 _= 给var修饰的属性进行重新赋值,其实就是调用age_=这个setter方法  
    person.age_=(20)  
  
    //直接调用类的属性,其实就是调用getter方法  
    println(person.age)  
  
    //调用类中的方法  
    person.hello("Hello ")  
    val result = person.addNum(10, 23)  
    println(result)  
  
  }  
}  
  

自定义getter和setter方法

对于 Scala 类中的每一个属性,编译后会有一个私有的字段和相应的getter、setter方法生成。

//getter方法   
println(person age)  
//setter方法   
person.age_= (18)  
person.age = 18  
  
//getter方法   
println(person.age)  

可以不使用自动生成的方式,自己定义getter和setter方法
自定义变量的getter和setter方法需要遵循以下原则:

class Dog {  
private var _leg = 0   
  
//自定义getter方法  
def leg = _leg   
  
//自定义setter方法  
def leg_=(newLeg: Int) {  
    _leg = newLeg  
  }  
}  
// 使用自定义getter和setter方法 val dog = new Dog   
dog.leg_=(4)   
println(dog.leg)  

Bean属性

类似于Java,当将Scala字段标注为 @BeanProperty时,getFoo和setFoo方法会自动生成。 使用@BeanProperty并不会影响Scala自己自动生成的getter和setter方法。 在使用时需要导入包scala.beans.BeanProperty

import scala.beans.BeanProperty  
  
class Teacher {  
    @BeanProperty var name:String = _  
}  
  
object BeanDemo{  
  def main(args: Array[String]): Unit = {  
	val tea: Teacher = new Teacher  
	tea.name = "zhagnsan"  
	tea.setName("lisi") //BeanProperty生成的setName方法 	println(tea.getName) //BeanProperty生成的getName方法  
	}  
}  

上述Teacher类中共生成了四个方法:

1. name: String  
2. name_= (newValue: String): Unit  
3. getName(): String  
4. setName (newValue: String): Unit  

构造器

// Scala中的主构造器与类名交织在一起,类名后面的参数即为主构造器的参数  
// 当主构造器的参数不用var或val修饰时,参数会生成类的私有val成员。  
// 主构造器直接定义在类中,其代码不包含在任何方法中  
class Animal(name: String, var age: Int) {  
  //创建类的对象时会去执行主构造器的代码。下面的println代码就是主构造器的一部分  
  println(name)  
  println(age)  
  println("=======================")  
  
  var gender: String = ""  
  
  
  def this(name: String, age: Int, gender: String) {  
    //每个辅助构造器,都必须以其他辅助构造器,或者主构造器的调用作为第一句代码  
    this(name, age)  
    this.gender = gender  
  }  
  
  var color = ""  
  def this(name: String, age: Int, gender: String, color: String) {  
    //调用上面的辅助构造器  
    this(name, age, gender)  
    this.color = color  
  }  
}  
  
object ConstructorDemo {  
  def main(args: Array[String]): Unit = {  
    val dog1 = new Animal("dog", 3)  
    val dog2 = new Animal("cat", 4, "man")  
    val dog3 = new Animal("pig", 5, "man", "white")  
  
    dog2.age = 10  
    println(dog2.age)  
  }  
}  

对象

继承extends

继承的概念

Scala中继承类的方式和Java一样,也是使用extends关键字:

class Employee extends Person{  
    var salary=1000  
}  

和Java一样,可在定义中给出子类需要而父类没有的字段和方法,或者重写父类的方法。

//Person类  
class Person(name:String,age:Int)  
  
//Student继承Person类  
class Student(name:String,age:Int,var studentNo:String) extends Person(name,age)   
  
object Demo{  
  def main(args: Array[String]): Unit = {  
     val student=new Student("john",18,"1024")  
}   
}  

构造器执行顺序

Scala在继承的时候构造器的执行顺序:首先执行父类的主构造器,其次执行子类自身的主构造器。

类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须以对先前定义的辅助构造器或主
构造器的调用开始。
子类的辅助构造器最终都会调用主构造器。只有主构造器可以调用父类的构造器。

class Person(name: String, age: Int) {  
  println("this is father")  
}  
class Student(name: String, age: Int, var stuNo: String) extends Person(name, age) {  
  println("this is student")  
}  
  
object ExtendsDemo {  
  def main(args: Array[String]): Unit = {  
    val student = new Student("April", 20, "1002")  
    println("--------------------")  
    student.stuNo = "123040"  
    println(s"${student.stuNo}")  
  
    /**  
     * this is father  
     * this is student  
     * --------------------  
     * 123040  
     */  
  }  
}  

override方法重写

方法重写指的是当子类继承父类的时候,从父类继承过来的方法不能满足子类的需要,子类希望有自己 的实现,这时需要对父类的方法进行重写,方法重写是实现多态的关键
Scala中的方法重写同Java一样,也是利用override关键字标识重写父类的方法。

class Programmer(name: String, age: Int) {  
  def coding(): Unit = {  
    println("I am coding")  
  }  
}  
  
class ScalaProgrammer(name: String, age: Int, workNo: String) extends Programmer(name, age) {  
  override def coding(): Unit = {  
    super.coding()  
    println("with scala")  
  }  
}  
  
object OverrideDemo {  
  def main(args: Array[String]): Unit = {  
    val scalaProgrammer = new ScalaProgrammer("April", 20, "1010")  
    scalaProgrammer.coding()  
    /**  
     * I am coding  
     * with scala  
     */  
  }  
}  

如果父类是抽象类,则override关键字可以不加。如果继承的父类是抽象类(假设抽象 类为AbstractClass,子类为SubClass),在SubClass类中,AbstractClass对应的抽象方法如果没有实 现的话,那SubClass也必须定义为抽象类,否则的话必须要有方法的实现。

//抽象的Person类  
abstract class Person(name:String,age:Int){  
  def walk():Unit  
}  
//Student继承抽象Person类  
class Student(name:String,age:Int,var studentNo:String) extends Person(name,age) {  
  
//重写抽象类中的walk方法,可以不加override关键字   
def walk():Unit={  
    println("walk like a elegant swan")  
  }  
}  
object Demo{  
  def main(args: Array[String]): Unit = {  
     val stu=new Student("john",18,"1024")  
     stu.walk()  
}   
}  

类型检查与转换

要测试某个对象是否属于某个给定的类或子类,可以用isInstanceOf方法。如果测试成功,可以用 asInstanceOf方法进行类型转换。

class Person2  
class Student2 extends Person2  
  
object InstanceDemo {  
  def main(args: Array[String]): Unit = {  
    val p: Person2 = new Student2  
    var s: Student2 = null  
  
    println(s.isInstanceOf[Student2])  
    println(p.isInstanceOf[Person2])  
  
    if (p.isInstanceOf[Student2]) {  
      //: 类型不匹配直接转化,会抛移除  
      s = p.asInstanceOf[Student2]  
    }  
    println(s.isInstanceOf[Student2])  
  
    // 判断是否为特定类  
    println(p.getClass == classOf[Person2])  
    println(p.getClass == classOf[Student2])  
  
    println("==================================")  
    // 使用模式匹配判断类型  
    p match {  
      case student: Student2 => println("is a student")  
      case _ => println("is nothing")  
    }  
  
  }  
}  

特质trait

作为接口使用的特质

Scala中的trait特质是一种特殊的概念。 首先可以将trait作为接口来使用,此时的trait就与Java中的接口非常类似。 在trait中可以定义抽象方法, 也可以定义有具体实现的方法

类可以使用extends关键字继承trait。在Scala中没有implement的概念,无论继承类还是trait特质,统一都是extends。 类继承trait特质后,必须实现其中的抽象方法,实现时可以省略override关键字。 Scala不支持对类进行多继承,但是支持多重继承trait特质,使用with关键字即可。

trait HelloTrait {  
  def sayHello  
}  
  
trait MakeFriendTrait {  
  def makeFriend  
}  
  
// trait多继承  
class Person(name: String) extends HelloTrait with MakeFriendTrait {  
  override def sayHello: Unit = println(s"Hello, my name is $name")  
  
  override def makeFriend: Unit = println("Can you be my friend?")  
}  
  
object TraitDemo {  
  def main(args: Array[String]): Unit = {  
    val per = new Person("April")  
    per.sayHello  
    per.makeFriend  
  }  
}  

带有具体实现的特质

trait People {  
  //定义抽象字段  
  val name: String  
  //定义了age字段  
  val age = 30  
  
  def eat: Unit = {  
    println("Eating ")  
  }  
}  
  
trait Worker {  
  //定义了age字段  
  val age = 24  
  
  def work: Unit = {  
    println("Working.....")  
  }  
}  
  
class Student extends People with Worker {  
  //重写抽象字段,override可以省略  
  val name: String = "lisi"  
  //继承的两个trait中都有age字段,此时需要重写age字段,override不能省略  
  override val age = 90  
}  
  
object TraitDemoTwo {  
  def main(args: Array[String]): Unit = {  
      val stu = new Student  
      println(s"${stu.name} ${stu.age}")  
      stu.eat  
      stu.work  
  }  
}  

特质构造顺序

在Scala中,trait特质也是有构造器的,也就是trait中的不包含在任何方法中的代码。 构造器以如下顺序执行:
1、执行父类的构造器;
2、执行trait的构造器,多个trait从左到右依次执行;
3、构造trait时会先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次;
4、所有trait构造完毕之后,子类的构造器才执行

class Person2 {  
  println("Father")  
}  
  
trait Logger{  
  println("Logger")  
}  
  
trait MyLogger extends Logger {  
  println("MyLogger")  
}  
  
trait TimeLogger extends Logger {  
  println("TimeLogger")  
}  
  
//类既继承了类又继承了特质,要先写父类  
class Student2 extends Person2 with TimeLogger with MyLogger {  
  println("Son ")  
}  
  
object TraitDemoThree {  
  def main(args: Array[String]): Unit = {  
    new Student2  
    println("=====================")  
    new Student2  
  
    /**  
     * Father  
     * Logger  
     * MyLogger  
     * TimeLogger  
     * Son  
     */  
  }  
}  

特质继承类

在Scala中,trait特质也可以继承class类,此时这个class类就会成为所有继承此trait的类的父类。

class MyUtil {  
  def printMessage(msg: String) = println(msg)  
}  
  
trait Log extends MyUtil {  
  def log = println("Log some thing")  
}  
  
class Person3(name: String) extends Log {  
  def sayHello = {  
    println(s"Say hello $name")  
    log  
    printMessage(s"message to $name")  
  }  
}  
  
object TraitDemoFour {  
  def main(args: Array[String]): Unit = {  
    val p = new Person3("April")  
    p.sayHello  
    println(p.isInstanceOf[MyUtil])  
    println(p.isInstanceOf[Log])  
    println(p.isInstanceOf[Person3])  
  
    val l = new Log {}  
    l.log  
    println(l)  
  
    /**  
     * Say hello April  
     * Log some thing  
     * message to April  
     * true  
     * true  
     * true  
     * Log some thing  
     * lagou.com.part06.TraitDemoFour$$anon$1@2752f6e2  
     */  
  }  
}  

Ordered和Ordering

在Java中对象的比较有两个接口,分别是Comparable和Comparator。它们之间的区别在于:

Scala 中有 Ordered 和 Ordering 分别继承了Comparable 和 Comparator, 功能类似:

case class Project(tag: String, score: Int) extends Ordered[Project] {  
  override def compare(that: Project): Int = this.tag.compare(that.tag)  
}  
  
object OrderDemo {  
  def main(args: Array[String]): Unit = {  
    val list = List(Project("Hadoop", 40), Project("Flink", 60), Project("Spark", 30),Project("Jacking", 80))  
    // 使用 Ordered 的实现进行比较  
    println(list.sorted)  
    // 使用 Ordering 进行比较  
    println(list.sorted(Ordering.by[Project, Int](_.score)))  
  
  
    val pairs = Array(("a", 7, 2), ("c", 9, 1), ("b", 8, 3))  
    // Ordering.by[(Int,Int,Double),Int](_._2)表示从Tuple3转到Int型  
    // 并按此Tuple3中第二个元素进行排序  
    Sorting.quickSort(pairs)(Ordering.by[(String, Int, Int), Int](_._2))  
    println(pairs.toBuffer)  
  }  
}  

模式匹配和样例类
match … case

模式匹配

Scala没有Java中的switch case,它有一个更加强大的模式匹配机制,可以应用到很多场合。
Scala的模式匹配可以匹配各种情况,比如变量的类型、集合的元素、有值或无值。
模式匹配的基本语法结构:变量 match { case 值 => 代码 }

模式匹配match case中,只要有一个case分支满足并处理了,就不会继续判断下一个case分支了,不 需要使用break语句。这点与Java不同,Java的switch case需要用break阻止。如果值为下划线,则代 表不满足以上所有情况的时候如何处理。

模式匹配match case最基本的应用,就是对变量的值进行模式匹配。match是表达式,与if表达式一 样,是有返回值的

字符和字符串匹配

// 匹配字符  
val character = 'p'  
character match {  
  case '+' => println("+")  
  case '-' => println("-")  
  case '*' => println("=")  
  case '/' => println("/")  
  case _ => println("Nothing")  //default  
}  
  
// 匹配字符串  
val arr = Array("hadoop", "zookeeper", "spark")  
val name = arr(Random.nextInt(arr.length))  
name match {  
  case "hadoop" => println("hadoopppppp")  
  case "spark" => println("spark.....")  
  case "zookeeper" => println("zoooo")  
  case _ => println("Oh, my god")  
}  
  

守卫式匹配

// 匹配守卫式  
// 所谓守卫就是添加if语句  
val char = '*'  
val num = char match {  
  case '+' => 1  
  case '-' => 2  
  case _ if char.equals('*') => 3  
  case _ => 4  
}  
println(s"number is $num")  

匹配类型

Scala的模式匹配还有一个强大的功能,它可以直接匹配类型,而不是值。这一点是Java的switch case 做不到的。
匹配类型的语法:case 变量 : 类型 => 代码

// 匹配类型  
def typeMatch(x:Any)={  
  x match {  
    case x: String => println("String")  
    case x: Int => println("Int")  
    case x: Boolean if(x==false) => println("false boolean")  
    case x: Array[Int] => println("Array[Int]")  
    case _ => println("not clear!")  
  }  
}  
  
typeMatch("name")  
typeMatch(false)  
typeMatch(true)  
typeMatch(Array(12,3))  

匹配数组、元组、集合

object MatchCollection {  
  def main(args: Array[String]): Unit = {  
    val array = Array(1, 3, 5, 7, 9)  
//    val array = Array(1, 3, 5)  
//    val array = Array(1)  
//    val array = Array(2)  
  
    array match {  
        // 有3个元素,且第一个是 1  
      case Array(1, x, y) => println(x + " " + y)  
        // 只有一个元素1  
      case Array(1) => println("Only 1....")  
        // 以1开头的数组  
      case Array(1, _*) => println("start with 1")  
        // 其他情况  
      case _ => println("Something else ...")  
    }  
  
    //构造List列表的两个基本单位是Nil和::,Nil表示为一个空列表  
    val list = List(2)  
    list match {  
        // x::y::Nil 等价于 List(x, y)  
      case x::y::Nil => println(s"$x  $y")  
        // 2::Nil 等价于 List(2)  
      case List(2) => println("Only 2...")  
      // 等价于 List(1, _*)  
      case 1::tail => println(s"start with 1")  
      case _ => println("Something else...")  
    }  
  
    val tuple = (2, 76, 5)  
    tuple match {  
          
      case (1, x, y) => println(s"$x  $y")  
      case (_, z, 5) => println(z)  
      case _ => println("else")  
    }  
  }  
}  

样例类

case class样例类是Scala中特殊的类。当声明样例类时,以下事情会自动发生:

class Amount  
case class Dollar(value: Double) extends Amount  
case class Currency(value: Double, unit: String) extends Amount  
case object Nothing extends Amount  
  
object CaseClassDemo {  
  def main(args: Array[String]): Unit = {  
    judgeIdentity(Currency(200, "HKD"))  
    judgeIdentity(Dollar(90))  
    judgeIdentity(Nothing)  
  
    println(Currency(900, "dd").productElement(0))  
  }  
  
  //自定义方法,模式匹配判断amt类型  
  def judgeIdentity(amt: Amount): Unit = {  
    amt match {  
      case Currency(value, unit) => println(s"Currency $value, $unit")  
      case Dollar(value) => println(s"Dollar $value")  
      case Nothing => println("Nothing ")  
      case _ => println("else")  
    }  
  }  
}  
  

Option与模式匹配

Scala Option选项类型用来表示一个值是可选的,有值或无值。
Option[T] 是一个类型为 T 的可选值的容器,可以通过get()函数获取Option的值。
如果值存在, Option[T] 就是一个 Some。如果不存在,Option[T] 就是对象 None 。
Option通常与模式匹配结合使用,用于判断某个变量是有值还是无值。

object OptionDemo {  
  val grades=Map("April"->90, "tome"->90, "Angie"->87)  
  
  def getGrade(name: String):Unit = {  
    val grade: Option[Int] = grades.get(name)  
    grade match {  
      case Some(grade) => println(s"grade: $grade")  
      case None => println("Not in the list")  
    }  
  }  
  
  def main(args: Array[String]): Unit = {  
    getGrade("April")  
    getGrade("zhangsan")  
  }  
}  

函数及抽象化

函数字面量及函数的定义

def add1(x:Int):Int = {x+1}  
// 函数的类型为: (Int) => Int  
// 输入参数列表只有一个括号,可以简写为: Int => Int  
  
def add2(x: Int, y: Int): Int = { x + y }  
// 函数的类型为: (Int, Int) => Int  
  
def add4(x: Int, y: Int, z: Int): (Int, Int) = { (x + y, y + z) }  
// 函数的类型为: (Int, Int, Int) => (Int, Int)  

函数类型:(输入参数类型列表) => (输出参数类型列表)
只有一个参数时,小括号可省略;函数体中只有1行语句时,大括号可以省略;

函数与方法的区别

严格的说:使用 val 定义的是函数(function),使用 def 定义的是方法(method)。二者在语义上的区别 很小,在绝大多数情况下都可以不去理会它们之间的区别,但是有时候有必要了解它们之间的不同。

// 方法  
def addm(x: Int, y: Int): Int = x + y  
// 函数  
val addf = (x: Int, y: Int) => x + y  

Scala中的方法与函数有以下区别:

// 下面用三种方式定义了函数,其中第二种方式最常见  
val adder1: (Int, Int) => Int = (x, y) => x+y   
val adder2 = (x: Int, y: Int) => x+y  
  
// Function2是特质,不能直接new  
// new Function2[Int,Int,Int]{ ... } 其实是定义并实例化一个实现了 Function2 特质的类的 对象  
val adder3 = new Function2[Int, Int, Int] {  
  override def apply(v1: Int, v2: Int): Int = {  
    v1 + v2  
  }  
}  
// 方法不能作为单独的表达式而存在,而函数可以  
scala> def addm(x: Int, y: Int): Int = x + y  
def addm(x: Int, y: Int): Int  
  
scala> val addf = (x: Int, y: Int) => x + y  
val addf: (Int, Int) => Int = $Lambda$1154/250215657@1197b54e  
  
scala> addm  
       ^  
       error: missing argument list for method addm  
       Unapplied methods are only converted to functions when a function type is expected.  
       You can make this conversion explicit by writing `addm _` or `addm(_,_)` instead of `addm`.  
  
scala> addf  
val res51: (Int, Int) => Int = $Lambda$1154/250215657@1197b54e  
  
// 函数必须要有参数列表,而方法可以没有参数列表  
scala> def m1 = "This is me"  
def m1: String  
  
scala> val f1 = () => "This is me"  
val f1: () => String = $Lambda$1156/724595938@6358359e  
  
// 方法名是方法调用  
scala> m1  
val res52: String = This is me  
  
// 函数名代表函数对象  
scala> f1  
val res53: () => String = $Lambda$1156/724595938@6358359e  
  
// 这才代表函数调用  
scala> f1()  
val res54: String = This is me  
  
// 需要函数的地方,可以传递一个方法  
scala> val list = (1 to 10).toList  
val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  
  
scala> def double(x: Int) = x*x  
def double(x: Int): Int  
  
scala> list.map(double(_))  
val res55: List[Int] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)  

将方法转换为函数 func = method _

scala> def f1 = double _  //注意:方法名与下划线之间有一个空格   
f1: Int => Int  
scala> f1  
res21: Int => Int = <function1>  

匿名函数与占位符

函数没有名字就是匿名函数;
匿名函数,又被称为 Lambda 表达式。 Lambda表达式的形式如下:
(参数名1: 类型1, 参数名2: 类型2, … …) => 函数体

// 将匿名函数作为参数传递给另一个函数  
list.map((x: Int) => x + 1)  
  
// x一定是Int类型,这里可以省略  
list.map((x) => x + 1)  
  
// 只有一个参数,小括号可以省略  
list.map(x => x + 1)  
  
// 使用占位符简化函数字面量  
list.map(_ + 1)  
  
// 实现将List中的每个元素*2 + 1,但是出错了  
list.map(_ + _ + 1)  
//<console>:13: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2).$plus(1))  
  
// 这样是可行的  
list.map(2 * _ + 1)  
  
// 通过reduce这个高阶函数,将list列表中的元素相加求和  
list.reduce((x,y) => x + y)  
  
// 使用占位符简化函数字面量  
// 第一个下划线代表第一个参数,第二个下划线代表第二个参数  
list.reduce(_ + _)  

多个下划线指代多个参数,而不是单个参数的重复运用

高阶函数

高阶函数:接收一个或多个函数作为输入 或 输出一个函数。

函数的参数可以是变量,而函数又可以赋值给变量,由于函数和变量地位一样,所以函数参数也可以是
函数;

常用的高阶函数:map、reduce、flatMap、foreach、filter、count … … (接收函数作为参数)

object HighFunction {  
  def main(args: Array[String]): Unit = {  
      
    val func=(n) => "*" * n  
    // 把函数传入高阶函数  
    (1 to 5).map(func(_)).foreach(println)  
    /**  
     * *  
     * **  
     * ***  
     * ****  
     * *****  
     */  
  
    // 定义一个高阶输出函数  
    val URLBuilder=(ssl: Boolean, domainName: String) => {  
      val schema = if (ssl) "https://" else "http://"  
  
          // 返回一个匿名函数  
      (endPoint: String, query: String) => s"$schema$domainName/$endPoint?$query"  
    }  
  
    val domainName="www.baidu.com"  
    def getUrl = URLBuilder(true, domainName)  
    val endpoint = "show"  
    val query = "id=1"  
    val url = getUrl(endpoint, query)  
    println(url)  
    // https://www.baidu.com/show?id=1  
  }  
}  
  

闭包

闭包是一种函数,一种比较特殊的函数,它和普通的函数有很大区别:

// 普通的函数  
val addMore1 = (x: Int) => x + 10   
// 外部变量,也称为自由变量  
var more = 10  
// 闭包  
val addMore2 = (x: Int) => x + more  
  
// 调用addMore1函数  
println(addMore1(5))  
// 每次addMore2函数被调用时,都会去捕获外部的自由变量 println(addMore2(10))  
more = 100  
println(addMore2(10))  
more = 1000  
println(addMore2(10))  

闭包是在其上下文中引用了自由变量的函数;
闭包引用到函数外面定义的变量,定义这个函数的过程就是将这个自由变量捕获而构成的一个封闭的函 数,也可理解为”把函数外部的一个自由变量关闭进来“。

何为闭包?需满足下面三个条件:
1、闭包是一个函数
2、函数必须要有返回值
3、返回值依赖声明在函数外部的一个或多个变量,用Java的话说,就是返回值和定义的全局变量有关

柯里化

函数编程中,接收多个参数的函数都可以转化为接收单个参数的函数,这个转化过程就叫柯里化 (Currying)。
Scala中,柯里化函数的定义形式和普通函数类似,区别在于柯里化函数拥有多组参数列表,每组参数 用小括号括起来。
Scala API中很多函数都是柯里化的形式。

// 使用普通的方式  
def add1(x: Int, y: Int) = x + y  
  
// 使用闭包的方式,将其中一个函数作为返回值   
def add2(x: Int) = (y:Int) => x + y  
  
// 使用柯里化的方式  
def add(x: Int)(y: Int) = x + y  
  
//调用柯里化函数add  
add(1)(2)  
  
//调用add2  
add2(1)(2)  
  
// 实际上 add 在内存中的形式 和 add2 是一样的  
scala> add _  
val res61: Int => (Int => Int)   
scala> add2 _  
val res62: Int => (Int => Int)   
//在这个函数中,接收一个x为参数,返回一个匿名函数,这个匿名函数的定义是:接收一个Int型参数y,函 数体是x+y。  

部分应用函数

部分应用函数(Partial Applied Function)也叫偏应用函数,与偏函数从名称上看非常接近,但二者之
间却有天壤之别。
部分应用函数是指缺少部分(甚至全部)参数的函数。
如果一个函数有n个参数, 而为其提供少于n个参数, 那就得到了一个部分应用函数。

// 定义一个函数  
def add(x:Int, y:Int, z:Int) = x+y+z  
  
// Int不能省略  
def addX = add(1, _:Int, _:Int) 
addX(2,3)  
addX(3,4)  
  
def addXAndY = add(10, 100, _:Int)  
addXAndY(1)  
  
def addZ = add(_:Int, _:Int, 10)  
addZ(1,2)  
  
// addX,addXAndY, addZ 就是add的部分应用函数  
  
// 省略了全部的参数,下面两个等价。第二个更常用   
def add1 = add(_: Int, _: Int, _: Int) def add2 = add _  

偏函数

偏函数(Partial Function)之所以“偏”,原因在于它们并不处理所有可能的输入,而只处理那些能与至
少一个 case 语句匹配的输入;
在偏函数中只能使用 case 语句,整个函数必须用大括号包围。这与普通的函数字面量不同,普通的函
数字面量可以使用大括号,也可以用小括号;
被包裹在大括号中的一组case语句是一个偏函数,是一个并非对所有输入值都有定义的函数;
Scala中的Partial Function是一个trait,其类型为PartialFunction[A,B],表示:接收一个类型为A的参 数,返回一个类型为B的结果。

// 过滤List中的String类型的元素,并将Int类型的元素加1。  
object PartialFunctionDemo {  
  def main(args: Array[String]): Unit = {  
  
    val partialFunction = new PartialFunction[Any, Int] {  
      override def isDefinedAt(x: Any): Boolean = {  
        println(x.toString)  
        x.isInstanceOf[Int]  
      }  
  
      override def apply(v1: Any): Int = v1.asInstanceOf[Int]+1  
    }  
  
    // 等价于上面  
    val partialFunction2: PartialFunction[Any, Int] = {case x: Int => x+1}  
  
    val list = List("A", 2, "ff", 34, "fdf", 12)  
  
    list.collect(partialFunction).foreach(println)  
  
    // 等价于上一行  
    list.collect{case x: Int => x+1}.foreach(println)  
  }  
}  
  

集合

可变和不可变集合

根据容器中元素的组织方式和操作方式,可以分为有序和无序、可变和不可变等不同的容器类别;

对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。 Scala优先采用不可变集合,不可变集合元素不可更改,可以安全的并发访问。
Scala集合有三大类:Seq(序列)、Set(集)、Map(映射); 所有的集合都扩展自Iterable特质。

Seq

Seq代表按照一定顺序排列的元素序列;
该序列是一种特别的可迭代集合,包含可重复的元素;
元素的顺序是确定的,每个元素对应一个索引值;
Seq提供了两个重要的子特质:

Set

Set(集合)是没有重复元素的对象集合,Set中的元素是唯一的; Set分为可变的和不可变的集合;
默认情况下,使用的是不可变集合(引用 scala.collection.immutable.Set);
使用可变集合,需要引用 scala.collection.mutable.Set 包;

object SetDemo {  
  def main(args: Array[String]): Unit = {  
    val set = Set(1, 2, 3, 4, 5, 6)  
    // drop 无效  
    set.drop(2)  
    println(set)  
  
    import scala.collection.mutable.Set  
    val mutableSet = Set(3, 4, 5)  
    // 添加,移除  
    mutableSet.add(4)  
    mutableSet.add(8)  
    mutableSet.remove(5)  
    println(mutableSet)  
  
    // 使用 += -= 添加,移除元素  
    mutableSet += 10  
    mutableSet -= 4  
    println(mutableSet)  
  
    // & intersect 求交集  
    println("=================intersect====================")  
    println(Set(1, 2, 3) & Set(2, 3, 4))  
    println(Set(1, 2, 3) intersect Set(2, 3, 4))  
  
    // | ++ union 求合集  
    println("=================union====================")  
    println(Set(1, 2, 3) | Set(2, 3, 4))  
    println(Set(1, 2, 3) ++ Set(2, 3, 4))  
    println(Set(1, 2, 3) union Set(2, 3, 4))  
  
    // &~ -- diff 求差集,A中有-B中有的=A中独有的  
    println("=================diff====================")  
    println(Set(1, 2, 3) &~ Set(2, 3, 4))  
    println(Set(1, 2, 3) -- Set(2, 3, 4))  
    println(Set(1, 2, 3) diff Set(2, 3, 4))  
  
  
    // A & B 独有的元素  
    println((Set(1, 2, 3) | Set(2, 3, 4)) -- (Set(1, 2, 3) & Set(2, 3, 4)))  
  }  
}  
  

Map

Map(映射)是一系列键值对的容器;
Scala 提供了可变的和不可变的两种版本的Map, 分别定义在包 scala.collection.mutable 和 scala.collection.immutable 里;
默认情况下,Scala中使用不可变的 Map;
如果要使用可变Map,必须导入scala.collection.mutable.Map;
在Map中,键的值是唯一的,可以根据键来对值进行快速的检索。

object MapDemo {  
  def main(args: Array[String]): Unit = {  
    // 两种定义map的方式  
    val map1 = Map('a'-> 1, 'b' -> 2)  
    val map2 = Map(('a', 1), ('b', 2))  
  
    map1.keys.foreach(println)  
    map1.values.foreach(println)  
  
    println(map1('b'))  
    // java.util.NoSuchElementException: key not found: c  
//    println(map1('c'))  
  
    // get 返回一个option对象,不存在就返回None, 存在就Some  
    println(map2.get('c'))  
  
    if (map2.get('b') != None) {  
        println("Some")  
    } else {  
      println("None")  
    }  
  
    // key不存在就返回默认值  
    val num2 = map1.getOrElse('d', 0)  
    println(num2)  
  
    // 可变的map  
    val map3 = collection.mutable.Map("a"->1, "b"->2)  
    println(map3)  
    map3("a") = 10  
    println(map3)  
    map3("c")=20  
    println(map3)  
  
    // 使用 += -= 添加删除元素  
    map3 += ("d"->4, "f"->5)  
    println(map3)  
  
    map3 -= "d"  
    println(map3)  
  
  
    // exchange key & value  
    val kv: collection.mutable.Map[Int, String] = for ((k, v) <- map3) yield (v, k)  
    println(kv)  
    map3.map(x=>(x._2,x._1)).foreach(println)  
  
    // 使用zip的方式构建map  
    val a = List(1, 2, 3)  
    val b = List("a", "b", "c")  
    val c = a.zip(b).toMap   //返回类型是 immutable.Map  
    println(c)  
    println(map3.mapValues(_+10))  
  }  
}  

集合常用算子

与Java集合的转换

使用 scala.collection.JavaConverters 与Java集合交互。它有一系列的隐式转换,添加了asJava和 asScala的转换方法。

import scala.collection.JavaConverters._  
val list: Java.util.List[Int] = List(1,2,3,4).asJava  
val buffer: scala.collection.mutable.Buffer[Int] = list.asScala  

隐式机制

隐式转换

隐式转换和隐式参数是Scala中两个非常强大的功能,利用隐式转换和隐式参数,可以提供类库,对类
库的使用者隐匿掉具体的细节。
Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自
动将其传入隐式转换函数,转换为另外一种类型的对象并返回,这就是“隐式转换”。

隐式转换需要使用implicit关键字。
使用Scala的隐式转换有一定的限制:

隐式转换函数

Scala的隐式转换最核心的就是定义隐式转换函数,即implicit conversion function。 定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。 隐式转换函数由Scala自动调用,通常建议将隐式转换函数的名称命名为“one2one”的形式。

class Num  
class RichNum(num: Num) {  
  def rich() : Unit = {  
    println("Hello implicit !")  
  }  
}  
  
object ImplicitDemo {  
  implicit def num2RichNum(num: Num):RichNum = new RichNum(num)  
  
  def main(args: Array[String]): Unit = {  
    val num = new Num  
  
    // num对象并没有rich方法,编译器会查找当前范围内是否有可转换的函数  
    // 如果没有则编译失败,如果有则会调用。  
    num.rich()  
  }  
}  

要实现隐式转换,只要在程序可见的范围内定义隐式转换函数即可,Scala会自动使用隐式转换函数。 隐式转换函数与普通函数的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。
隐式转换案例:特殊售票窗口(只接受特殊人群买票,比如学生、老人等),其他人不能在特殊售票窗
口买票。

class SpecialPerson(var name: String)  
class Older(var name: String)  
class Student(var name: String)  
class Worker(var name: String)  
  
object ImplicitDemoTwo {  
  
  def buySpecialTicketWindow(person: SpecialPerson): Unit = {  
    if (person == null) {  
      println("You are not special person!!")  
    } else {  
      println(person.name + " Here's your ticket!")  
    }  
  }  
  
  implicit def any2SpecialPerson(any: Any):SpecialPerson = {  
    any match {  
      case any: Older => new SpecialPerson(any.asInstanceOf[Older].name)  
      case any: Student => new SpecialPerson(any.asInstanceOf[Student].name)  
      case _ => null  
    }  
  }  
  
  def main(args: Array[String]): Unit = {  
    val stu = new Student("stu")  
    val older = new Older("old man")  
    val worker = new Worker("Worker")  
  
    buySpecialTicketWindow(stu)  
    buySpecialTicketWindow(older)  
    buySpecialTicketWindow(worker)  
  }  
}  

隐式参数和隐式值

在函数定义的时候,支持在最后一组参数中使用 implicit,表明这是一组隐式参数。在调用该函数
的时候,可以不用传递隐式参数,而编译器会自动寻找一个 implicit 标记过的合适的值作为参数。 Scala编译器会在两个范围内查找:

object DoublelyDemo {  
  //在print函数中定义一个隐式参数fmt  
  def print(num: Double)(implicit fmt: String): Unit = {  
    println(fmt format(num))  
  }  
  
  def main(args: Array[String]): Unit = {  
  
    print(3.1415)("%.1f")  
  
    //定义一个隐式变量  
    implicit val printFmt = "%.3f"  
    //当调用print函数时没有给第二个隐式参数赋值,  
    //那么Scala会在当前作用域内寻找可见的val或var定义的隐式变量,一旦找到就会应用  
    print(3.1415)  
  }  
}  

类型参数

Scala的类型参数与Java的泛型是一样的,可以在集合、类、函数中定义类型参数,从而保证程序更好的
健壮性。

泛型类

泛型类,顾名思义,其实就是在类的声明中定义一些泛型类型,然后在类内部的字段或者方法,就可以
使用这些泛型类型。
使用泛型类,通常是需要对类中的某些成员,比如某些字段和方法中的参数或变量进行统一的类型限
制,这样可以保证程序更好的健壮性和稳定性。
如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中难免会出现问题,比如传入了不希望
的类型导致程序出问题。
在使用泛型类的时候,比如创建泛型类的对象,只需将类型参数替换为实际的类型即可。 Scala自动推断泛型类型特性:直接给使用泛型类型的字段赋值时,Scala会自动进行类型推断。

class Stack[T1, T2, T3](name: T1) {  
  var age: T2 = _  
  var address: T3 = _  
  
  def getInfo:Unit = {  
    println(s"$name, $age, $address")  
  }  
}  
  
object GenericDemo {  
  def main(args: Array[String]): Unit = {  
    val stack = new Stack[String, Int, String]("lisi")  
    stack.age = 20  
    stack.address = "Beijing"  
    stack.getInfo  
  }  
}  

泛型函数

泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返
回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型
限制。
与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可 以在调用函数时,手动指定泛型类型。

案例:卡片售卖机,可以指定卡片的内容,内容可以是String类型或Int类型

object GenericityFunction {  
  def getCard[T](content: T) = {  
    content match {  
      case content: Int => s"card:$content is Int "  
      case content: String => s"card:$content is String"  
      case _ => s"card:$content"  
	}   
}  
  def main(args: Array[String]): Unit = {  
    println(getCard[String]("hello"))  
    println(getCard(1001))  
   }   
}  

协变和逆变

Scala的协变和逆变是非常有特色的,完全解决了Java中的泛型的一大缺憾!
举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是 Card[Master]的子类?
答案是:不是。因此对于开发程序造成了很多的麻烦。
而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。

class Master  
class Professor extends Master  
class Teacher  
  
//这个是协变,当前类及其子类  
//class Card[+T]  
  
//逆变, 当前类及其父类  
class Card[-T]  
  
object CovarianceDemo {  
  def enterMeet(card: Card[Professor]): Unit = {  
    println("Welcome !")  
  }  
  
  def main(args: Array[String]): Unit = {  
    val masterCard = new Card[Master]  
    val professorCard = new Card[Professor]  
    val teacher = new Card[Teacher]  
  
    enterMeet(masterCard)  
    enterMeet(professorCard)  
//    enterMeet(teacher)  
  }  
}  

Akka

Akka是Java虚拟机平台上构建高并发、分布式和容错应用的工具包和运行时。 Akka用Scala语言编写,同时提供了Scala和Java的开发接口。 Akka处理并发的方法基于Actor模型,Actor之间通信的唯一机制就是消息传递。

Actor

Scala的Actor类似于Java中的多线程编程。 但是不同的是,Scala的Actor提供的模型与多线程有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。 Actor可以看作是一个个独立的实体,Actor之间可以通过交换消息的方式进行通信,每个Actor都有自己的收件箱(Mailbox)。
一个Actor收到其他Actor的信息后,根据需要作出各种相应。消息的类型可以是任意的,消息的内容也 可以是任意的。

ActorSystem

在Akka中,ActorSystem是一个重量级的结构。
它需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个ActorSystem创建很多Actor。

Akka案例