Scala

概述

Scala (Scalable Language, 可伸缩语言) , 从计算机的角度来讲, Scala是一门完整的可伸缩的完全面向对象的软件编程语言。之所以说它可伸缩, 是因为这门语言体现了面向对象, 函数式编程等多种不同的语言范式, 且融合了不同语言新的特性, 同时它也是一门强类型静态类型的语言。在大数据领域中, 其开发效率更高, 更直观, 更容易理解。
Scala编程语言是由联邦理工学院洛桑(EPFL)的Martin Odersky于2003年设计研发的.
官网 : https://www.scala-lang.org/

HelloWord

package cn.lofe.scala

Object HelloWord {
	def main(args: Array[String]): Unit = {
		System.out.println("Hello Scala")
		println("Hello Scala")
	}
}

由于scala语言是基于Java语言开发的,所以也会编译为class文件,那么我们可以通过反编译指令javapjavap -c -l 类名或反编译工具jd-gui.exe来查看scala编译后的代码。

变量和数据类型

注释

Scala注释使用和Java完全一样。

// 单行注释
/*
	多行注释
*/
/**
	文档注释
*/

变量

变量是一种使用方便的占位符,用于引用计算机内存地址,变量创建后会占用一定的内存空间。基于变量的数据类型,操作系统会进行内存分配并且决定什么将被储存在保留内存中。因此,通过给变量分配不同的数据类型,可以在这些变量中存储整数,小数或者字母。
声明格式
var/val 变量名 : 变量类型 = 变量值

	object ScalaVariable {
	    def main(args: Array[String]): Unit = {
	        // 用户名称
	        var username : String = "zhangsan"
	        // 用户密码
	        val userpswd : String = "000000" 
	    }
	}

如果变量的类型能够通过变量值推断出来, name可以省略类型声明, 省略后, 由Scala编译器在编译时自动声明。

	object ScalaVariable {
	    def main(args: Array[String]): Unit = {
	        var username = "zhangsan"
	        val userpswd = "000000" 
	    }
	}

初始化
要求 : 必须显示初始化

	object ScalaVariable {
	    def main(args: Array[String]): Unit = {
	        var username // Error
	        val username = "zhangsan" // Nice
	    }
	}

可变变量
值可以改变的变量, 称之为可变变量, 但是变量类型无法发生改变。Scala中可变变量使用关键字var声明

	object ScalaVariable {
	    def main(args: Array[String]): Unit = {
	        var username : String = "zhangsan"
	    }
	}

不可变变量
值一旦初始化后无法改变的变量, 称之为不可变变量。Scala中不可变变量使用关键字val声明, 类似于Java语言中的final关键字。

	object ScalaVariable {
	    def main(args: Array[String]): Unit = {
	        val username : String = "zhangsan"
	    }
	}

注意 : Java中的字符串(String), 称之为不可变字符串。
标识符
> Scala可以使用两种形式的标识符, 字符数字和符号。
> 使用符号命名时, 可能与代码冲突的字符一般不能使用。
> 当Scala代码编译为字节码文件时, 其内部实现时编译器会使用转义的标识符, 比如 :-> 使用 $colon$minus$greater 来表示这个符号。
> Scala的命名规范采用和Java类似的 “驼峰” 式的命名规范, 且避免使用 “$” 开始和使用 “_” 结尾的标识符, 防止冲突。
> 如果想使用关键字来命名变量时, 可使用转义字符(飘号)进行转义, 其内部实现仍然使用转义来表示该关键字。比如 `private` 。
Scala中的关键字(或保留字)

字符串

在Scala中, 字符串的类型实际上就是Java中的String类, 它本身是没有String类的。
> 字符串连接 :

	val name : String = "zhang san"
	println("name = " + name)//name = zhang san

> 传值字符串

	printf("name = %s", name)//name = zhang san

> 插值字符串

	println(s"name = $name")//name = zhang san
	println(s"name = ${name.substring(0,1)} // name = z

> 多行字符串

	println(
	s""""
		|name = $name
		|sex = $sex
	 """".stripMargin)
	 //name = zhang san
	 //sex = Man

输入输出

> 输入
  从控制台中获取输入

	val age : Int = StdIn.readInt()

从文件中获取输入

	//使用相对路径时, 是相对于当前Project目录 ; 也可使用绝对路径
	val strings = Source.fromFile("input/user.json").getLines()
	for (elem <- strings) {
	  println(elem)
	}
	// OR
	Source.fromFile("input/user.json").foreach(
      elem => {
        println(elem)
      }
    )

> 输出到文件中 : 底层用的是 java 中的 I/O 类

	//使用相对路径时, 是相对于当前Project目录 ; 也可使用绝对路径
	val value = new PrintWriter(new File("output/test.txt"))
	value.write("Hello Scala")
	value.close()

网络
Scala进行网络数据交互时, 采用的也依然是Java中的I/O类。

	// 服务器类
	object TestServer {
	    def main(args: Array[String]): Unit = {
	        val server = new ServerSocket(9999)
	        while ( true ) {
	            val socket: Socket = server.accept()
	            val reader = new BufferedReader(
	                new InputStreamReader(
	                    socket.getInputStream,
	                    "UTF-8"
	                )
	            )
	            var s : String = ""
	            var flg = true
	            while ( flg  ) {
	                s = reader.readLine()
	                if ( s != null ) {
	                    println(s)
	                } else {
	                    flg = false
	                }
	            }
	        }
	    }
	}
	// 客户端类
	object TestClient {
	    def main(args: Array[String]): Unit = {
	        val client = new Socket("localhost", 9999)
	        val out = new PrintWriter(
	            new OutputStreamWriter(
	                client.getOutputStream,
	                "UTF-8"
	            )
	        )
	        out.print("hello Scala")
	        out.flush()
	        out.close()
	        client.close()
	    }
	}

数据类型

Scala是完全面向对象的语言, 所以不存在基本数据类型的概念, 有的只是任意值对象类型(AnyVal)和任意引用对象类型(AnyRef)。
Scala的数据类型
在这里插入图片描述

类型转换

自动类型转换

	// Char → Int
	// Byte → Short → Int → Long → Float → Double
	val c1 : Char = 'A'
	val c2 : Int = c1
	val c3 : Byte = 10
	val c4 : Short = c3
	val c5 : Int = c4

强制类型转换
使用方法的方式进行类型间转换。

	var a : Int = 10
	Var b : Byte = a.toByte

字符串类型转换

	// 所有类型都提供了toString()方法, 可以直接转换为字符串
	val i : Int = 10
	val s1 : String = i1.toString
	// 所有类型都提供了和字符串进行拼接的方法 +
	val s2 = i.+(s1)
	val s3 = i + s1

运算符

Scala中其实是没有运算符的, 所有意义上的运算符都是方法。

  • Scala是完全面向对象的语言, 所以数字其实也是对象。
  • 当调用对象的方法时, “.” 可以省略
  • 如果函数的参数只有一个, 或没有, “()” 可以省略
    例如 :
	val i = 1.+(2)
	val i = 1 + 2

算术运算符

假设变量A为10, B为20。
那么 : 在这里插入图片描述

关系运算符

假设变量A为10, B为20。
那么 :
在这里插入图片描述

赋值运算符

在这里插入图片描述

逻辑运算符

假设变量A为1, B为0。
那么 : 在这里插入图片描述

位运算符

假设变量A为60, B为13, 二者对应的二进制位 A = 00111100 ; B = 00001101
那么 : 在这里插入图片描述

流程控制

分支控制

让程序有选择的执行。分支控制有三种: 单分支、双分支、多分支
单分支

	if (布尔表达式) {
		// 如果布尔表达式为true, 则执行该语句块
	}

双分支

	if (布尔表达式) {
		// 如果布尔表达式为true, 则执行该语句块
	} else {
		// 如果布尔表达式为false, 则执行该语句块
	}

多分支

	if (布尔表达式1) {
		// 如果布尔表达式1为true, 则执行该语句块
	} else if (布尔表达式2) {
		// 如果布尔表达式2位true, 则执行该语句块
	} ... 
	} else {
		// 如果上面条件都不满足, 则执行该语句块
	}

实际上, Scala中的表达式都是有返回值的, 例如

	object ScalaBranch {
	    def main(args: Array[String]): Unit = {
	        val age = 30
	        val result = if ( age < 18 ) {
	            "童年"
	        } else if ( age <= 30 ) {
	            "青年"
	        } else if ( age <= 50 ) {
	            "中年"
	        } else {
	            "老年"
	        }
	        println(result)
	   }
	}

循环控制

循环类型
for循环

  • 语法
	for (循环变量 <- 数据集) {
		// 循环体
	}
	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        for ( i <- Range(1,5) ) { 	// Range(1, 5) 范围集合 [1, 5)
	            println("i = " + i )
	        }
	        for ( i <- 1 to 5 ) {		// 1 to 5 => [1, 5]
	            println("i = " + i )
	        }
	        for ( i <- 1 until 5 ) { 	// 1 until 5 => [1, 5)
	            println("i = " + i )
	        }
	    }
	}
  • 循环守卫
    循环时可以增加条件来决定是否继续循环体的执行, 这里的判断条件称为循环守卫
	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        for ( i <- Range(1,5) if i != 3  ) { // [1, 5) & i != 3 => 1, 2, 4
	            println("i = " + i )
	        }
	    }
	}
  • 循环步长
    Scala的集合也可以设定循环的增长幅度, 也就是所谓的步长step
	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        for ( i <- Range(1,5,2) ) { // [1, 5) & step = 2 => 1, 3
	            println("i = " + i )
	        }
	        for ( i <- 1 to 5 by 2 ) {  // [1, 5] & step = 2 => 1, 3, 5
	            println("i = " + i )
	        }
	        for ( i <- 1 until 5 by 2 ) {  // [1, 5) & step = 2 => 1, 3
     			println("i = " + i )
    		}
	    }
	}
  • 循环嵌套
    两层循环可写进一层内
	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        for ( i <- Range(1,5); j <- Range(1,4) ) {
	            println("i = " + i + ",j = " + j )
	        }
	        for ( i <- Range(1,5) ) {
	            for ( j <- Range(1,4) ) {
	                println("i = " + i + ",j = " + j )
	            }
	        }
	    }
	}
  • 引入变量
    在循环条件中可引入变量
	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        for ( i <- Range(1,5); j = i - 1 ) {
	            println("j = " + j )
	        }
	    }
	}

> 题目 : 如何只使用一次for循环实现九层妖塔。

	val sum = 9
	for (i <- Range(0, sum); j = 2 * i + 1) {
	  println(" " * (sum - i - 1) + "*" * j)
	}

while循环

  • 语法
	while( 循环条件表达式 ) {
	    循环体
	}
	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        var i = 0
	        while ( i < 5 ) {
	            println(i)
	            i += 1
	        }
	    }
	}

do…while循环

	do {
	    循环体
	} while ( 循环条件表达式 )
	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        var i = 5
	        do {
	            println(i)
	        } while ( i < 5 )
	    }
	}

循环中断
Scala是完全面向对象的语言, 取消了break, continue关键字, 采用函数式编程的方式代替

	object ScalaLoop {
	    def main(args: Array[String]): Unit = {
	        scala.util.control.Breaks.breakable {
	            for ( i <- 1 to 5 ) {
	                if ( i == 3 ) {
	                    scala.util.control.Breaks.break
	                }
	                println(i)
	            }
	        }
	    }
	}

嵌套循环
循环中有循环, 就是嵌套循环。

	var i = 1
	var j = 1
	val sum = 9
	while (i <= sum) {
	  j = i
	  while (j <= sum) {
	    print(s"$i*$j=${i * j}\t")
	    j += 1
	  }
	  println()
	  i += 1
	}

函数式编程

Scala是完全面向对象编程, 同时也是完全函数式编程。
> 面向对象编程 : 分解对象, 行为, 属性, 然后通过对象的关系及行为的调用来解决问题。
> 函数式编程 : 将问题分解为一个一个的步骤, 将每个步骤进行封装(函数), 通过调用这些封装好的功能按照指定的步骤, 解决问题。

函数编程 - 基础篇

  • 基本语法
    [修饰符] def 函数名 (参数列表) [:返回值类型] = {函数体}
	def test(str: String): Unit = {
	  println(str)
	}
  • Scala中存在方法与函数两个不同的概念
    方法是类的一部分, 反之, 在类中定义的函数即是方法, 仍然有重载和重写
    函数是一个对象, 可以赋值给一个变量, 不是在类中定义的就是函数, 没有重载和重写

  • 函数的定义

  1. 无参, 无返回值
	def test1(): Unit = {
	}
	
	println(s"test1 : ${test1()}")
  1. 无参, 有返回值
	def test2(): String = {
	  "hello"
	}
	
	println(s"test2 : ${test2()}")
  1. 有参, 无返回值
	def test3(i: Int): Unit = {
	}
	
	println(s"test3 : ${test3(1)}")
  1. 有参, 有返回值
	def test4(i: Int): Int = {
	  i
	}
	
	println(s"test4 : ${test4(1)}")
  1. 多参, 无返回值
	def test5(i: Int, s: String): Unit = {
	  println(s"i : $i ; s : $s")
	}
	
	test5(1, "hello")
  1. 多参, 有返回值
	def test6(i: Int, s: String): String = {
	  s"i : $i ; s : $s"
	}
	
	println(test6(1, "hello"))
  • 函数参数的个数最多是 22 个
    声明时可以超过22个
    但是将函数作为对象赋值给变量时会报错

  • 可变参数 String*
    可变参数必须放置在参数列表的最后

	def test7(s: String*): Unit = {
	  for (elem <- s) {
	    println(elem)
	  }
	}
	
	test7("1", "2", "3")
  • Scala中函数的参数默认使用val声明, 无法修改
    def test00(s: Int): Unit = {
      //s = 1 // error : Reassignment to val
    }
  • 参数默认值
    调用时, 可以给设置默认值的参数赋值, 则该参数值被覆盖
    调用时, 可以不给设置默认值的参数赋值, 则该参数值为默认值
	def test8(s: String, i: Int = 1): Unit = {
	  println(s"s : $s ; i : $i")
	}
	
	test8("1")
	test8("2", 3)
  • 带名参数, 指明要赋值的参数
	def test9(s: String = "000", i: Int): Unit = {
	  println(s"s : $s ; i : $i")
	}
	
	test9(i = 1)

函数式编程 - 噩梦篇

  • Scala 中函数有一个原则 : 至简原则
  1. 省略 return
    当函数需要返回值时, 可以将函数体中最后一行执行的代码作为返回结果, 所以可以省略return关键字
	def test1(): String = {
	  "test1"
	}
	
	println(test1())
  1. 省略返回值类型
    如果编译器可以推断出函数的返回值类型, 那么返回值类型可以省略
	def test2() = {
	  "test2"
	}
	
	println(test2())
  1. 省略 {}
    如果函数体中只有一行代码, 那么大括号可以省略
	def test3() = "test3"
	
	println(test3())
  1. 省略 ()
    如果函数没有提供参数, 那么调用时, 小括号可以省略
    如果函数没有提供参数, 那么声明时, 小括号可以省略, 且调用时, 也必须省略
	def test4 = "test4"
	
	println(test4)
  1. 省略 = : 过程函数(procedure)
    函数如果明确使用Unit声明, 那么函数体中的return关键字不起作用
    函数体中如果明确使用return关键字, 那么声明时返回值类型不能省略
	def test5: Unit = return "test5"
	
	println(test5)
	
	// 明确函数就是没有返回值, 但是Unit又不想声明, 那么此时就必须连等号一起省略, 但是大括号不能省略
	def test6 {
	  return "test6"
	}
	
	println(test6)
  1. 省略 函数名 和 def : 匿名函数
    当只关心代码逻辑, 而不关心函数名时, 函数名和def关键字可以省略
    需要调用的话, 可以将函数赋值给一个变量
	val t = () => "test7"
	println(t())

函数式编程 - 地狱篇

函数是函数式编程中最重要的概念, “一等公民”。
Scala是完全面向对象编程, 所以函数也是对象。

  1. 函数可以作为对象赋值给变量
	def test1(): String = {
	  "zhang san"
	}
	// 将函数赋值给变量, 那么这个变量其实就是函数, 可以调用
	// 函数如果没有参数列表, 那么调用时可以省略 "()"
	// 如果此时希望将函数不执行, 而是当成一个整体赋值给变量, 那么需要使用 "_"
    val f1 = test1() // f1   :   String // 变量类型
    val f2 = test1 _ // f2   :    () => String // 方法类型
    println(f2()) // zhang san
    // 另外的解决方法 : 指定该变量的类型为函数类型   参数列表 => 返回值类型
    val f3: () => String = test1
    println(f3()) // zhang san
  1. 函数可以作为参数传递给其他的函数
	def test2(name: () => String): String = {
	  name()
	}
	
	// 将函数赋值为变量
	val result1 = test1 _
	println(test2(result1))
	// 将函数作为参数使用时, 一般不关心函数的名称, 所以一般使用匿名函数
	// 匿名函数规则 : (参数列表) => {代码逻辑}
	val result2 = test2(() => "li si")
	println(result2)
	println(test2(() => "li si"))

至简原则

	def test4(age: Int, out: Int => String): String = {
	  out(age + 1)
	}
	
	val result3 = test4(20, (add: Int) => {
	  s"Next year's age : $add"
	})
	// 如果逻辑代码只有一行, "{}" 可以省略
	val result4 = test4(20, (add: Int) => s"Next year's age : $add")
	// 如果匿名函数的参数类型可以推断出来, 那么类型可以省略
	val result5 = test4(20, (add) => s"Next year's age : $add")
	// 如果匿名函数的参数列表只有一个或没有, 那么 "()" 可以省略
	val result6 = test4(20, add => s"Next year's age : $add")
	// 如果匿名函数中参数在逻辑代码中只使用了一次, 那么参数和=>可以省略
	// 使用下划线代替参数
	val result7 = test4(20, "Next year's age : " + _)
	// val result8 = test4(20,s"Next year's age : $_")
	def sum(x: Int, y: Int): Int = {
	  x + y
	}
	
	def calcAnalysis(f: (Int, Int) => Int): Int = {
	  val boyCnt = 23
	  val girlCnt = 25
	  f(boyCnt, girlCnt)
	}
	
	val f = sum _
	println(calcAnalysis(f))
	
	calcAnalysis((x: Int, y: Int) => {
	  x + y
	})
	
	// 简化 ↓
	calcAnalysis((x: Int, y: Int) => x + y)
	calcAnalysis((x, y) => x + y)
	calcAnalysis(_ + _) // 最简版

函数可以作为函数的返回值返回

	def test1(i: Int): Int = {
	  i * 2
	}
	
	def fun1(): Int => Int = {
	  test1 _
	}
	
	// 调用
	val a = fun1() // 当前fun1函数的执行结果为函数, 那么此时a就是函数
	println(a(10))
	// 调用
	println(fun1()(10))
  • 当函数作为返回值使用时, 一般会使用嵌套函数
	def fun2() = {
	  def test2(i: Int): Int = {
	    i * 2
	  }
	
	  test2 _
	}
	
	// 调用
	println(fun2()(10))
	
	// 如果不想使用下划线返回对象, 那么需要显示声明函数的返回值类型
	def fun3(): Int => Int = {
	  def test2(i: Int): Int = {
	    i * 2
	  }
	
	  test2
	}

闭包 : 改变生命周期

  • Scala中, 执行方法时, 会有压栈, 弹栈, 栈帧的概念, 先进后出
    当Scala中的函数在执行时, 会先被编译成Java中的方法, 才能在JVM上运行
    所以, 下面的代码, 会先执行test3(压栈), 弹栈后(局部变量失效), 执行sum(压栈), 此时的i是怎么从test3传到sum中的呢?此时需要延长变量i的生命周期。
    函数在使用外部变量时, 如果外部变量失效时, 会将这个变量包含到当前函数的内部, 形成闭合的使用效果, 改变了变量的生命周期。
    将这种操作称之为closure(闭包)

  • Scala2.12 : 底层编译器重新声明了内部函数的参数, 将使用的外部变量作为内部函数的参数使用。“不同的i, 相同的值”
    Scala2.11 : 底层闭包会被编译为匿名函数类, 如果使用外部变量, 会将外部变量作为类的属性供内部函数使用。即使没有使用外部的变量, 可能也会有闭包的效果, 只是没有包含外部的变量。

  • 匿名函数肯定为闭包, 将函数赋值给变量使用也是闭包, 嵌套的内部函数在外部使用也是闭包。

  • Spark如何判断闭包 : 判断类名中是否为匿名函数类

	def test3(i: Int) = {
	  def sum(j: Int) = {
	    i + j
	  }
	
	  sum _
	}
	
	println(test3(10)(20))

函数的柯里化 : 多个参数列表
所谓的柯里haul, 其实就是多个参数列表

  1. 简化嵌套函数开发
  2. 将复杂的参数逻辑简单化, 可以支持更多的语法
	// 使用函数柯里化的方式声明函数
	def test4(i: Int)(j: Int)(f: (Int, Int) => Int): Int = {
	  f(i, j)
	}
	
	println(test4(10)(20)(_ + _))

控制抽象 : 将代码逻辑作为参数传递给函数

  • 控制抽象时写框架必不可少的语法
	// 参数列表中如果有多行逻辑, 那么可以采用大括号代替小括号
	// 举例 : breakable是一个函数
	breakable { // 捕捉异常
	  for (i <- 1 to 5) {
	    if (i == 3) {
	      break //抛出异常
	    }
	    println("i = " + i)
	  }
	}
	println("main...")
  • 如果函数参数想传递代码逻辑, 那么类型声明方式为 :
    参数名 : => 返回值类型
	def test5(f: => Unit): Unit = {
	  f
	}
	
	// 调用
	// 因为参数类型中没有声明小括号, 所以调用时, 也不能加小括号
	test5(println("test5..."))

递归 - 普通递归

  1. 方法执行过程中调用自身
  2. 存在可以跳出递归的逻辑, 不然可能包StackOverflowError(栈溢出)
  3. 方法调用时, 传递的参数之间应该存在规律
  4. Scala中递归方法需要明确返回值类型
	// 阶乘 : 一个大于1的数的阶乘等于这个数乘以自身减一的阶乘
	val num = 10
	
	def factorial1(num: Int): Int = {
	  if (num < 1) {
	    1
	  } else {
	    num + factorial1(num - 1) // 此处的结果需要依据下一层函数的结果, 会导致方法连续压栈
	  }
	}
	
	println(factorial1(5)) // 15
	println(factorial1(100000)) // StackOverflowError
	// 原因 : 递归太深 → 方法太多 → 栈帧太多 → 栈溢出

递归 - 尾递归

  • 在逻辑的最后一行调用自身, 且递归的方法不依赖外部的变量
  • 编译器会将该代码逻辑自动优化为while循环
	def factorial2(num: Int, result: Int): Int = {
	  if (num < 1) {
	    result
	  } else {
	    factorial2(num - 1, num + result)
	  }
	}
	
	println(factorial2(5, 1)) // 15
	println(factorial2(100000, 1)) //705082705

惰性函数 : 延迟加载

	def test6():String={ // ① 如果此处返回的是10000条User数据
	  println("xxx")
	  "zhang san"
	}
	val name1 = test6() // ② 此处加载该方法, 却没使用, 导致大量数据占用内存
	println("*********") // ③ 如果此过程执行了1个小时, 那么会导致内存长时间占用
	println("name = " + name1) // ④ 使用并释放内存
	//xxxxxx
	//*********
	//name = zhang san
  • 解决 : 使用惰性函数延迟加载, 则当用到数据时才加载数据
  • 底层lazy延迟加载功能是编译器在编译时产生大量的方法进行调用实现的。
	lazy val name2 = test6()
	println("*********")
	println("name = " + name2)
	//*********
	//xxxxxx
	//name = zhang san

面向对象编程

基础面向对象编程

面向对象编程 - 包 (package)

  • Java中
  1. 声明包
    域名 + 项目名称 + 模块名 + 程序类型名称
  2. package的作用 :
    2.1 管理类
    2.2 区分类
    2.3 访问权限
    2.4 编译类
    Java语法中, package包语法的作用没有那么强大
  • Scala语言对package语法进行了功能的补充, 使之更加强大
  1. package和源码文件的物理路径没有关系
    Scala会按照package的声明编译指定路径的class, 源码路径随便放
  2. package关键字可以多次声明
    当前类编译后会在最后的package所在的包中生成class文件
	//package cn.lofe.test.Package
	package cn
	package lofe
	package scala
	package test
  1. package可以使用层级结构
    在package包名的后面增加大括号, 设定作用域范围以及层级关系
    子包不需要import父包就可以使用父包的内容(作用域)
	package test{
	  package childTest{
	    object Package{
	
	    }
	  }
	}
  1. 包也可以当成对象来使用
    package中为了和Java兼容, 默认不能声明属性和函数(方法), 但是是可以声明类的
    Scala提供了一种特殊的对象声明方式 : 包对象
    该对象的内容只供当前包内的所有类使用
    可以将一个包中共同性的方法或属性在包对象中声明
    包对象
    面向对象编程 - import
  • Java中
  1. Java中的import作用是导类
    import java.sql.*
  2. Java中引入了import static静态导入, 导入类的静态属性和静态方法
  • Scala语言在Java语法基础上进行了扩展
  1. import可以声明在任意地方
  2. Java中默认导入的类 : java.lang包下的类
    Scala中默认导入的类 : java.lang包下的类 ; scala包中的类 ; Predef(类似于Java中的静态导入)
  3. Scala中的import可以导包
	import java.sql
	val date = new sql.Date()
  1. 导入一个包中所有的类
    Scala中使用下划线代替java中的星号
	import java.sql._
  1. 可以在一行中导入同一个包的多个类
	import java.util.{ArrayList, HashMap}
  1. 使用import关键字将包中的类隐藏掉
	import java.sql.{Date=>_, _} // 导入所有的类及隐藏Date类
  1. 使用import关键字给指定类起别名
	import java.util.{Date=>UtilDate}
	// 或
	type UtilDate = java.util.Date
	// 如 : Scala中没有字符串, 直接使用java中的字符串
	val s:String = "abc" // 底层 : type String = java.lang.String
  1. Scala默认import是按照当前包的相对路径进行导入的。
    此时双亲委派机制没起作用
    如果不想使用相对路径, 可以采用特殊路径(root根路径)访问
	new java.util.HashMap // 当前包下的HashMap类
	new _root_.java.util.HashMap // java自带的HashMap类
  1. Scala中可以采用import关键字导入对象
    导入对象只能对val声明的对象进行导入, var是不可以的
val user = new User()
import user._
test1()
test2()
  • 双亲委派机制
    在这里插入图片描述
    类加载过程 :

    加载前会判断执行的位置, 然后调用相应的加载器进行加载
    应用类加载器加载前会委托扩展类加载器加载, 扩展类加载器委托启动类加载器加载
    而此时, 启动类加载器中有JDK自带的类, 则加载该类
    如果启动类加载器中没有该类, 则会委托扩展类加载器加载, 扩展类加载器委托应用类加载器
    如果还找不到该类, 则报异常

面向对象编程 - class

  • class和object的区别
  1. object在编译时会产生两个类文件
    当前类的文件 和 单例的类文件
    > 如 : Test_Class.class , Test_Class$.class
    class在编译时只会产生当前类的class文件
    > 如 : Test_Class.class
    2.class用来修饰普通的类, 而object用于修饰伴随着这个类所产生的一个单例对象, 用于模仿java中的静态语法。
    object中的方法和属性可以通过类名直接访问, 类似于静态语法
  2. 一般将使用object声明的类称之为伴生类, 对应的那个单例对象称之为伴生对象
    后统一将使用class声明的类称之为伴生类, 使用object声明的类为伴生对象
  3. IDEA中如果一个Scala源码文件的图标为黄色的o, 表示这个文件中只有伴生对象。
    IDEA中如果一个Scala源码文件的图标为蓝黄相间的c, 表示这个文件中有伴生对象和伴生类。
    IDEA中如果一个Scala源码文件的图标为灰色的文件图标和三条橘色的线, 表示这个文件内容很杂。
    IDEA中如果一个Scala源码文件的图标为蓝色的c和三条橘色的线, 表示这个文件中只有类(伴生类)。
  • 问题 : Thread的sleep和wait的区别?
    核心区别 : sleep是静态方法 、wait是成员方法
    静态方法和类型相关, 和对象无关
    成员方法和对象相关
	Thread t1 = new Thread;
	Thread t2 = new Thread;
	t1.start;
	t2.start;
	// sleep方法和t1无关, 就不可能让t1休眠, 而是让当前执行该方法的线程(main)休眠。
	// 所以 sleep无法释放对象锁
	t1.sleep(1000);
	// wait方法和t2相关, 所以会让t2等待
	// 所以wait可以释放对象锁
	t2.wait();
  • Scala中class可以继承(extends)父类
    Scala当省略类型时, 编译器会自动将构建对象的类型进行推断
	val child = new Child()

如果需要使用多态操作, 那么必须显示声明类型

	val child : Parent = new Child()

面向对象编程 - field

	object Field {
	  def main(args: Array[String]): Unit = {
	    // 在类中声明属性, 就等同于在类中声明局部变量的感觉, 可以使用var/val声明, 可以通过对象在外部访问
	    val user = new User
	    user.name = "zhang san"
	    user.age = 23
	    println(user.name + user.age)
	    // Scala中声明的属性, 在编译时, 都是private修饰的, 并同时提供了公共的get/set方法
	    //    但是, 其命名方式并没有遵循bean规范, 这样会在很多框架中无法使用
	    //    可以采用在声明属性时添加注解的方式使其在编译时额外生成遵循bean规范的get/set方法, @BeanProperty
	    // 使用属性时, 获取值和赋值时, 相当于调用了get/set方法
	    // 使用val声明的属性, 在编译时还会使用final修饰, 且没有set方法
	  }
	
	  class User {
	    // 属性
	    // 变量应该显示的初始化
	    var name: String = _
	    // 如果想要像java中类的属性初始化一样, 需要采用特殊的符号 : 下划线
	    @BeanProperty
	    var age: Int = _
	    // 如果属性使用val来声明, 那么初始值不能使用下划线
	  }
	}

面向对象编程 - 访问权限

	class User01 {
	  /*
	  Java : 4中访问权限
	    private : 		  私有权限		同类
	    (default) : 	  包权限		  同类, 同包
	    protected : 	  受保护权限	同类, 同包, 子类
	    public : 		    公共权限		任意地方
	
	   Scala : 4种访问权限
	    (default) : 	  公共权限		任意地方
	    private : 		  私有权限		同类
	    private[包名] : 包私有权限	同类, 同包
	    protected : 	  受保护权限	同类, 子类
	 */
	  // 如果属性声明为private, 在编译时生成的set/get方法也会使用private修饰
	  private val name: String = "zhang san"
	  // 使用@BeanProperty注解后, 属性不能声明为private
	  @BeanProperty
	  val age: Int = 23
	  // 包名可以向上使用, 但必须在当前包的作用域范围内使用
	  private[lofe] val sex: String = "man"
	  // Scala中的源码文件中可以声明多个公共类
	}
	
	class User02 {}
	
	class USer03 {}

面向对象编程 - 方法

  • 方法的重载 : \多个方法名相同, 但是参数列表(参数个数/参数类型/参数顺序)不相同
    数值类型, 在重载的方法中会提升精度
    引用类型, 在重载的方法中如果找不到对应的类型, 会从类树往上查找
  • 方法的重写 : 方法的重写一定要存在父子类, 子类重写父类相同方法的逻辑
    方法名一致, 参数列表保持一致, 异常范围不大于父类, 访问权限不低父类
    既然父类和子类有相同的方法, 形成了方法的重写, 那么在调用时, 无法确定到底执行哪一个方法, 那么需要<动态绑定机制>
    动态绑定机制 : 程序执行过程中, 如果调用了对象的成员方法时, 会将方法和对象的实际内存进行绑定, 然后调用。
    动态绑定与属性无关。
	object Method {
	  def main(args: Array[String]): Unit = {
	    val user = new User
	    // 面向对象 - 方法
	    // 所谓的方法其实就是在类中声明的函数
	    // 1. 默认方法
	    //    java.lang.Object
	    user.toString
	    user.hashCode()
	    //    scala中提供的方法
	    user.isInstanceOf[User]
	    user.asInstanceOf[User]
	    //    Predef提供的方法
	    // 获取对象的类型信息(方法内存)
	//    val clazz: Class[_ <: User] = user.getClass
	    val clazz: Class[_ <: User] = classOf[User]
	  }
	}
	
	class User {}
  • 面向对象编程 - 方法 - apply(应用)
	object Method_Apply {
	  def main(args: Array[String]): Unit = {
	    //    面向对象 - 方法 - apply(应用)
	
	    //    apply主要作用用于构建对象
	    //    私有的构造方法是没法通过new的方式构建对象的, 此时可以使用apply构建
	    val user2 = User.apply() // apply的方式构建对象
	
	    //    使用new, 等同于调用对象的构造方法构建对象
	    //    如果不使用new来构建对象, 那么等同于调用伴生对象的apply方法
	    val user1 = new User() // new的方式构建对象
	
	    //    apply方法一般用于object伴生对象中构建对象
	    //    伴生对象可以访问伴生类的所有属性和方法
	    //    Scala会自动识别apply方法, 所以调用伴生对象的apply方法时, apply方法名可以省略
	    val user3 = User()
	
	    //    如果将伴生对象不使用小括号操作, 那么等同于将伴生对象赋值给变量, 而不是调用apply方法
	    //    apply方法如果想要被编译器自动识别, 那么不能省略小括号
	    val user4 = User
	
	    //    apply方法主要用于构建对象, 但是这个构建对象不一定是当前类的对象
	    //    apply方法可以重载
	  }
	}
	
	class User {
	}
	
	object User {
	  def apply(): User = {
	    new User()
	  }
	}
  • < 方法的重载>

    • 多个方法名相同, 但是参数列表(参数个数/参数类型/参数顺序)不相同
    • 数值类型, 在重载的方法中会提升精度
    • 引用类型, 在重载的方法中如果找不到对应的类型, 会从类树往上查找
  • <方法的重写>

    • 方法的重写一定要存在父子类, 子类重写父类相同方法的逻辑, 要求方法名一致, 参数列表保持一致, 异常范围不大于父类, 访问权限不低父类
    • 既然父类和子类有相同的方法, 形成了方法的重写, 那么在调用时, 无法确定到底执行哪一个方法, 则需要<动态绑定机制>
    • 动态绑定机制 : 程序执行过程中, 如果调用了对象的成员方法时, 会将方法和对象的实际内存进行绑定, 然后调用。动态绑定与属性无关。
      > Scala中 :
    • 如果子类重写父类的完整方法, 需要显示增加override关键字修饰
    	override def test() : Unit = {}
    
    • 如果子类重写父类的抽象方法, 直接补充完整即可
    • Scala中属性也有重写的概念, 但只能重写val修饰的属性
    • 子类重写父类的抽象属性, 那么需要将属性补充完整即可
    • 子类重写父类的完整属性, 那么需要使用abstract修饰
    • 子类重写父类的属性时, 该属性不能是可变的, 因为Scala中各类的属性在编译时, 会自动生成私有属性和对应的get/set方法
  • 面向对象编程 - 构造方法
    > 使用new关键字创建的对象其实等同于调用类的构造方法
    > Scala是一个完全面向对象的语言, 同时还是一个完全面向函数的语言
    > Scala中类其实也是一个函数, 类名其实就是函数, 类名后面可以增加括号, 表示函数参数列表
    这个类名所代表的的函数其实就是构造方法/构造函数
    构造方法执行时, 会完成类的主题内容的初始化

	val user = new User()
	class User {
	}

> Scala中提供了两种不同类型的构造方法

  1. 主构造方法 : 在类名后的构造方法。可以完成类的初始化
  2. 辅助构造方法 : 为了完成类初始化的辅助功能而提供的构造方法
    声明方法 : def this()
    要求 : 在使用辅助构造方法时, 必须直接或间接的调用主构造方法
    辅助构造也存在重载的概念
	class User(){
	  def this(name:String)={
	    this()
	  }
	  def this(name:String, age:Int)={
	    this(name)
	  }
	}

> 给构造方法增加参数的目的是从外部将数据传递到对象的属性中
> Scala中一般构造方法的参数用于属性的初始化, 所以为了减少数据的冗余, 可以使用关键字var/val将构造参数当成类的属性来使用

	class User(var name:String){
	}

> 构建子类对象时, 应该首先构建父类对象, 如果父类的构造方法有参, 那么子类在构建父类对象也应该有参。
> 需要在父类名称的后面加括号就可以传参了

	class Person (name : String) {
	}
	class User(name : String) extends Person (name) {
	}

例题 : 关于Object类中clone方法的调用

	public class CloneTest {
	    public static void main(String[] args) {
	        // Object中的clone方法如下
	        // protected native Object clone() throws CloneNotSupportedException;
	        // protected : 受保护权限 ; 同类, 同包, 子类可调用
	
	        // 默认所有的类都会继承Object类, 因此会获取其中的所有属性和方法
	        //      那么, 在CloneTest类中通过创建对象的方式, 能否调用User类中的clone方法
	        User user = new User();
	//        user.clone; // 不能!
	        // 问题1. 方法的调用者 : cn.lofe.scalao1.day06.CloneTest
	        //        方法的提供者 : java.lang.Object
	        // 问题2. user.clone;中"."的作用 : 表示从属关系 : user对象的clone方法, 并不是调用的意思
	        //  显然, CloneTest类与Object并不同类, 也不同包, 但是却无法调用, 因此 :
	        // 问题3. CloneTest和Object没有父子关系
	        // 结论 : CloneTest和User类继承于Object类, 会分别加载Object类中的属性及方法,
	        //      当各自调用各自父类(Object)中的元素时, 相安无事, 当CloneTest调用user对象的clone方法时,
	        //      由于CloneTest类与User类的父类(Object)并无继承关系, 故无法调用
	
	    }
	}
	
	class User {
	
	}

调用机制

高阶面向对象编程

面向对象编程 - 抽象
抽象的对象一般理解为不完整的对象

  • 抽象类 : 使用abstract修饰
  • 抽象方法 : 只有声明而没有实现的方法, 无需使用abstract修饰
  • 抽象属性 : 属性只有声明, 没有初始化
    > Scala中引入了**<抽象属性><属性的重写>**的概念
    > 如果一个类中有抽象方法/属性, 那么这个类一定是抽象类
    > 如果一个类是抽象类, 但内部不一定有抽象方法/属性
    > 抽象类无法直接构造对象, 只能被继承
    > 如果子类继承抽象类, 必须实现父类的抽象方法/属性, 否则仍为抽象类
    > 若子类重写父类的抽象属性, 只需将属性补充完整即可
    > 若子类重写父类的完整属性, 则需使用abstract修饰
    > 抽象属性在编译时, 不会产生类的属性, 而是产生属性的set/get方法, 且是抽象的
    > 重写属性, 等同于普通属性的声明 : 属性、set、get
	abstract class User { // 抽象类
		var name : String // 抽象属性
		var age : Int = 23 // 完整属性
		def test() : Unit // 抽象方法
	}
	abstract class SubUser1 extends User {
		// 未实现父类的抽象方法, 故仍为抽象类
	}
	class SubUser2 extends User {
		var name : String = "zhang san" // 重写父类的抽象属性
		// 重写父类的完整属性时, 该属性不能是可变的
		// abstract var age : Int = 24 // error
		abstract val age : Int = 24 // 重写父类的完整属性
		
		def test() : Unit = {} // 实现了父类的抽象方法, 则该类非抽象类, 可被继承
	}

面向对象编程 - 特质(接口)
Java中 :

  • 抽象类 : 将子类共通的内容给抽取出来, 放在父类中
    • 父类不能直接构建对象, 必须通过子类来创建, 所以声明为抽象类
    • 抽象类一般就在父子继承和方法重写的时候用的比较多
  • 接口 : 和类不一样, 不是一个体系, 可以多继承
    • 将接口理解为规范和规则。 如果一个类符合指定的规则, 就应该实现该接口
	public class InterfaceTest {
	    public static void main(String[] args) {
	        Test test = new User(); // User继承Person, Person实现Test, 那么User实现Test?
	        System.out.println(User.class.getInterfaces().length); // User实现接口的数量: 0
	        // 结论 : User并没有实现Test接口, 而是多态的传递
	    }
	}
	
	interface Test {
	
	}
	
	class Person implements Test{
	
	}
	
	class User extends Person {
	
	}

Scala中 :

  • 没有接口的概念, 也就没有interface关键字
  • Scala可以将多个类中相同的特征从类里剥离出来, 形成特殊的语法结构, 称之为"特质", 使用关键字trait声明。
  • 如果一个类符合这个特质, 那么就可以将这个特质混入到类中, 使用extends关键字
	// 声明特质
	trait Operate {
	  // 特质中可以声明抽象方法
	  def oper () : Unit
	}
	
	// 类混入特质
	class MySQL extends Operate{
	  // 实现特质中的抽象方法
	  override def oper(): Unit = {}
	}
  • 可将特质理解为接口
    • 特质之间可以继承, 且是多继承的, 使用with关键字连接
  • 可将特质理解为抽象类,
    • 特质可以继承其他类, 并可以混入其他特质, 使用with关键字连接
  • 如果特质混入类中, 需使用extends或with关键字
    • 将特质理解为抽象类, 那么就可以被继承(extends), 且可以混入其他特质或类(with)。
    • 如果特质为抽象的, 那么混入的类应该实现其父类的抽象方法/属性, 否则仍为抽象类
    • 如果特质中存在具体方法, 重写父类的方法/属性时, 需要使用override关键字
  • Scala中可以直接使用java中的代码, 由于scala中没有接口, 故java中所有的接口在scala中都当成特质来用
	trait ParentTest1 {}
	
	trait ParentTest2 {}
	
	trait Test extends Exception with ParentTest1 with ParentTest2 {
	  // 抽象方法
	  def test(): Unit
	
	  // 具体方法
	  def test1(): Unit = {
	    println("test...")
	  }
	}
	
	class User extends Test with ParentTest1 with java.io.Serializable {
	  def test(): Unit = {}
	
	  override def test1(): Unit = {
	    print("test...")
	  }
	}
  • 动态混入
    • 动态混入(动态扩展)完全遵循OCP(开闭原则)开发原则
    • 特质中不仅仅有抽象方法, 还可以有具体的方法
    • 如果对象声明后想要扩展功能, 可以不需修改源码, 直接使用动态混入实现
	object Interface {
	  def main(args: Array[String]): Unit = {
	    val mysql = new MySQL with Operate
	    mysql.insert()
	  }
	}
	
	trait Operate {
	  def insert() : Unit = {
	    println("insert data...")
	  }
	}
	class MySQL {
	  def select() : Unit = {
	    print("select data...")
	  }
	}
  • 初始化顺序

类/特质的初始化顺序 :

  1. 类如果有父类, 而父类混入特质1, 而特质1又继承特质2
  2. 那么特质2会先初始化
  3. 然后特质1再初始化
  4. 接下来父类初始化
  5. 如果当前类还有特质的话, 那么特质会初始化
  6. 当前类初始化
	object Interface {
		def main(args: Array[String]): Unit = {
			new SubUser() // abdce
		}
	}
	
	trait Parent {
	    print("a")
	}
	
	trait Test extends Parent {
	    print("b")
	}
	
	trait SubTest extends Parent {
	   print("c") 
	}
	
	class User extends Test {
	    print("d")
	}
	
	class SubUser extends User with SubTest {
	    print("e')
	}

如果类混入多个特质时, 特质的初始化顺序为从左到右

	object Interface {
  		def main(args: Array[String]): Unit = {
			new User() // abcd
  		}
	}
	
	trait Test1 {
	    println("a")
	}
	
	trait Test2 {
	    println("b")
	}
	
	trait Test3 {
	    println("c")
	}
	
	class User extends Test1 with Test2 with Test3 {
	    println("d")
	}
  • 功能执行顺序

类/特质的功能执行顺序 :

  1. 如果类混入多个特质时, 功能的执行顺序为从右向左的
  2. 特质中的super其实有特殊的含义, 表示的不是父类特质, 而是上级特质
  3. 如果想要改变执行顺序, 需要制定特质的类型
	object Interface {
	  def main(args: Array[String]): Unit = {
	    new MySQL().operData() // 向数据库中操作数据...
	  }
	}
	
	trait Operate {
	  def operData(): Unit = {
	    println("操作数据...")
	  }
	}
	
	trait DataBase extends Operate {
	  override def operData(): Unit = {
	    print("向数据库中")
	    super.operData()
	  }
	}
	
	class MySQL extends DataBase {}

输出结果 : 向数据库中操作数据…

	object Interface {
	  def main(args: Array[String]): Unit = {
	    new MySQL().operData() // 向日志中向数据库中操作数据
	  }
	}
	
	trait Operate {
	  def operData(): Unit = {
	    println("操作数据...")
	  }
	}
	
	trait DataBase extends Operate {
	  override def operData(): Unit = {
	    print("向数据库中")
	    super.operData()
	  }
	}
	
	trait Log extends Operate {
	  override def operData(): Unit = {
	    print("向日志中")
	    super.operData() // 调用上级特质
	  }
	}
	
	class MySQL extends DataBase with Log {
	
	}

输出结果 : 向日志中向数据库中操作数据…

如果想要改变执行顺序, 需要制定特质的类型

	object Interface {
	  def main(args: Array[String]): Unit = {
	    new MySQL().operData() // 向日志中操作数据
	  }
	}
	
	trait Operate {
	  def operData(): Unit = {
	    println("操作数据...")
	  }
	}
	
	trait DataBase extends Operate {
	  override def operData(): Unit = {
	    print("向数据库中")
	    super.operData()
	  }
	}
	
	trait Log extends Operate {
	  override def operData(): Unit = {
	    print("向日志中")
	    // 如果想要改变执行顺序, 需要制定特质的类型
	    super[Operate].operData() // 调用Operate的OperData()
	  }
	}

	class MySQL extends DataBase with Log {
	
	}

输出结果 : 向日志中操作数据…
在这里插入图片描述
扩展

  • 枚举
	object Color extends Enumeration {
	    val RED = Value(1, "red")
	    val YELLOW = Vlaue(2, "yellow")
	    val BLUE = Vlaue(2, "blue")
	}
	
	mian {
	    println(Color.RED)
	    println(Color.BLUE)
	}
  • 应用类
	object Test extends APP {
	    println("xxx")
	    println("yyy")
	}
  • 类型检查和转换
    • 判断对象是否为某个类型的实例
    	val bool: Boolean = person.isInstanceOf[Person]
    
    • 将对象转换为某个类型的实例
    	val p1: Person = person.asInstanceOf[Person]
    
    • 获取类的信息
    	val pClass: Class[Person] = classOf[Person]
    

集合

Scala的集合有三大类 : 序列Seq、集Set、映射Map, 所有集合都扩展自Iterable特质。
对于几乎所有的集合类, Scala都同时提供了可变和不可变的版本。

可变和不可变集合一般可根据集合所在包名进行区分
scala.collection.immutable(不可变)
在这里插入图片描述
scala.collection.mutable(可变)
在这里插入图片描述
默认情况下, Scala提供的集合都是不可变的。immutable

  • 可变数组
    • 内存存储的数据可以动态操作, 而不会产生新的集合
    • 可变数组提供了大量对数据操作的方法, 基本是方法名都是英文单词
  • 不可变数组
    • 对数据的操作都会产生新的集合
    • 提供对数据的操作方法相对来说较少, 而且都是一些符号

数组

不可变数组

	// 创建数组
	// val array = new Array[Int](4)
	val array = Array[Int](4)
	println(array.length) // 4
	// 使用()访问数组元素
	array(0) = 1
	array(1) = 2
	array(2) = 3
	array(3) = 4
	// 遍历数组中元素
	for (elem <- array) {
	  println(elem)
	}
	array.foreach(elem => print(elem))
	// 至简原则
	array.foreach(println)
	// 按照规则生成字符串
	println(array.mkString(" ")) // 1 2 3 4
	
	// Array属于不可变数组
	// 对不可变集合的数据操作会产生新的数组
	// 使用apply的方式, 创建数组
	val array1 = Array(1, 2, 3, 4) // 1, 2, 3, 4
	val array2 = Array(1, 2, 3, 4)
	// 添加操作
	// :+  向数组的后面添加数据
	val newArray1: Array[Int] = array1 :+ 5 // 1, 2, 3, 4, 5
	// +:  向数组的前面添加数据
	// 如果集合的方法采用冒号结尾, 那么运算结果从右向左执行
	val newArray2: Array[Int] = newArray1.+:(6) // 6, 1, 2, 3, 4, 5
	val newArray3: Array[Int] = 7 +: newArray2 // 7, 6, 1, 2, 3, 4, 5
	// ++  将集合的数据添加到当前集合
	val newArray4: Array[Int] = array1 ++ array2 // 1, 2, 3, 4, 1, 2, 3, 4
	// 向数组[Int]中添加字符串
	val newArray5: Array[Any] = array :+ "5" // 1, 2, 3, 4, 5
	
	// 判断集合是否为空
	println(array.isEmpty) // false
	
	// 工具类
	// 合并数组
	val newArray6: Array[Int] = Array.concat[Int](array1, array2) // 1, 2, 3, 4, 1, 2, 3, 4
	// 创建指定范围的数组
	val array3: Array[Int] = Array.range(0, 4) // 0, 1, 2, 3
	// 创建并填充指定数量重复元素的数组
	val array4: Array[Int] = Array.fill[Int](5)(0) // 0, 0, 0, 0, 0
	
	// 多维数组
	val array5: Array[Array[Int]] = Array.ofDim[Int](3, 3)
	array5(0) = Array[Int](1)
	array5(1) = Array[Int](1, 2)
	array5(2) = Array[Int](1, 2, 3)
	// 添加新元素
	val array6: Array[Array[Int]] = array5:+Array[Int](1, 2, 3, 4)
	// 遍历数组
	array6.foreach(array => println(array.mkString(", ")))

可变数组

	// 创建数组
	// 可变数组不能通过new的方式创建数组
	var arrayBuffer: ArrayBuffer[Int] = ArrayBuffer[Int](1, 2, 3, 4)
	
	// 访问数据
	println(arrayBuffer(1))
	// 追加数据
	arrayBuffer.append(5) // 1, 2, 3, 4, 5
	// 向指定的位置(索引)插入数据
	arrayBuffer.insert(1, 6, 7) // 1, 6, 7, 2, 3, 4, 5
	// 修改数据
	arrayBuffer(1) = 8 // 1, 8, 7, 2, 3, 4, 5
	arrayBuffer.update(2, 9) // 1, 8, 9, 2, 3, 4, 5
	// 删除数据
	arrayBuffer.remove(1, 2) // 1, 2, 3, 4, 5
	arrayBuffer.remove(4) // 1, 2, 3, 4
	// 添加数据
	arrayBuffer += 5 // 1, 2, 3, 4, 5
	arrayBuffer +:= 0 // 0, 1, 2, 3, 4, 5
	// 添加数组元素
	val newArrayBuffer: ArrayBuffer[Int] = arrayBuffer ++ ArrayBuffer[Int](6, 7) // 0, 1, 2, 3, 4, 5, 6, 7
	arrayBuffer ++= ArrayBuffer[Int](6, 7) // 0, 1, 2, 3, 4, 5, 6, 7
	// 遍历数组
	arrayBuffer.foreach(println)
	
	// 判断集合是否为空
	println(arrayBuffer.isEmpty) // false
	
	// 可变数组 => 不可变数组
	val array1: Array[Int] = arrayBuffer.toArray
	
	// 不可变数组 => 可变数组
	val array2: mutable.Buffer[Int] = array1.toBuffer

Seq集合

不可变List

	// 创建集合
	// 默认不可变集合List是抽象类, 无法使用new的方式进行实例化, 可以采用apply的方式
	val list = List(1, 2, 3, 4)
	// 访问数据
	println(list(1)) // 2
	// 增加数据
	val newList1: List[Int] = list :+ 5 // List(1, 2, 3, 4, 5)
	val newList2: List[Int] = list.+:(5) // List(5, 1, 2, 3, 4)
	val newList3: List[Int] = 5 +: list // List(5, 1, 2, 3, 4)
	// 连接集合(合并集合)
	val newList4: List[Int] = list ++ List(5, 6) // List(1, 2, 3, 4, 5, 6)
	val newList5: List[Int] = List.concat(list, List(5, 6)) // List(1, 2, 3, 4, 5, 6)
	// 更新数据
	val newList6: List[Int] = list.updated(1, 5) // List(1, 5, 3, 4)
	// 遍历集合
	list.foreach(println)
	
	// 判断集合是否为空
	println(list.isEmpty) // false
	
	// 创建并填充指定数量重复元素的集合
	val list4: List[Int] = List.fill[Int](4)(0) // List(0, 0, 0, 0)
	
	
	
	// 空集合
	val nil1: List[Nothing] = Nil // List()
	val nil2: Nil.type = Nil // List()
	// 空集合一般用于增加数据
	val list1: List[Int] = 1 :: 2 :: 3 :: Nil // List(1, 2, 3)
	val list2: List[Any] = 4 :: 5 :: list1 :: Nil // List(4, 5, List(1, 2, 3))
	// 将list1拆分后添加到新集合, 这种操作称之为<扁平化>
	val list3: List[Int] = 4 :: 5 :: list1 ::: Nil // List(4, 5, 1, 2, 3)

可变List

	// 创建可变List
	val listBuffer: ListBuffer[Int] = ListBuffer(1, 2, 3, 4)
	// 访问数据
	println(listBuffer(1))
	// 增加数据
	listBuffer.append(5) // ListBuffer(1, 2, 3, 4, 5)
	listBuffer += 6 // ListBuffer(1, 2, 3, 4, 5, 6)
	val newListBuffer1: ListBuffer[Int] = listBuffer :+ 7 // ListBuffer(1, 2, 3, 4, 5, 6, 7)
	val newListBuffer2: ListBuffer[Int] = listBuffer.+:(7) // ListBuffer(7, 1, 2, 3, 4, 5, 6)
	val newListBuffer3: ListBuffer[Int] = 7 +: listBuffer // ListBuffer(7, 1, 2, 3, 4, 5, 6)
	val newListBuffer4: ListBuffer[Int] = listBuffer ++ ListBuffer[Int](7, 8) // ListBuffer(7, 1, 2, 3, 4, 5, 6, 7, 8)
	listBuffer ++= ListBuffer[Int](7)
	// 插入数据
	listBuffer.insert(1, 0, 0) // ListBuffer(1, 0, 0, 2, 3, 4, 5, 6, 7)
	// 修改数据
	listBuffer(1) = 9 // ListBuffer(1, 9, 0, 2, 3, 4, 5, 6, 7)
	val newLlistBuffer5: ListBuffer[Int] = listBuffer.updated(2, 9) // ListBuffer(1, 9, 9, 2, 3, 4, 5, 6, 7)
	// 删除数据
	listBuffer.remove(1, 2) // ListBuffer(1, 2, 3, 4, 5, 6, 7)
	listBuffer.remove(6) // ListBuffer(1, 2, 3, 4, 5, 6)
	val newListBuffer6: ListBuffer[Int] = listBuffer - 6 // ListBuffer(1, 2, 3, 4, 5)
	listBuffer -= 6 // ListBuffer(1, 2, 3, 4, 5)
	// 遍历集合
	listBuffer.foreach(println)
	println(listBuffer.mkString(", "))
	
	// 判断集合是否为空
	println(listBuffer.isEmpty) // false
	
	// 可变集合 => 不可变集合
	val list1: List[Int] = listBuffer.toList
	// 不可变集合 => 可变集合
	val list2: mutable.Buffer[Int] = list1.toBuffer

Set集合

不可变Set

	// 默认的集合是不可变集合
	// 数据是<无序, 不可重复>的
	val set: Set[Int] = Set(1, 2, 3, 4 , 1) // Set(1, 2, 3, 4)
	// 访问数据
	println(set(1)) // true
	// 增加数据
	val newSet1: Set[Int] = set + 5 // Set(5, 1, 2, 3, 4)
	val newSet2: Set[Int] = set ++ Set[Int](5, 6) // Set(5, 1, 6, 2, 3, 4)
	// 删除数据
	val newSet3: Set[Int] = set - 1 // Set(2, 3, 4)
	val newSet4: Set[Int] = set -- Set[Int](1, 2) // Set(3, 4)
	// 遍历数据
	set.foreach(println)
	println(set.mkString(", "))
	
	// 判断集合是否为空
	println(set.isEmpty) // false

可变Set

	// 数据是<无序, 不可重复>的
	// 创建集合
	val set: mutable.Set[Int] = mutable.Set(1, 2, 3, 4, 1) // Set(1, 2, 3, 4)
	// 访问数据
	println(set(1)) // true
	// 增加数据
	set.add(5) // Set(1, 5, 2, 3, 4)
	val newSet1: mutable.Set[Int] = set + 6 // Set(1, 5, 2, 6, 3, 4)
	set += 6 // Set(1, 5, 2, 6, 3, 4)
	// 修改数据
	// 添加
	set.update(7, true) // Set(1, 5, 2, 6, 3, 7, 4)
	// 删除
	set.update(5, false) // Set(1, 2, 6, 3, 7, 4)
	// 删除数据
	val newSet2: mutable.Set[Int] = set - 7 - 4 // Set(1, 2, 6, 3)
	set -= 7 // Set(1, 2, 6, 3, 4)
	// 遍历数据
	println(set.mkString(", "))
	set.foreach(println)
	
	// 判断集合是否为空
	println(set.isEmpty) // false
	
	val set1: mutable.Set[Int] = mutable.Set(1, 2, 3, 4)
	val set2: mutable.Set[Int] = mutable.Set(3, 4, 5, 6)
	
	// 交集
	val newSet3: mutable.Set[Int] = set1 & set2 // Set(3, 4)
	// 差集
	val newSet4: mutable.Set[Int] = set1 &~ set2 // Set(1, 2)

Map集合

不可变Map

	// 数据是<无序, key不可重复(覆盖)>的
	// 创建集合
	val map: Map[String, Int] = Map("a" -> 6, "b" -> 2, "c" -> 3, "d" -> 4, "e" -> 5, "a" -> 1)
	// 访问数据
	// 获取指定key的值
	val i1: Int = map("a") // 1
	val i2: Int = map.apply("b") // 2
	// 获取可能存在的值
	val maybeInt1: Option[Int] = map.get("c") // Some(3)
	val maybeInt2: Option[Int] = map.get("z") // None
	// 获取可能存在的值。 若存在, 则返回该值 ; 若不存在, 则返回默认值(此处为-1)
	val i3: Int = map.get("z").getOrElse(-1) // -1
	val i4: Int = map.getOrElse("z", -1) // -1
	
	// 增加数据
	val newMap1: Map[String, Int] = map + ("e" -> 6) // Map(e -> 6, a -> 1, b -> 2, c -> 3, d -> 4)
	val newMap2: Map[String, Int] = map ++ Map("d" -> 5, "e" -> 4) // Map(e -> 4, a -> 1, b -> 2, c -> 3, d -> 5)
	// 修改数据
	val newMap3: Map[String, Int] = map.updated("e", 100) // Map(e -> 100, a -> 1, b -> 2, c -> 3, d -> 4)
	// 删除数据
	val newMap4: Map[String, Int] = map - "e" - "d" // Map(a -> 1, b -> 2, c -> 3)
	// 遍历集合
	map.foreach(println)
	println(map.mkString(", "))
	
	// 判断集合是否为空
	println(map.isEmpty) // false
	
	// 创建空集合
	val empty: Map[Nothing, Nothing] = Map.empty

可变Map

	    // 数据是<无序, key不可重复(覆盖)>的
	    val map: mutable.Map[String, Int] = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
	    // 访问数据(与不可变Map方式相同)
	    // 添加数据
	    val newMap1: mutable.Map[String, Int] = map + ("d" -> 4) // Map(b -> 2, d -> 4, a -> 1, c -> 3)
	    map += ("d" -> 4) // Map(b -> 2, d -> 4, a -> 1, c -> 3)
	    val newMap2: mutable.Map[String, Int] = map ++ mutable.Map("d" -> 4, "e" -> 5) // Map(e -> 5, b -> 2, d -> 4, a -> 1, c -> 3)
	    map ++= mutable.Map("e" -> 5, "f" -> 6) // Map(e -> 5, b -> 2, d -> 4, a -> 1, c -> 3, f -> 6)
	    // 修改数据
	    map.update("e", 8) // Map(e -> 8, b -> 2, d -> 4, a -> 1, c -> 3, f -> 6)
	    // 删除数据
	    map.remove("e") // Map(b -> 2, d -> 4, a -> 1, c -> 3, f -> 6)
	    val newMap3: mutable.Map[String, Int] = map - "d" // Map(b -> 2, a -> 1, c -> 3, f -> 6)
	    map -= "d" // Map(b -> 2, a -> 1, c -> 3, f -> 6)
	//    map.clear() // Map()
	    // 遍历数据
	    map.foreach(println)
	    println(map.mkString(", "))
	
	    val seq: Seq[(String, Int)] = map.toSeq
	    val array: Array[(String, Int)] = map.toArray
	    val set: Set[(String, Int)] = map.toSet
	    val list: List[(String, Int)] = map.toList
	
	    val map1: Map[String, Int] = seq.toMap
	    val map2: Map[String, Int] = array.toMap
	    val map3: Map[String, Int] = set.toMap
	    val map4: Map[String, Int] = list.toMap

Tuple(元组)

	// Scala中, 我们可以将多个无关的数据元素封装为一个整体, 这个整体称之为元组。
	// 换言之, 元组就是容纳元素的容器, 其中最多只能容纳22个元素, 但元素类型无限制
	
	// 创建元组
	val tuple: (Int, String, Int) = (1, "zhang san", 30)
	// 访问数据
	// 根据顺序访问
	println(tuple._1) // 1
	println(tuple._2) // zhang san
	println(tuple._3) // 30
	// 根据索引访问
	println(tuple.productElement(0)) // 1
	println(tuple.productElement(1)) // zhang san
	println(tuple.productElement(2)) // 30
	// 迭代器访问
	val iterator: Iterator[Any] = tuple.productIterator
	while (iterator.hasNext) {
	  println(iterator.next())
	}
	// 获取其长度
	println(tuple.productPrefix) // Tuple3
	
	// 当元组中的元素只有两个时, 我们称之为<对偶元组>, 也称之为<键值对>
	val kv1: (String, Int) = ("a", 1)
	val kv2: (String, Int) = "a" -> 1
	// Map中的元素其实就是<元组>
	val map: Map[String, Int] = Map(("a", 1), ("b", 2), ("c", 3))
	for (elem <- map) {
	  println(elem._1 + "=" + elem._2)
	}

集合常用方法

	val list = List(1, 2, 3, 4)
	val list2 = List(3, 4, 5, 6)
	// 获取集合的长度
	println(list.length) // 4
	println(list.size) // 4
	// 遍历数据
	println(list.mkString(", "))
	list.foreach(println)
	val iterator: Iterator[Int] = list.iterator
	while(iterator.hasNext){
	  println(iterator.next())
	}
	// 判断是否为空
	println(list.isEmpty) // false
	// 简单运算
	println(list.sum) // 10
	println(list.product) // 24
	println(list.min) // 1
	println(list.max) // 4
	
	// 函数式编程中使用最多的算法就是递归算法
	
	// 头 : 第一个元素
	val head: Int = list.head // 1
	// 尾 : 除第一个之外的元素
	val tail: List[Int] = list.tail // List(2, 3, 4)
	// 尾迭代
	val tails: Iterator[List[Int]] = list.tails
	// 最后一个元素
	val i1: Int = list.last // 4
	// 初始化 : 除最后一个之外的元素
	val ints1: List[Int] = list.init // List(1, 2, 3)
	// 初始化迭代
	val inits: Iterator[List[Int]] = list.inits
	// 反转集合
	val ints2: List[Int] = list.reverse // List(4, 3, 2, 1)
	// 判断集合汇总是否包含某个元素
	val bool: Boolean = list.contains(2) // true
	// 去重
	val ints3: List[Int] = list.distinct // List(1, 2, 3, 4)
	val ints4: List[Int] = list.toSet.toList // List(1, 2, 3, 4)
	// 取数据
	val ints5: List[Int] = list.take(1) // List(1)
	val ints6: List[Int] = list.takeRight(1) // List(4)
	// 丢弃数据
	val ints7: List[Int] = list.drop(1) // List(2, 3, 4)
	val ints8: List[Int] = list.dropRight(1) // List(1, 2, 3)
	// 集合并集
	val ints9: List[Int] = list.union(list2) // List(1, 2, 3, 4, 3, 4, 5, 6)
	// 集合交集
	val ints10: List[Int] = list.intersect(list2) // List(3, 4)
	// 集合差集
	val ints11: List[Int] = list.diff(list2) // List(1, 2)
	// 切分集合
	val tuple: (List[Int], List[Int]) = list.splitAt(2) // (List(1, 2),List(3, 4))
	// 滑动
	val iterator1: Iterator[List[Int]] = list.sliding(2)
	while(iterator1.hasNext){
	  println(iterator1.next())
	}
	// 滚动
	val iterator2: Iterator[List[Int]] = list.sliding(2, 2)
	while(iterator2.hasNext){
	  println(iterator2.next())
	}
	// 拉链
	val tuples: List[(Int, Int)] = list.zip(list2) // List((1,3), (2,4), (3,5), (4,6))
	// 数据索引拉链
	val index: List[(Int, Int)] = list.zipWithIndex // List((1,0), (2,1), (3,2), (4,3))
	//============= 映射转换(map) ===============
	val list1 = List(1, 2, 3, 4, 5, 6)
	
	// 需求 : 将list1集合中所有元素 * 2 倍
	// 方法一 : yield
	val newList1: List[Int] = for (elem <- list1) yield {
	  elem * 2
	}
	// 方法二 : 映射转换(map)
	// map方法可以将集合通过制定的转换规则变成新的集合
	// 制定的转换规则适用于集合中的每一个元素
	val newList2: List[Int] = list1.map(_ * 2)

	// 在kv数据处理过程中, 如果k保持不变, 只对v进行处理, 可以采用mapValues()方法
	val map = Map(("a", 1), ("b", 2), ("c", 3))
	map.mapValues(_ * 2) // Map(a -> 2, b -> 4, c -> 6)
	
	//============== 扁平化(flatten) ==============
	// 所谓的扁平化, 就是将整体拆分成个体使用
	// 扁平化操作默认只能对外层数据进行操作, 内层的数据无法操作
	val list2 = List(List(List(1, 2)), List(List(3, 4)))
	
	// 需求 : 获取list2集合中的所有数字
	// 1::list:::Nil
	val newList3: List[List[Int]] = list2.flatten // List(List(1, 2), List(3, 4))
	val newList4: List[Int] = list2.flatten.flatten // List(1, 2, 3, 4)
	
	//============== 扁平化映射(flatMap) ==============
	val list3 = List("hello scala", "hello spark")
	
	// 需求 : 获取list3集合中所有的单词
	// 方法一 : 扁平化 -> 映射转换
	val newList5: List[List[String]] = list3.map(_.split(" ").toList) // List(List(hello, scala), List(hello, spark))
	val newList6: List[String] = newList5.flatten // List(hello, scala, hello, spark)
	// 方法二 : 扁平化 + 映射转换
	// 方法中的参数
	val newList7: List[String] = list3.flatMap(_.split(" ").toList) // List(hello, scala, hello, spark)
	
	// 从原理上来讲, 做不了扁平化, 但是逻辑上可以实现
	val list4 = List(1, 2, 3, 4)
	println(list4.flatMap(num => List(num * 2))) // List(2, 4, 6, 8)
	object Method_3 {
	  def main(args: Array[String]): Unit = {
	    val list = List(1, 2, 3, 4)
	    //============== 过滤 ==============
	    val newList1: List[Int] = list.filter(_ % 2 == 0) // List(2, 4)
	
	    //============== 分组 ==============
	    val newList2: Map[Int, List[Int]] = list.groupBy(_ % 2) // Map(1 -> List(1, 3), 0 -> List(2, 4))
	
	    //============== 排序 ==============
	    // 默认升序
	    val newList3: List[Int] = list.sortBy(word => word) // List(1, 2, 3, 4)
	    // 降序操作
	    val newList4: List[Int] = list.sortBy(word => -word) // List(4, 3, 2, 1)
	    val newList5: List[Int] = list.sortBy(word => word)(Ordering.Int.reverse) // List(4, 3, 2, 1)
	    // 自定义规则排序
    	val list4 = List("1", "5", "3", "4")
    	val newList: List[String] = list4.sortWith((left, right) => (left.toInt - right.toInt) > 0) // List(5, 4, 3, 1)
	
	    // 需求 : 对User对象进行排序 : 先按照年龄升序, 再按照名字降序
	    val user1 = new User()
	    user1.name = "wangwu"
	    user1.age = 30
	
	    val user2 = new User()
	    user2.name = "lisi"
	    user2.age = 20
	
	    val user3 = new User()
	    user3.name = "zhangsan"
	    user3.age = 20
	
	    val users = List(user1, user2, user3)
	
	    // Tuple自动比较大小
	    // 规则 : 先比较第一个元素, 再比较第二个元素, 以此类推...默认升序
	    val resultList: List[User] = users.sortBy(user =>
	      (-user.age, user.name))(Ordering.Tuple2[Int, String].reverse) // List(<zhangsan,20>, <lisi,20>, <wangwu,30>)
	  }
	}
	
	class User {
	  var name: String = _
	  var age: Int = _
	
	  override def toString: String = "<" + name + "," + age + ">"
	} 
	val list1 = List(1, 2, 3, 4)
	val list2 = List(3, 4, 5, 6)
	// 拉链
	// 两个集合的相同位置的数据进行关联
	val tuples: List[(Int, Int)] = list1.zip(list2) // List((1,3), (2,4), (3,5), (4,6))
	val list3 = List(5, 4, 3, 2, 1)
	// 不同长度的List拉链
	val tuples1: List[(Int, Int)] = list1.zip(list3) // List((1,5), (2,4), (3,3), (4,2))
	// 自拉链
	val tuples2: List[(Int, Int)] = list3.zip(list3) // List((5,5), (4,4), (3,3), (2,2), (1,1))
	// 数据索引拉链
	val index: List[(Int, Int)] = list1.zipWithIndex // List((1,0), (2,1), (3,2), (4,3))
	
	// 滑动
	// 数据指定的范围进行滑动, 这个范围称之为窗口
	val iterator1: Iterator[List[Int]] = list1.sliding(2) // 每个窗口两条数据, 步长默认为1
	while(iterator1.hasNext){
	  print(iterator1.next() + " ") // List(1, 2) List(2, 3) List(3, 4) List(1, 2)
	}
	
	val iterator2: Iterator[List[Int]] = list1.sliding(2, 3) // 每个窗口两条数据, 步长为3
	while(iterator2.hasNext){
	  print(iterator2.next() + " ") // List(1, 2) List(4)
	}
	
	// 自定义规则排序
	val list4 = List("1", "5", "3", "4")
	val newList: List[String] = list4.sortWith((left, right) => (left.toInt - right.toInt) > 0) // List(5, 4, 3, 1)
	// ============ Reduce(简化规约) ============
	// reduce中传递的参数的规则 : 参数和返回值类型相同
	// Scala中的集合的计算基本都是两两计算
	
	val list = List(1, 2, 3, 4)
	val i1: Int = list.reduce(_ - _) // -8
	// 原理 : (((1 - 2) - 3) - 4) = 10
	
	// 源码中, reduce操作就是reduceLeft
	val i2: Int = list.reduceLeft(_ - _) // -8
	
	val i3: Int = list.reduceRight(_ - _) // -2
	// 原理 : (1 - (2 - (3 - 4))) = -2
	
	// ================ 折叠 =================
	// 将集合之外的数据和集合内的数据进行聚合操作
	
	// fold的参数 : [(z : A1)((A1, A1) => A1)] >> z为zero, 表示数据处理的初始值
	// fold方法在进行数据处理时, 外部的数据应该和内部的数据的类型保持一致
	val i4: Int = list.fold(10)(_ - _) // 0
	
	// 源码中, fold的操作就是foldLeft
	// fold, foldLeft, foldRight方法的返回值类型为初始值类型
	val str1: String = list.foldLeft("a")(_ + _) // a1234
	// 原理 : (((("a" + 1) + 2) + 3) + 4)
	val str2: String = list.foldRight("a")(_ + _) // 1234a
	// 原理 : (1 + (2 + (3 + (4 + "a"))))
	
	// 需求 : 合并集合(聚合)
	//    Map("a" -> 1, "b" -> 2, "c" -> 3)
	//    Map("a" -> 4, "d" -> 5, "c" -> 6)
	//    ===> Map(a -> 5, b -> 2, d -> 5, c -> 9)
	val map1: mutable.Map[String, Int] = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
	val map2: mutable.Map[String, Int] = mutable.Map("a" -> 4, "d" -> 5, "c" -> 6)
	val newMap: Map[String, Int] = map1.foldLeft(map2)((map, kv) => {
	  map(kv._1) = map.getOrElse(kv._1, 0) + kv._2
	  map
	}).
	  toList.
	  sortBy(_._1).
	  toMap
	println(newMap) // Map(a -> 5, b -> 2, c -> 9, d -> 5)
	
	// ==================== 扫描 =======================
	// fold方法直接获取最终的结果
	// scan方法类似fold, 但是会将中间的处理结果也保留
	val newList1: List[Int] = list.scan(10)(_-_) // List(10, 9, 7, 4, 0)
	val newList2: List[String] = list.scanLeft("a")(_+_) // List(a, a1, a12, a123, a1234)
	val newList3: List[String] = list.scanRight("a")(_+_) // List(1234a, 234a, 34a, 4a, a)
	
	// =================== 队列(Queue) =========================
	// 创建队列
	val que = new mutable.Queue[String]()
	// 添加元素(进队)
	que.enqueue("a", "b", "c") // Queue(a, b, c)
	que += "d" // Queue(a, b, c, d)
	// 获取元素(出队)
	// 先进先出
	que.dequeue() // Queue(b, c, d)
	que.dequeue() // Queue(c, d)
	que.dequeue() // Queue(d)
	
	// =================== 并行 ===================
	// Scala为了充分使用多核CPU, 提供了并行集合, 用于多核环境的并行计算
	// Vector(串行)
	val result1 = (0 to 100).map{x => Thread.currentThread.getName}
	// ParVector(并行)
	val result2 = (0 to 100).par.map{x => Thread.currentThread.getName}
	
	println(result1)
	println(result2)

案例实操

需求 : 将文件中的单词出现的次数统计并排序取前三名
输入数据 :
目录 : 当前Project\input\WordCount.txt

Scala Hello Spark
Spark Test Java Test
Scala Word Scala Test
Spark Stock Word Sink Source
From Test Scala

输出数据 :

(Scala,4)
(Test,4)
(Spark,3)

代码实现 :

	// 1. 从文件中按行获取数据
	val lineList: List[String] = Source.fromFile("input/WordCount.txt").getLines().toList
	// 2. 将每行数据切分为单词
	val wordList: List[String] = lineList.flatMap(_.split(" "))
	// 3. 按单词进行分组
	val wordGroupMap: Map[String, List[String]] = wordList.groupBy(word => word)
	// 4. 对单词出现的次数进行统计
	val wordToCountMap: Map[String, Int] = wordGroupMap.map(kv => (kv._1, kv._2.length))
	// 5. 按照v进行降序
	val sortList: List[(String, Int)] = wordToCountMap.toList.sortBy(_._2)(Ordering.Int.reverse)
	// 6. 取Top3
	val result: List[(String, Int)] = sortList.take(3)
	// 输出
	result.foreach(println)

简化后代码 :

	Source.fromFile("input/WordCount.txt").
	  getLines().
	  toList.
	  flatMap(line => line.split(" ")).
	  groupBy(word => word).
	  map(kv => (kv._1, kv._2.length)).
	  toList.
	  sortBy(_._2)(Ordering.Int.reverse).
	  take(3).foreach(println)

需求 :1. 将targetList中的数据进行WordCount后排序取前三名。

val targetList = List(
 (“hello”, 4),
 (“hello spark”, 3),
 (“hello spark scala”, 2),
 (“hello spark scala hive”, 1)
)

输出数据 :

(hello,10)
(spark,6)
(scala,3)

实现方式一 :

    // 获取单词
    val lineList: List[String] = targetList.map(k => (k._1 + " ") * k._2)
    val wordList: List[String] = lineList.flatMap(line => line.split(" "))
    // 分组
    val wordGroupMap: Map[String, List[String]] = wordList.groupBy(word => word)
    // 统计
    val resultMap: Map[String, Int] = wordGroupMap.map(kv => (kv._1, kv._2.length))
    // 降序
    val resultSortList: List[(String, Int)] = resultMap.toList.sortBy(_._2)(Ordering.Int.reverse)
    // 取Top3
    val resultList: List[(String, Int)] = resultSortList.take(3)
    // 打印
    println(resultList.mkString("\n"))

实现方式一简化后代码 :

	println(targetList.map(k => (k._1 + " ") * k._2).
	  flatMap(a => a.split(" ")).
	  groupBy(word => word).
	  map(kv => (kv._1, kv._2.length)).
	  toList.
	  sortBy(kv => kv._2)(Ordering.Int.reverse).
	  take(3).
	  mkString("\n"))

实现方式二 :

	def main(args: Array[String]): Unit = {
	    val targetList = List(
	      ("hello", 4),
	      ("hello spark", 3),
	      ("hello spark scala", 2),
	      ("hello spark scala hive", 1)
	    )
	    // 需求 :
	    //    1. 将上面的数据进行WordCount后排序取前三名!
	    //    2. 使用2种不同的方式。
	
	    // 结果一 :
	    // ("hello", 4)
	    // ("hello", 3), ("spark", 3)
	    // ("hello", 2), ("spark", 2), ("scala", 2)
	    // ("hello", 1), ("spark", 1), ("scala", 1), ("hive", 1)
	
	    println(targetList.
	      flatMap(kv => kv._1.split(" ").map(word => (word, kv._2))). // 获取结果一
	      groupBy(_._1). // 根据word分组 Map[String, List[Tuple[String, Int]]]
	      map(kv => (kv._1, kv._2.map(_._2).sum)). // 获取word出现的次数count Map[String, Int]
	      toList.sortBy(_._2). // 根据count排序 Map[String, Int]
	      take(3). // 取Top3
	      mkString("\n"))
	  }

模式匹配

概述

Scala中的模式匹配类似于Java中的switch语法,但是scala从语法中补充了更多的功能,可以按照指定的规则对数据或对象进行匹配, 所以更加强大。

	int i = 20
	switch (i) {
	    default : 
	        System.out.println("other number");
	        break;
	    case 10 :
	        System.out.println("10");
	        //break;
	    case 20 : 
	        System.out.println("20");
	        break;
	}

匹配规则

	//============== 匹配常量(Constant) ===================
	def describe1(x: Any): String = x match {
	  case 5 => "Int five"
	  case "hello" => "String hello"
	  case true => "Boolean true"
	  case '+' => "Char +"
	  case _ => "Other thing"
	}
	
	println(describe1(5)) // Int five
	
	//============== 匹配类型(Type) ====================
	def describe2(x: Any) = x match {
	  case i: Int => "Int"
	  case s: String => "String"
	  case l: List[_] => "List"
	  case a: Array[_] => "Array"
	  case otherThing => "otherThing else" + otherThing
	}
	
	println(describe2(a)) // Int
	
	//============== 匹配数组(Array) ==================
	val array1 = Array(
	  Array(0),
	  Array(1, 0),
	  Array(0, 1, 0),
	  Array(1, 1, 0),
	  Array(1, 1, 0, 1),
	  Array("hello", 90))
	for (arr <- array1) {
	  val result = arr match {
	    case Array(0) => "0"
	    case Array(x, y) => x + "," + y
	    case Array(0, _*) => "以0开头的数组"
	    case _ => "something else"
	  }
	  println("result = " + result)
	}
	
	//============= 匹配列表(List) ===============
	val array2 = Array(List(0), List(1, 0), List(0, 0, 0), List(8, 8))
	for (arr <- array2) {
	  val result = arr match {
	    case List(0) => "0"
	    case List(x, y) => x + "," + y
	    case List(0, _*) => "以0开头"
	    case _ => "something else"
	  }
	  println("result = " + result)
	}
	
	//============== 匹配元组(Tuple) =================
	val array3 = Array((0, 1), (1, 0), (1, 1), (1, 0, 2))
	for (arr <- array3) {
	  val result = arr match {
	    case (0, 0) => "(0, 0)"
	    case (0, _) => "(0, *)"
	    case (x, 0) => "(*, 0)"
	    case (x, y) => s"($x, $y)"
	    case _ => "something else"
	  }
	  println(result)
	}
	
	//============== 匹配对象 ==================
	class User1(var name: String, var age: Int)
	object User1 {
	  // 使用参数自动构建对象
	  def apply(name: String, age: Int): User1 = new User1(name, age)
	  // 使用对象自动获取参数
	  def unapply(user: User1): Option[(String, Int)] = {
	    Option((user.name, user.age))
	  }
	}
	
	val user1 = User1("zhang san", 11)
	// Scala中模式匹配对象时, 会自动调用对象的unapply方法进行匹配
	def result2 = user1 match {
	  case User1("zhang san", 11) => "yes"
	  case _ => "no"
	}
	println(result2)
	
	// ============= 样例类 ===============
	// 使用case关键字声明的类, 称之为样例类
	// 专门用于匹配对象
	// 1. 样例类在编译时, 会自动生成伴生类以及一些常用方法
	//    如 : apply、unapply、toString、equals、hashCode、copy
	// 2. 样例类的构造参数默认使用val声明, 所以参数其实就是类的属性
	//    如果想要更改属性, 需要显示的将属性使用var声明(不建议这样做)
	// 3. 样例类自动实现可序列化接口
	// 4. 在实际开发中, 一般都使用样例, 便于开发
	case class User2(name:String, age:Int)
	val user2:User2 = User2("zhang san", 23)
	val result3 = user2 match{
	  case User2("zhang san", 23) => "yes"
	  case _ => "no"
	}
	println(result3)

应用场景

	// 变量声明 (元组)
	val (id, name, age) = (1, "zhang san", 24)
	println(age) // 24
	
	val Array(first, second, _*) = Array(1, 7, 2, 9)
	println(first) // 1
	
	// 循环匹配
	val map1: Map[String, Int] = Map("A" -> 1, "B" -> 2, "C" -> 3)
	for ((k, v) <- map1) { // 直接将map2中的kv匹配到
	  println(k + " -> " + v)
	}
	for ((k, 2) <- map1) { // 匹配出v为0的k
	  println(k + " -> " + 2)
	}
	for ((k, v) <- map1 if v == 2) { // 匹配出v为2的kv
	  println(k + " -> " + v)
	}
	
	// 函数参数
	val map2: Map[String, (String, Int)] = Map("张三" -> ("男", 24), "李四" -> ("男", 26))
	map2.foreach { case (name, (sex, age)) => println(age) } // 此处需使用花括号
	
	val list1 = List(("a", 1), ("b", 2), ("c", 3))
	val newList1: List[(String, Int)] = list1.map { case (word, count) => (word, count * 2) } // 此处需使用花括号
	println(newList1) // List((a,2), (b,4), (c,6))

偏函数

	// 偏函数
	// 就是对集合中符合条件的数据进行处理的函数
	// 声明偏函数
	val pf: PartialFunction[Any, Any] = {case i: Int => i} // 只匹配Int的元素
	// 应用偏函数
	val list2 = List(1, 2, "3", 4)
	//    println(list2.map(pf)) // map()方法不支持偏函数, 报 MatchError
	println(list2.collect(pf)) // List(1, 2, 4)
	
	//=========== 案例实操 ============
	// 需求 : 将list3中的Int类型的元素加一, 并去掉字符串
	val list3 = List(1, 2, 3, 4, 5, 6, "7")
	println(list3.collect { case i: Int => i + 1 }) // List(2, 3, 4, 5, 6, 7)

异常

	//=========== 异常 - Java =============
	// 异常的处理顺序应上小下大, 否则会报编译时异常
	try {
	    // 可能发生异常的代码
	} catch (ArithmeticException ae) {
	    // 当发生算术异常时的备用方案
	} catch (Exception e) {
	    // 当发生非算术异常的异常时的备用方案
	} finally {
	    // 不管是否发生异常, 一定会被执行的的代码
	    // 常用于资源的释放
	}
	//=========== 异常 - Scala =============
	// 处理顺序无要求,不报错, 执行时顺序执行。一般上小下大
	// Scala中不区分编译时异常和运行时异常, 也无需显示抛出异常, 所以Scala中没有throws关键字
	try {
	  // 可能发生异常的代码
	} catch {
	  case ae: ArithmeticException => {
	    // 发生算术异常时的备用方案
	  }
	  case e: Exception => {
	    // 发生非算术异常的异常时的备用方案
	  }
	} finally {
	  // 不管是否发生异常, 一定会被执行的代码
	  // 常用于资源的释放
	}

隐式转换

	    //=============== 隐式转换 ===============
	    // 应用场景 :
	    // 1. 当程序因意外情况, 导致正确的逻辑出现错误。
	    // 2. 扩展功能。
	    // 为了遵循OCP原则, 可以使用隐式转换的方式对代码进行"修改/升级"
	
	    //=============== 隐式函数 ===============
	    // 声明隐式函数
	    implicit def doubleToInt1(d: Double): Int = {
	      d.toInt
	    }
	
	    val d1: Double = 2.0
	    val i1: Int = d1 // Double => Int 正常情况下会报错
	    // 当编译不通过时, 编译器会查找相匹配的自定义隐式函数, 并进行二次编译(隐式调用)
	    println(i1) // 2
	
	    //============ 隐式参数 & 隐式变量 ===============
	    def doubleToInt2(implicit d: Double): Int = {
	      d.toInt
	    }
	
	    implicit val d2: Double = 2.0
	    println(doubleToInt2) // 2
	
	    val emp : Emp = new Emp
	    emp.insertFun()
	    // 使用隐式类的功能
	    emp.updataFun()
	
	    // 隐式机制
	    // 当编译出现错误时, 编译器会从哪些地方查找对应的隐式转换规则
	    // 1. 当前代码作用域
	    // 2. 当前代码上级作用域
	    // 3. 当前类所在的包对象
	    // 4. 当前类(对象)的父类或特质(父特质)
	  }
	
	  //============= 隐式类 ================
	  // 在Scala后提供了隐式类的概念
	  // 要求 : 1. 构造参数有且只能有一个 ; 2. 隐式类不能是顶级的, 必须被定义在类/伴生对象/包对象中
	  class Emp {
	    def insertFun(): Unit = {
	      println("insertFun...")
	    }
	  }
	
	  implicit class User(emp: Emp) {
	    def updataFun(): Unit = {
	      println("updataFun...")
	    }
	  }

泛型

Scala的泛型和Java中的泛型表达的含义都是一样的,对处理的数据类型进行约束,但是Scala提供了更加强大的功能

	class Test[A] {
		private var elements: List[A] = Nil
	}

泛型转换

泛型不可变

	object ScalaGeneric {
	    def main(args: Array[String]): Unit = {
	
	        val test1 : Test[User] = new Test[User] // OK
	        val test2 : Test[User] = new Test[Parent] // Error
	        val test3 : Test[User] = new Test[SubUser]  // Error
	
	    }
	    class Test[T] {
	    }
	    class Parent {
	    }
	    class User extends Parent{
	    }
	    class SubUser extends User {
	    }
	}

泛型协变

	object ScalaGeneric {
	    def main(args: Array[String]): Unit = {
	
	        val test1 : Test[User] = new Test[User] // OK
	        val test2 : Test[User] = new Test[Parent] // Error
	        val test3 : Test[User] = new Test[SubUser]  // OK
	
	    }
	    class Test[+T] {
	    }
	    class Parent {
	    }
	    class User extends Parent{
	    }
	    class SubUser extends User {
	    }
	}

泛型逆变

	object ScalaGeneric {
	    def main(args: Array[String]): Unit = {
	
	        val test1 : Test[User] = new Test[User] // OK
	        val test2 : Test[User] = new Test[Parent] // OK
	        val test3 : Test[User] = new Test[SubUser]  // Error
	
	    }
	    class Test[-T] {
	    }
	    class Parent {
	    }
	    class User extends Parent{
	    }
	    class SubUser extends User {
	    }
	}

泛型边界

Scala的泛型可以根据功能设定类树的边界

	object ScalaGeneric {
	    def main(args: Array[String]): Unit = {
	        val parent : Parent = new Parent()
	        val user : User = new User()
	        val subuser : SubUser = new SubUser()
	        test[User](parent) // Error
	        test[User](user)   // OK
	        test[User](subuser) // OK
	    }
	    def  test[A]( a : A ): Unit = {
	        println(a)
	    }
	    class Parent {
	    }
	    class User extends Parent{
	    }
	    class SubUser extends User {
	    }
	}

泛型上限

	object ScalaGeneric {
	    def main(args: Array[String]): Unit = {
	        val parent : Parent = new Parent()
	        val user : User = new User()
	        val subuser : SubUser = new SubUser()
	        test[Parent](parent) // Error
	        test[User](user)   // OK
	        test[SubUser](subuser) // OK
	    }
	    def  test[A<:User]( a : A ): Unit = {
	        println(a)
	    }
	    class Parent {
	    }
	    class User extends Parent{
	    }
	    class SubUser extends User {
	    }
	}

泛型下限

	object ScalaGeneric {
	    def main(args: Array[String]): Unit = {
	        val parent : Parent = new Parent()
	        val user : User = new User()
	        val subuser : SubUser = new SubUser()
	        test[Parent](parent) // OK
	        test[User](user)   // OK
	        test[SubUser](subuser) // Error
	    }
	    def  test[A>:User]( a : A ): Unit = {
	        println(a)
	    }
	    class Parent {
	    }
	    class User extends Parent{
	    }
	    class SubUser extends User {
	    }
	}

上下文限定

上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过implicitly[Ordering[A]]获取隐式变量,如果此时无法查找到对应类型的隐式变量,会发生出错误。

	object ScalaGeneric {
	    def main(args: Array[String]): Unit = {
	        def f[A : Test](a: A) = println(a)
	        implicit val test : Test[User] = new Test[User]
	        f( new User() )
	    }
	    class Test[T] {
	    }
	    class Parent {
	    }
	    class User extends Parent{
	    }
	    class SubUser extends User {
	    }
	}

正则表达式

简介

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

语法

	object ScalaRegex {
	    def main(args: Array[String]): Unit = {
	        // 构建正则表达式
	        val pattern = "Scala".r
	        val str = "Scala is Scalable Language"
	
	        // 匹配字符串 - 第一个
	        println(pattern findFirstIn str)
	
	        // 匹配字符串 - 所有
	        val iterator: Regex.MatchIterator = pattern findAllIn str
	        while ( iterator.hasNext ) {
	            println(iterator.next())
	        }
	
	        println("***************************")
	        // 匹配规则:大写,小写都可
	        val pattern1 = new Regex("(S|s)cala")
	        val str1 = "Scala is scalable Language"
	        println((pattern1 findAllIn str1).mkString(","))
	    }
	}

案例实操

手机号正则表达式验证方法

	object ScalaRegex {
	    def main(args: Array[String]): Unit = {
	        // 构建正则表达式
	        println(isMobileNumber("18801234567"))
	        println(isMobileNumber("11111111111"))
	    }
	    def isMobileNumber(number: String): Boolean ={
	        val regex = "^((13[0-9])|(14[5,7,9])|(15[^4])|(18[0-9])|(17[0,1,3,5,6,7,8]))[0-9]{8}$".r
	        val length = number.length
	        regex.findFirstMatchIn(number.slice(length-11,length)) != None
	    }
	}

提取邮件地址的域名部分

	object ScalaRegex {
	    def main(args: Array[String]): Unit = {
	        // 构建正则表达式
	        val r = """([_A-Za-z0-9-]+(?:\.[_A-Za-z0-9-\+]+)*)(@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?:\.[A-Za-z]{2,})) ?""".r
	        println(r.replaceAllIn("abc.edf+jianli@gmail.com   hello@gmail.com.cn", (m => "*****" + m.group(2))))
	    }
	}

下划线的用法总结

  1. 下划线可以作为标识符使用
  2. 下划线可以将函数作为整体使用
  3. 下划线在匿名函数中可以代替参数使用
  4. 下划线在导类时可以代替java中的*
  5. 下划线可以将制定类在编译时隐藏
  6. 下划线可以对类的属性进行默认初始化
  7. 如果匿名函数的参数不采纳与逻辑处理, 可以使用下划线省略
  8. 下划线在模式匹配中可以表示所有的值(default)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值