Scala从入门到XX 阿善看到

Scala概述

  • Scala介绍

https://www.scala-lang.org/

1608945168074

Scala是一门综合了面向对象和函数式编程的语言, 运行在JVM之上, 能够和Java语言互操作

1608945355632

  • Scala的特点

    • 语法简洁

    • 开发速度快/运行速度快

    • 兼容Java

    • 很多大数据框架的源码或编程接口都支持Scala

    • 运用广泛

Scala开发环境

运行环境

1.安装JDK

2.下载ScalaSDK:https://www.scala-lang.org/download/2.12.12.html

3.安装ScalaSDK

直接双击scala-2.12.11.msi或解压scala-2.12.11.zip到指定目录,如C:developsoftscala-2.12.11,要求纯英文目录

4.检查或配置环境变量:

1608946409419

 

5.检验是否安装成功

1608946494062

 

 

6.验证Scala是运行在JVM之上的

  • 在txt里面编写HelloWorld.scala

object HelloWorld {
 def main(args: Array[String]): Unit = {
   println("Hello World!")
}
}
  • scalac 进行编译

  • scala运行编译之后的字节码

1608946737549

 

 

开发环境

idea插件在线安装

1608947278070

 

 

离线安装

1.查看版本

1608947301246

 

1608947328302

 

2.下载对应版本的插件

https://plugins.jetbrains.com/plugin/1347-scala/versions

 

3.导入本地插件

1608947468359

 

 

 

4.重启idea

 

5.后续就可以使用idea进行scala项目开发

 

6.注意: 新建项目的时候还是建maven的项目(方便后续引入java相关依赖)

 

7.在项目中关联本地的Scala的SDK + 插件的支持 就可以去新建Scala相关的类了

 

Scala初体验

创建项目

1.创建maven项目

 

2.添加本地的ScalaSDK支持

1608948705226

 

 

3.添加mavne的pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>cn.itcast</groupId>
   <artifactId>scala_study_47</artifactId>
   <version>1.0-SNAPSHOT</version>

   <properties>
       <project.build.source.Encoding>UTF-8</project.build.source.Encoding>
       <maven.compiler.source>1.8</maven.compiler.source>
       <maven.compiler.target>1.8</maven.compiler.target>
   </properties>

   <dependencies>
       <dependency>
           <groupId>org.scala-lang</groupId>
           <artifactId>scala-library</artifactId>
           <version>2.12.11</version>
       </dependency>

       <!-- 演示Scala-actor并发编程时需要的依赖,因为过期了在2.12中被移除了,所以引入2.11的-->
       <!--<dependency>
           <groupId>org.scala-lang</groupId>
           <artifactId>scala-actors</artifactId>
           <version>2.11.8</version>
       </dependency>
       <dependency>
           <groupId>com.typesafe.akka</groupId>
           <artifactId>akka-actor_2.11</artifactId>
           <version>2.3.14</version>
       </dependency>
       <dependency>
           <groupId>com.typesafe.akka</groupId>
           <artifactId>akka-remote_2.11</artifactId>
           <version>2.3.14</version>
       </dependency>-->

   </dependencies>

   <build>
       <plugins>
           <!-- Scala编译插件-->
           <plugin>
               <groupId>org.scala-tools</groupId>
               <artifactId>maven-scala-plugin</artifactId>
               <version>2.15.2</version>
               <executions>
                   <execution>
                       <id>scala-compile-first</id>
                       <goals>
                           <goal>compile</goal>
                       </goals>
                       <configuration>
                           <includes>
                               <include>**/*.scala</include>
                           </includes>
                       </configuration>
                   </execution>
                   <execution>
                       <id>scala-test-compile</id>
                       <goals>
                           <goal>testCompile</goal>
                       </goals>
                   </execution>
               </executions>
           </plugin>
           <!-- maven编译插件-->
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>3.7.0</version>
               <configuration>
                   <source>1.8</source>
                   <target>1.8</target>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

 

4.准备编码目录

1608948874798

 

 

1608948902866

 

 

编写代码

新建Scala的object

1608948936893

 

 

1608948971370

 

 

package cn.itcast.hello

/**
* Author itcast
* Date 2020/12/26 10:16
* Desc 演示Scala入门案例-scala操作java
*/
object HelloWorld {
 def main(args: Array[String]): Unit = {
   println("hello scala")//scala
   System.out.println("hello java")
}
}

 

package cn.itcast;

import scala.Console;

/**
* Author itcast
* Date 2020/12/26 10:18
* Desc 演示java操作scala
*/
public class HelloWorld {
   public static void main(String[] args) {
       System.out.println("hello java");
       Console.println("hello scala");
  }
}

 

补充:设置idea类型提示

 

 

1608949327627

 

 

Scala基础语法

变量

1608949530059

 

package cn.itcast.variable

/**
* Author itcast
* Date 2020/12/26 10:26
* Desc 演示Scala中的变量定义和语法细节
*/
object VariableDemo01 {
 def main(args: Array[String]): Unit = {
   //注意1:语句末位可以不加;
   val name:String = "itcast"

   //注意2:类型可以省略,scala编译器可以自动推断
   //val age:Int = 18
   var age = 18

   //注意3:val修饰的变量不能被重新赋值,类似与final
   //name = "itheima"

   //注意4:var修饰的变量可以被重新赋值
   age = 20

   //注意5:开发中优先使用val,因为scala常用在并发环境,使用val更加安全


   //注意6:可以使用type给类型起别名--了解
   type myString = String
   val address:myString = "北京"

   //注意7:可以使用lazy修饰变量,延迟变量的初始化,等到变量真正使用到的时候再执行初始化--了解
   lazy val info = queryInfo()
   println("queryInfo被调用了,但没有真正执行")
   println(info)

}
 def queryInfo():String={
   println("queryInfo真正执行了")
   "user info"
}
}
​阿善看到

 

数据类型

1608950675410

 

package cn.itcast.syntax

/**
* Author itcast
* Date 2020/12/26 10:45
* Desc 演示Scala数据类型-值类型
*/
object Demo02_DataType {
 def main(args: Array[String]): Unit = {
   //注意:Scala中的值类型都是大写开头
   val v1: Byte = 1
   val v2: Short = 1
   val v3: Int = 1
   val v4: Long = 1L
   val v5: Double = 10.0
   val v6: Float = 10.0f
   val v7: Boolean = true
   val v8: Char = 'c'

   //String定义方式1: " "
   val name:String = "jack"


   //String定义方式2: """ """
   val sql1:String =
     """
       |select *
       |from 表名
       |where 条件
       |""".stripMargin

   //String定义方式3:字符串拼接可以使用插值表达式
   val tableName = "student"
   val id = 1
   //以前的写法
   val sql2:String = "select * from "+tableName+" where id = "+ id
   //现在的写法使用插值表达式
   val sql3:String = s"select * from ${tableName} where id = ${id}"

   //String定义方式4:插值表达式+""" """
   val sql4:String =
     s"""
       |select *
       |from ${tableName}
       |where id = ${id}
       |""".stripMargin

   println(sql1)
   println(sql2)
   println(sql3)
   println(sql4)
}
}

 

 

操作符

1608952789387

 

package cn.itcast.syntax

/**
* Author itcast
* Date 2020/12/26 11:14
* Desc 演示Scala操作符
*/
object Demo03_Operator {
 def main(args: Array[String]): Unit = {
   var a: Int = 2
   val b: Int = 1

   //注意:Scala中的操作符和Java中类似
   println(a + b)//3
   println(a - b)//1
   println(a * b)//2
   println(a / b)//2
   println(a % b)//0

   //注意:Scala中的操作符本质上是方法
   println(a.+(b))//3

   //注意:Scala中没有++ --因为Scala认为++ --放前面放后面容易混淆,所以提供了+= -=
   //println(a++)
   a += 1
   println(a)//3
}
}

 

块表达式

1608952886658

 

package cn.itcast.syntax

/**
* Author itcast
* Date 2020/12/26 11:14
* Desc 演示Scala块表达式
*/
object Demo04_BlockExpression {
 def main(args: Array[String]): Unit = {
   val a: Int = 2
   val b: Int = 1

   val result1:Int = a + b
   //块表达式的最后一行就是整个块表达式的值
   val result2:Int = {
     a + b
  }

   val result3:Int = {
     a + b
     0//块表达式的最后一行就是整个块表达式的值
  }

   println(result1)//3
   println(result2)//3
   println(result3)//0
}
}

 

判断

1608953194677

 

package cn.itcast.syntax

/**
 * Author itcast
 * Date 2020/12/26 11:14
 * Desc 演示Scala判断
 */
object Demo05_Predict {
  def main(args: Array[String]): Unit = {
    val age:Int = 17

    if (age >= 18){
      println("已成年")
    }else{
      println("未成年") //未成年
    }
    //注意:if-else有返回值
    val result1:String = if (age >= 18){
      "已成年"
    }else{
      "未成年"
    }

    //注意:返回值类型不一样时可以用Any或省略
    val result2:Any = if (age >= 18){
      "已成年"
    }else{
      0
    }

    //注意:()表示返回Unit空类似void
    val result3:Unit = if (age >= 18){
      println("已成年")
    }else{
      () //表示返回Unit
    }

    println(result1)//未成年
    println(result2)//0
    println(result3)//()
  }
}

 

 

循环

1608954770728

 

1608954779138

 

1608954787323

 

package cn.itcast.syntax

/**
 * Author itcast
 * Date 2020/12/26 11:14
 * Desc 演示Scala循环
 */
object Demo06_Loop {
  def main(args: Array[String]): Unit = {
    val list1 = 1 to 10
    val list2 = 1 until 10
    val list3 = 1 to 10 by 2
    println(list1.toBuffer) //ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    println(list2.toBuffer) //ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9)
    println(list3.toBuffer) //ArrayBuffer(1, 3, 5, 7, 9)

    println("-----普通遍历------")
    //普通遍历
    for (i <- list1) {
      println(i)
    }

    println("-----遍历list1中的偶数------")
    //遍历list1中的偶数
    for (i <- list1) {
      if (i % 2 == 0) {
        println(i)
      }
    }

    println("-----遍历list1中的偶数-使用守卫/卫语句------")
    //遍历list1中的偶数-使用守卫/卫语句
    for (i <- list1 if i % 2 == 0) {
      println(i)
    }

    println("-----for推导式------")
    val result = for (i <- list1) yield i * 10
    println(result.toBuffer)//10 20 30

    println("-----嵌套for循环------")
    for(i <- 1 to 9){
      for(j<- 1 to 9){
        if(i >= j) {
          print(s"${j} * ${i} = ${j*i} ")
        }
      }
      println()
    }
    println("-----嵌套for循环-简写------")
    for(i <- 1 to 9;j<- 1 to 9){
      if(i >= j) {
        print(s"${j} * ${i} = ${j*i} ")
      }
      if(j == 9){
        println()
      }
    }

    println("----循环没有返回值-----")
    val result2 = for (i <- list1) {
      println(i)
      0
    }
    println(result2)//()

    //下面的了解
    println("=========break()实现break==========")
    //注意:Scala中没有break和continue关键字,因为scala认为break和continue代码容易出错,不推荐使用,但是提供了其他的方式
    //打印1-10的数字,如果数字到达5,退出for表达式
    import scala.util.control.Breaks._
    breakable{
      for(i <- 1 to 10) {
        if(i >= 5)
          break()
        else
          println(i)
      }
    }

    println("=========break()实现continue==========")
    //-实现continue
    //打印1-10的数字,如果数字能整除3,跳过不打印,继续下一个 
    for(i <- 1 to 10 ) {
      breakable{
        if(i % 3 == 0)
          break()
        else println(i)
      }
    }
  }

}

 

方法和函数

方法

1608965232841

 

package cn.itcast.method_function

/**
 * Author itcast
 * Date 2020/12/26 14:40
 * Desc 演示Scala方法定义格式
 */
object Demo01_MethodDefinition {
  def main(args: Array[String]): Unit = {
    println(add(1, 2))//3

    println(sum(1, 10))//55

    println(sum(1, 100))//5050
  }
  //def 方法名称(参数名称1:数据类型1,参数名称2:数据类型2.......):返回值={方法体}
  //需求1:定义一个方法,接收2个int,返回他们的和int
  def add(a:Int,b:Int):Int={
    a + b
  }

  //需求2:定义一个方法,接收2个int,a和b,返回从a加到b的和int
  def sum(a:Int,b:Int):Int={
    var sum:Int  = 0
    for(i<- a to b){
      sum += i
    }
    sum
  }

}

 

1608965246056

 

package cn.itcast.method_function

/**
 * Author itcast
 * Desc 演示Scala方法细节
 */
object Demo02_Method_Detaile {
  def main(args: Array[String]): Unit = {
    println(m2(10)) //55
    println(m2(100)) //5050
    println(m3()) //()
    println(m5())//100
    println(m5(200))//200
    m6(1,2)//a:1,b:2
    m6(b = 1,a = 2) //a:2,b:1

    m7()
    m7 //m7方法不需要参数,调用的时候可以省略()
    
    //m7_2()//m7_2方法不需要参数,且定义的时候省略了().所以调用的时候也必须省略()
    m7_2

    m8(1,2,3,4,5)
  }

  //method,是用来封装某些功能的,它可以接收参数,并在方法体中进行处理,最后可以返回处理结果
  //方法定义格式:
  //def 方法名(参数名1:类型,参数名2:类型):返回值类型 = {方法体}

  //1.方法的返回值类型和return可以不写,编译器可以自动推断出来
  def m1() = {
    ""
  }

  //2.对于递归方法,必须指定返回类型,递归:就是方法调用方法!
  //如递归求1到n的累加和,求[1~10]的累加和,n=10
  def m2(n: Int): Int = {
    if (n == 1) {
      1
    } else {
      n + m2(n - 1)
    }
  }

  //3.如果方法没有返回值,返回Unit类型(类似于void,也可以不写)
  def m3(): Unit = {
    println("该方法无返回值")
  }

  //4.返回值类型有多种情况则返回Any(也可以不写)
  def m4(n: Int): Any = {
    if (n >= 18) {
      "已成年"
    } else {
      0
    }
  }

  //5.带有默认值参数的方法,调用时,可以给定新值,也可以使用默认值
  def m5(num:Int =100):Int={
    num
  }

  //6.可以通过参数名来指定传递给哪一个参数,这样传递参数时就可以不按照顺序传递
  def m6(a:Int,b:Int):Unit={
    println("a:"+a)
    println("b:"+b)
  }

  //7.方法没有参数,调用时可以省略(),如果定义时()省略,调用时则必须省略
  def m7():Unit={
    println("m7被调用了")
  }

  def m7_2:Unit={
    println("m7_2被调用了")
  }

  //8.可变参数使用 变量名:类型* (类似Java的...)
  //可变参数可以理解为一个集合/数组
  def m8(nums:Int*): Unit ={
    for(n <- nums){
      println(n)
    }
  }
}

 

函数

回顾一下Java中的函数

Java8之后开始支持函数式编程--Lambda表达式

1608966216068

 

package cn.itcast;

/**
 * Author itcast
 * Date 2020/12/26 14:57
 * Desc 回顾Java8中的函数-Lambda表达式
 */
public class FunctionDemo {
    public static void main(String[] args) {
        new Thread(
                new Runnable(){
                    @Override
                    public void run() {
                        System.out.println("匿名内部类对象实现run方法");
                    }
                }
        ).start();

        //lambda表达式语法:
        //(参数列表)->{函数体}
        new Thread(
                ()->System.out.println("lambda表达式函数式编程实现run方法")
        ).start();
    }
}

 

Scala中的函数

1608966253497

 

package cn.itcast.method_function

/**
 * Author itcast
 * Desc 演示Scala函数定义格式
 */
object Demo03_FunctionDefinition {
  def main(args: Array[String]): Unit = {
    //需求:定义一个函数接收2个int参数并返回他们的和
    //●完整语法:
    //val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>{函数体}
    val fun1:(Int,Int)=>Int = (a:Int,b:Int)=>{a + b}

    //●简写语法:
    //val函数名称 = (参数名称:参数类型) => {函数体}
    val fun2 = (a:Int,b:Int)=>{a + b}

    //●进一步简写:
    //val函数名称 = (参数名称:参数类型) => 函数体
    val fun3 = (a:Int,b:Int)=> a + b

    println(fun1(1, 2))//3
    println(fun2(1, 2))//3
    println(fun3(1, 2))//3

  }
}

 

 

证明:Scala中的函数本质也是对象!

1608966899612

 

package cn.itcast.method_function

/**
 * Author itcast
 * Desc 演示证明函数是对象
 */
object Demo04_FunctionIsObject {
  def main(args: Array[String]): Unit = {
    //●完整语法:
    //val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>{函数体}
    //●简写语法:
    //val函数名称 = (参数名称:参数类型) => {函数体}
    //●进一步简写:
    //val函数名称 = (参数名称:参数类型) => 函数体

    //定义一个函数
    val fun = (a:Int,b:Int) => {a+b}
    //证明1.函数是对象可以赋值给变量
    val fun2 = fun

    //证明2.函数是对象可以打点调用方法
    println(fun.toString())

    //证明3.函数是对象可以传递给方法的参数来接收
    println(m1(1, 2, fun))//3

    //扩展:方法和函数的功能一样,所以方法也可以转为函数
    val function: (Int, Int) => Int = add _ //方法 _表示将方法显示的转为函数
    println(m1(1, 2, function))//3
    println(m1(1, 2, add))//3 //方法自动/隐式的转为了函数

    //扩展:函数的优势:简洁、功能灵活
    //如:完成4则运算,方法要定义4个,而使用函数式编程,函数可以直接传,都无需单独定义,功能灵活
    println(m1(1, 2, (a,b) => a+b))//3
    println(m1(1, 2, (a,b) => a-b))//-1
    println(m1(1, 2, (a,b) => a*b))//2
    println(m1(1, 2, (a,b) => a/b))//0

  }

  //定义一个方法:接收2个Int值,和1个函数,在方法体里面将2个Int值传给函数
  //def 方法名称(参数名称1:数据类型1,参数名称2:数据类型2.......):返回值={方法体}
  def m1(a:Int,b:Int,funx:(Int,Int)=>Int):Int={
    funx(a,b)
  }

  def add(a:Int,b:Int):Int={
    a + b
  }

}

 

 

 

Scala常用集合

 

集合分类

1608969030745

 

1608969111240

 

1608969133250

 

 

 

继承体系

不可变集合

1608969304387

 

可变集合

1608969395115

 

 

 

Array

1608969777545

 

package cn.itcast.collection

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

/**
 * Author itcast
 * Date 2020/12/26 16:03
 * Desc 演示Scala中的Array的基本使用--更常用的高级函数式API后面单独讲
 */
object Demo01_Seq_Array {
  def main(args: Array[String]): Unit = {
    //●不可变/定长数组:
    //val/var 变量名= new Array[T](数组长度)
    //val/var 变量名 = Array(元素1, 元素2, 元素3...)
    val arr1: Array[Int] = new Array[Int](5)
    val arr2: Array[Int] = Array(1,2,3,4,5)
    println(arr1.length)//5
    println(arr1)//地址值
    println(arr1.toBuffer)//ArrayBuffer(0, 0, 0, 0, 0)
    println(arr1.mkString("|"))//0|0|0|0|0
    println(arr2.size)
    println(arr2)//地址值
    println(arr2.toBuffer)//ArrayBuffer(1, 2, 3, 4, 5)
    println(arr2.mkString("|"))//1|2|3|4|5

    arr1(0) = 100 //设置值
    println(arr1(0))//获取值 100
    println(arr1.toBuffer)//ArrayBuffer(100, 0, 0, 0, 0)

    //arr1 += 1  //不可变集合不能改变长度
    //arr1-=1 //不可变集合不能改变长度

    //●可变/变长数组:
    ////需要导入import scala.collection.mutable.ArrayBuffer包
    //val/var 变量名 = ArrayBuffer[T]()
    //val/var 变量名 = ArrayBuffer(元素1, 元素2, 元素3...)

    val arr3: ArrayBuffer[Int] = ArrayBuffer[Int]()
    val arr4: ArrayBuffer[Int] = ArrayBuffer(1,2)
    arr3 += 1
    arr3 += 2
    arr3 += 3
    arr4 += 3
    println(arr3)//ArrayBuffer(1, 2, 3)
    println(arr4)//ArrayBuffer(1, 2, 3)

    //不可变转可变:返回一个可变的
    val result: mutable.Buffer[Int] = arr1.toBuffer
    //arr1+=1 //原来的还是不可变

    //可变转不可变::返回一个不可变的
    val result2: Array[Int] = arr3.toArray
    arr3+=1 //原来的还是可变

  }
}

 

 

List

1608970691358

 

 

1608970695593

 

package cn.itcast.collection

import scala.collection.mutable
import scala.collection.mutable.{ArrayBuffer, ListBuffer}

/**
 * Author itcast
 * Date 2020/12/26 16:03
 * Desc 演示Scala中的List的基本使用--更常用的高级函数式API后面单独讲
 */
object Demo02_Seq_List {
  def main(args: Array[String]): Unit = {
    //●不可变列表(默认)
    //import scala.collection.immutable._
    //创建方式1.使用List(元素1, 元素2, 元素3, ...)来创建一个不可变列表
    //val/var 变量名 = List(元素1, 元素2, 元素3...)
    //创建方式2.使用::方法创建一个不可变列表
    //val/var 变量名 = 元素1 :: 元素2 :: Nil
    //注意:使用::拼接方式来创建列表,必须在最后添加一个Nil
    val list1: List[Int] = List(1, 2, 3)
    val list2: List[Int] = 1::2::3::Nil  //Nil表示一个空列表

    println(list1)//List(1, 2, 3)
    println(list2)//List(1, 2, 3)

    //list1 += 1 //长度不可变
    //list1(0) = 100 //内容不可变

    println(list1.head)//1
    println(list1.tail)//List(2, 3)

    //●可变列表
    //import scala.collection.mutable._
    //创建方式1.使用ListBuffer[元素类型]()创建空的可变列表
    //val/var 变量名 = ListBuffer[Int]()
    //创建方式2.使用ListBuffer(元素1, 元素2, 元素3...)创建可变列表
    //val/var 变量名 = ListBuffer(元素1,元素2,元素3...)

    val list3: ListBuffer[Int] = ListBuffer[Int]()
    val list4: ListBuffer[Int] = ListBuffer(1,2)
    list3.append(1,2,3)
    list4 += 3
    println(list3)//ListBuffer(1, 2, 3)
    println(list4)//ListBuffer(1, 2, 3)
    val result = list3 ++= list4
    println(result)//ListBuffer(1, 2, 3, 1, 2, 3)

    //可变转不可变
    val result2: List[Int] = list3.toList
    val result3: Array[Int] = list3.toArray
    //不可变转可变
    val result4: mutable.Buffer[Int] = list1.toBuffer
    println(result2)//List(1, 2, 3, 1, 2, 3)
    println(result3)//地址值
    println(result4)//ArrayBuffer(1, 2, 3)

  }
}

 

Set

1608971818269

 

package cn.itcast.collection

import scala.collection.mutable


/**
 * Author itcast
 * Date 2020/12/26 16:03
 * Desc 演示Scala中的Set的基本使用--更常用的高级函数式API后面单独讲
 */
object Demo03_Set {
  def main(args: Array[String]): Unit = {
    //●不可变Set(默认)
    //import scala.collection.immutable._
    ////val/var 变量名 = Set[类型]()
    //val/var 变量名 = Set(元素1, 元素2, 元素3...)
    val set1: Set[Int] = Set(1,2,3,4,5,6,7,8,9,9)
    //注意Set无序且元素不可重复
    println(set1)//Set(5, 1, 6, 9, 2, 7, 3, 8, 4)
    //set1 += 1

    //●可变Set
    import scala.collection.mutable._
    //格式相同,导包不同
    //val/var 变量名 = Set[类型]()
    //val/var 变量名 = Set(元素1, 元素2, 元素3...)
    val set2: Set[Int] = Set(1,2,3,4,5,6,7,8,9,9)
    val set3: Set[Int] = Set(0,1,2,3,4,5)
    set2 += 10
    println(set2)//Set(9, 1, 5, 2, 6, 3, 10, 7, 4, 8)

    //交集
    val result1: mutable.Set[Int] = set2 & set3
    println(result1)//12345 //Set(1, 5, 2, 3, 4)

    //并集
    val result2: mutable.Set[Int] = set2 ++ set3
    println(result2)//0~10 //Set(0, 9, 1, 5, 2, 6, 3, 10, 7, 4, 8)

    //差集
    val result3: mutable.Set[Int] = set2 &~ set3
    println(result3)//6~10 //Set(9, 6, 10, 7, 8)

  }
}

 

 

Tuple

1608973534422

 

package cn.itcast.collection


/**
 * Author itcast
 * Date 2020/12/26 16:03
 * Desc 演示Scala中的Tuple的基本使用--更常用的高级函数式API后面单独讲
 */
object Demo04_Tuple {
  def main(args: Array[String]): Unit = {
    //●创建元组
    //格式1:使用箭头来定义元组(元组只有两个元素)
    //val/var 元组 = 元素1->元素2 //对偶/二元组是最简单的元组(k,v)
    //格式2:使用括号来定义元组
    //val/var 元组 = (元素1, 元素2, 元素3....)

    //注意:Tuple元组可以看作是一个特殊的容器,里面可以存放相同或不同类型的元素
    val t1: (String, Int) = "jack"->18
    val t2: (String, Int) = ("tom",20)

    //注意:如果是定义二元组使用方式1/2都可以
    //但是如果是3元组,则需要注意:只能使用方式2:
    val t3: ((String, Int), String) = "tony"->19->"北京"
    val t4: (String, Int, String) = ("lily",21,"上海")

    println(t1)//(jack,18)
    println(t2)//(tom,20)
    println(t3)//((tony,19),北京) //注意:这个嵌套的二元组
    println(t4)//(lily,21,上海)

    //获取元素使用元组._数字
    val value1: (String, Int) = t3._1 //(tony,19)
    val value2: String = t3._2 //北京
    val value3: String = t3._1._1 //tony
    val value4: (String, (String, Int)) = t3.swap //(北京,(tony,19))
    println(value1)//(tony,19)
    println(value2)//北京
    println(value3)//tony
    println(value4)//(北京,(tony,19))

    //t4.swap //只有二元组有swap
  }
}

 

 

 

Map

1608974243418

 

package cn.itcast.collection


/**
 * Author itcast
 * Date 2020/12/26 16:03
 * Desc 演示Scala中的Map的基本使用--更常用的高级函数式API后面单独讲
 */
object Demo05_Map {
  def main(args: Array[String]): Unit = {
    //●不可变Map
    //import scala.collection.immutable.Map
    //格式1:使用箭头val/var map = Map(键->值, 键->值, 键->值...)	// 推荐,可读性更好
    //格式2:利用元组val/var map = Map((键, 值), (键, 值), (键, 值), (键, 值)...)
    val map1: Map[String, Any] = Map("name"->"jackma","age"->18,"address"->"杭州")
    val map2: Map[String, Any] = Map(("name","jackma"),("age",18),("address","杭州"))
    //map1 += ("k","v")


    //●可变Map
    import scala.collection.mutable.Map
    //格式相同,导包不同
    val map3: Map[String, Any] = Map("name"->"jackma","age"->18,"address"->"杭州")
    val map4: Map[String, Any] = Map(("name","jackma"),("age",18),("address","杭州"))

    map3.put("gender","男")
    map3 += ("company1"->"alibaba")
    map3 += (("company2","蚂蚁金服"))
    println(map3) //Map(address -> 杭州, company2 -> 蚂蚁金服, age -> 18, name -> jackma, gender -> 男, company1 -> alibaba)
    map3.remove("company1")
    map3-=("company2")
    println(map3)//Map(address -> 杭州, age -> 18, name -> jackma, gender -> 男)

    println(map3("name"))//jackma
    println(map3.get("name").get)//jackma

    //println(map3("sex")) //报错.因为key不存在
    //println(map3.get("sex").get)//报错.因为key不存在

    if(map3.contains("sex")){
      println(map3("sex"))
    }
    println(map3.getOrElse("sex", "纯爷们"))//纯爷们
    println(map3.getOrElse("name", "xx"))//jackma

  }
}

 

 

Queue和Stack

 

1608975480936

 

1608975507253  

 

package cn.itcast.collection

import scala.collection.mutable

/**
 * Author itcast
 * Date 2020/12/26 16:03
 * Desc 演示Scala中的QueueAndStack的基本使用--更常用的高级函数式API后面单独讲
 */
object Demo06_QueueAndStack{
  def main(args: Array[String]): Unit = {
    import scala.collection.mutable._
    val q = Queue[Int]()
    val s = Stack[Int]()

    //队列:先进先出
    q.enqueue(1)
    q.enqueue(2)
    q.enqueue(3)
    println(q.dequeue())//1
    println(q.dequeue())//2
    println(q.dequeue())//3

    //栈:先进后出
    s.push(1)
    s.push(2)
    s.push(3)
    println(s.pop())//3
    println(s.pop())//2
    println(s.pop())//1
  }
}

 

 

集合遍历

package cn.itcast.collection

/**
 * Author itcast
 * Date 2020/12/26 16:03
 * Desc 演示Scala中的集合遍历--更常用的高级函数式API后面单独讲
 */
object Demo07_Iterator{
  def main(args: Array[String]): Unit = {
    val seq: Seq[Int] = List(1, 2, 3, 4, 5)
    val set: Set[Int] = Set(1, 2, 3, 4, 5)
    val map: Map[String, Any] = Map("name" -> "jack", "age" -> "18", "score" -> 99)
    val tuple: (String, String, String) = ("上海", "北京", "广州")

    println("=====seq=====")
    for(i <- seq){
      println(i)
    }
    println("=====set=====")
    for(i <- set){
      println(i)
    }
    println("=====map=====")
    for(i <- map){
      println(i)
    }
    for(i <- map){
      println(s"k:${i._1},v:${i._2}")
    }
    for((k,v) <- map){
      println(s"k:${k},v:${v}")
    }
    for(k <- map.keys){
      println(s"k:${k},v:${map(k)}")
    }
    for(v <- map.values){
      println(s"v:${v}")
    }
    println("=====tuple=====")
    for(i <- tuple.productIterator){
      println(i)
    }
    println("=====foreach=====")
    //后续会学习函数式编程,直接一句话搞定:
    seq.foreach((i:Int)=>{println(i)})
    seq.foreach(i=>println(i))
    seq.foreach(println(_))
    seq.foreach(println)
  }
}

 

作业

1.安装环境

2.练习基础语法和常用集合

3.重点理解什么是函数!---对象

4.预习集合的函数式API

 

 

思考题

面试题: 使用多种方式交换2个变量的值

package cn.itcast.temp

import scala.collection.mutable.ListBuffer

/**
 * Author itcast
 * Date 2020/12/26 18:01
 * Desc 面试题--使用多种方式交换2个变量的值
 */
object Interview_1 {
  def main(args: Array[String]): Unit = {
    var a = 1
    var b = 2
    println(a,b) // 1,2

    //方式1:使用临时变量
    val temp = a //1
    a = b //2
    b = temp //1
    println(a,b) //2,1

    //方式2:使用算术运算符
    a = a + b //3
    b = a - b //2
    a = a - b //1
    println(a,b)//1,2

    //方式3:使用位运算符
    a = a ^ b // a ^ b
    b = a ^ b // a ^ b ^ b == a ^ 0 == a
    a = a ^ b // a ^ b ^ a == b ^ 0 == b
    println(a,b)//2,1

    //解释:^异或位运算符,特点:相同为0,不同为1
    //a ^ b 记为  a ^ b
    //a ^ b ^ b == a ^ 0
    //一个数异或0 == 这个数本身
    //a ^ 0 == a
    //a ^ b ^ a = b ^ 0 == b

    //^的其他用法
    val key = 66666666 //密钥
    val password = 88888888//原密码
    val password1 = password ^ key //加密后的密码
    val password2 = password1 ^ key //解密后的密码
    println(password)//88888888
    println(password1)//112531090
    println(password2)//88888888

    //^的其他用法,有一个海量连续数据集合A,如1~N,其中丢了一个数,现在让快速找出丢的数
    //用异或将A集合和B集合(0~N没有丢失的)的元素一一遍历异或
    val A = ListBuffer(1,2,3,4,5,6,7,8,9)
    val B = ListBuffer(1,2,3,4,5,6,7,8,9,10)
    A ++=B //[1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,10]
    //现在A里面就有了A和B所有的元素
    var result = 0
    for(i <- A){
      result ^= i
    }
    println("丢失的数为:"+result)
  }

}

 

 

 

 

Scala从入门到XX

一、课程说明和学习目标

(一)课程说明

●高能预警

 

 

 

统计世界top100大学计算机系年级前三名,从初中开始编程,学过20多种语言,最后认为Scala最难。

好了,我们开始享受这个过程吧!

 

●课程特点:

  1. 快:课程进度快,知识遗忘快
  2. 多:语法细节多,演示代码多
  3. 难:入门门槛高,熟练精通难

 

●课程要求:

  1. 知识点:反复复习!反复复习! ---> 书读百遍,其义自现
  2. 代码:代码一定要自己动手至少操作一遍,不熟悉的代码要多遍 ---> 键盘敲烂,月薪过万
  3. bug:尝试着自己多动手去解决 ---> 高级程序员和初级程序员的一个显著差异就是调bug的能力!
  4. 学习能力:培养自己的学习能力 ---> 新技术层出不穷,唯有不断的学习成长才能保持竞争力!

 

(二)课程目标

  1. 了解Scala编程语言的特点
  2. 掌握Scala基本语法
  3. 掌握Scala集合操作
  4. 掌握Scala面向对象编程
  5. 掌握Scala函数式编程
  6. 理解高阶函数
  7. 理解隐式转换
  8. 了解Scala Actor并发编程
  9. 了解简易版Akka实现的Spark通信框架
  10. 掌握使用scala编写Spark程序
  11. 为阅读Spark内核源码做准备

 

二、Scala概述

(一)什么是Scala

●前世今生

2003年Scala诞生于瑞士的洛桑联邦理工学院(EPFL),起初一直默默无闻,直到2013年的时候火了,原来是因为大数据计算框架Spark是基于Scala编写的!

(Kafka的最初几个版本也是使用Scala编写,后续的新版本使用的Java,据说是因为之前那一批的Scala程序员退休了,新来的程序员觉得Scala太难了!)

 

●Scala官网:

http://www.scala-lang.org/

 

 

●Scala简介

Scala(Scalable Language可扩展语言)是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。

Scala 是一门静态类型语言,以Java 虚拟机为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起,Scala的主要优势是它的表达性

你可以使用Scala 编写出更加精简的程序,也能用于构建大型复杂系统,并且他可以访问任何Java 类库并且与Java 框架进行互操作。

Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。

 

 

 

 

●扩展阅读:Scala的特性

1. 运行在JVM 和JavaScript 之上的语言

Scala 不仅利用了JVM 的高性能以及最优化性,Java 丰富的工具及类库生态系统也为其所用。不过Scala 并不是只能运行在JVM 之上! Scala.js(http://www.scala-js.org)正在尝试将其迁移到JavaScript 世界。

 

2. 静态类型

在Scala 语言中,静态类型(static typing)是构建健壮应用系统的一个工具。Scala 修正了Java 类型系统中的一些缺陷,此外通过类型推演(type inference)也免除了大量的冗余代码。

 

3. 混合式编程范式——面向对象编程

Scala 完全支持面向对象编程(OOP)。Scala 引入特征(trait)改进了Java 的对象模型。trait 能通过使用混合结构(mixin composition)简洁地实现新的类型。在Scala 中,一切都是对象,即使是数值类型。对象的数据类型以及行为由类和特质描述。

类抽象机制的扩展有两种途径:一种途径是子类继承,另一种途径是灵活的混入机制。这两种途径能避免多重继承的种种问题。

 

4. 混合式编程范式——函数式编程

Scala 完全支持函数式编程(FP),函数式编程已经被视为解决并发、大数据以及代码正确性问题的最佳工具。使用不可变值、被视为一等公民的函数、无副作用的函数、高阶函数以及函数集合,有助于编写出简洁、强大而又正确的代码。其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型。

 

5. 复杂的类型系统

Scala 对Java 类型系统进行了扩展,提供了更灵活的泛型以及一些有助于提高代码正确性的改进。通过使用类型推演,Scala 编写的代码能够和动态类型语言编写的代码一样精简。类型系统通过编译时检查,保证代码的安全性和一致性。类型系统具体支持以下特性:

泛型类

协变和逆变

标注

类型参数的上下限约束

把类别和抽象类型作为对象成员

复合类型

引用自己时显式指定类型

视图

多态方法

 

6. 简洁、优雅、灵活的语法

使用Scala 之后,Java 中冗长的表达式不见了,取而代之的是简洁的Scala 方言。Scala 提供了一些工具,这些工具可用于构建领域特定语言(DSL),以及对用户友好的API 接口。

 

7. 可扩展的架构

使用Scala,你能编写出简短的解释性脚本,并将其粘合成大型的分布式应用。

以下四种语言机制有助于提升系统的扩展性:

1) 使用trait 实现的混合结构;

2) 抽象类型成员和泛型;

3) 嵌套类;

4) 显式自类型(self type)。

Scala的设计秉承一项事实,即在实践中,某个领域特定的应用程序开发往往需要特定于该领域的语言扩展。Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构:

任何方法可用作前缀或后缀操作符

可以根据预期类型自动构造闭包。

 

8. 并发性

Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现。

 

 

(二)为什么要学Scala

●为什么要学Scala

1、优雅:这是框架设计师第一个要考虑的问题,框架的用户是应用开发程序员,API是否优雅直接影响用户体验。

2、速度快:Scala语言表达能力强,一行代码抵得上Java多行,开发速度快;Scala是静态编译的,所以和JRuby,Groovy比起来速度会快很多。

3、兼容Java:可以访问庞大的Java类库,例如:操作mysql、redis、mq等等 

4、可以做Web开发:如果要使用Scala做Web开发可以学习Lift 框架、Play 框架这些框架,当然我们做大数据开发,后面需要学习的是Spark框架,它的底层是使用Scala编写的,有Scala和Java的API(Flink是Java语言开发的,也支持Java和Scala的API)

5、使用广泛

2009年4月,Twitter宣布他们已经把大部分后端程序从Ruby迁移到Scala,其余部分也打算要迁移。

此外,Wattzon已经公开宣称,其整个平台都已经是基于Scala基础设施编写的。

瑞银集团把Scala用于一般产品中。

Coursera把Scala作为服务器语言使用。

......

国内越来越多的大数据开发者选用Scala作为开发语言

 

 

●Java代码与Scala代码对比

下面通过两个案例,分别使用java和scala实现的代码量

定义三个实体类(用户、订单、商品)

Java代码

/**
 * 用户实体类
 */
public class User {
    private String name;
    private List<Order> orders;

    public String getName() {
      return name;
  }

    public void setName(String name) {
      this.name = name;
  }

    public List<Order> getOrders() {
      return orders;
  }

    public void setOrders(List<Order> orders) {
      this.orders = orders;
  }
}

/**
 * 订单实体类
 */
public class Order {
    private int id;
    private List<Product> products;

    public int getId() {
      return id;
  }

    public void setId(int id) {
      this.id = id;
  }

    public List<Product> getProducts() {
      return products;
  }

    public void setProducts(List<Product> products) {
      this.products = products;
  }
}

/**
 * 商品实体类
 */
public class Product {
    private int id;
    private String category;

    public int getId() {
      return id;
   }

    public void setId(int id) {
      this.id = id;
   }

    public String getCategory() {
      return category;
   }

    public void setCategory(String category) {
     this.category = category;
   }
}

 

scala代码

case class User(var name:String, var orders:List[Order])    // 用户实体类
case class Order(var id:Int, var products:List[Product])    // 订单实体类
case class Product(var id:Int, var category:String)         // 商品实体类

 

(三)扩展阅读:

https://www.zhihu.com/question/19748408

https://www.zhihu.com/question/25679583?sort=created

https://www.zhihu.com/question/26707124

http://www.mamicode.com/info-detail-663480.html

https://www.sohu.com/a/77122724_156793

 

1.Scala优缺点

 

 

 

 

 

 

 

 

 

2.动态语言和静态语言

1.动态类型语言:是指在运行期间才去做数据类型检查的语言。在用动态语言编程时,不用给变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python 和Ruby 就是一种典型的动态类型语言;

2.静态类型语言:与动态类型语言刚好相反,它的数据类型检查发生在在编译阶段,也就是说在写程序时要声明变量的数据类型。C/C++、C#、JAVA 都是静态类型语言的典型代表。注意:Scala是一门静态类型的语言,但是类型声明可以省略(有自动类型推断机制,Java10开始也有了自动类型推断机制)

 

3.强类型语言和弱类型语言

1.强类型语言:是指强制数据类型定义的语言。没有强制类型转化前,不允许两种不同类型的变量相互操作。强类型定义语言是类型安全的语言,如Java、C# 和 Python,比如Java 中int i = “0.0”;是无法通过编译的;

2.弱类型语言:数据类型可以被忽略的语言。与强类型语言相反, 一个变量可以赋不同数据类型的值,允许将一块内存看做多种类型,比如直接将整型变量与字符变量相加。JavaScript、PHP 都是弱类型语言。

注意,强类型语言在编码速度上略逊色于弱类型语言,使用弱类型语言可节省很多代码量,有更高的开发效率。而对于构建大型项目,使用强类型语言可能会比使用弱类型更加规范可靠。

 

4.面向XX 编程:

 

面向过程编程:整个程序按照步骤编写,程序更着重业务步骤的。

面向对象编程:提出对象封装概念,将业务修改成对象之间的关系处理。

面向接口编程:更高层次的抽象,将业务抽象成一些接口规则框架。

面向切面编程:另一个思考维度,着重于业务逻辑的动态组织。

命令式编程:关心解决问题的步骤

函数式编程:关心数据的映射,是数学函数,不是程序中的函数概念

 

三、Scala开发环境搭建

(一)安装测试

●安装JDK

安装JDK

因为Scala是运行在JVM平台上的,所以安装Scala之前要安装JDK

JDK = JRE + 开发工具 = JVM + 类库 + 开发工具

 

●Windows安装Scala编译器

1. https://www.scala-lang.org/download/all.html

下载scala-2.11.8.msi双击傻瓜式安装就可以了(一般会自动配置上环境变量)。

也可以下载scala-2.11.8.zip解压即可

2.检查并配置环境变量SCALA_HOME和path=%SCALA_HOME%in;

 

 

●Linux安装Scala编译器(不需要)

1.下载Scala地址https://www.scala-lang.org/download/2.11.8.html

然后解压Scala到指定目录

tar -zxvf scala-2.11.8.tgz -C /opt/soft

2.配置环境变量,将scala加入到PATH中

vim /etc/profile

export JAVA_HOME=/opt/soft/jdk1.8

export PATH=$PATH:$JAVA_HOME/bin:/opt/soft/scala-2.11.8/bin

 

●测试是否安装成功

使用scala命令查看安装版本

 

 

 

 

 

(二)Scala初体验

1.在控制台和记事本编写HelloWorld

 

 

 

1.编写HelloScala.scala

object HelloScala{

    def main(args: Array[String]): Unit = {

        println("Hello, world!")

    }

}

2.把源码编译为字节码

scalac HelloScala.scala  

3.把字节码放到虚拟机中解释运行

scala HelloScala  

 

2.在IDEA中编写HelloWorld

1)IDAE安装Scala插件

如果有网络可以选择在线安装Scala插件。

或者下载插件离线安装 https://plugins.jetbrains.com/plugin/1347-scala

 

●在线安装

 

 

 

●离线安装Scala插件:

Configure -> Plugins -> Install plugin from disk -> 选择Scala插件 -> OK -> 重启IDEA

 

 

 

 

●设置补全变量类型(后面需要用到)

 

 

 

 

2)创建Scala项目

●创建项目

 

 

 

●注意:如果没有识别Scala-SDK需要手动设置

 

 

 

3)创建Maven项目

●pom.xml:

<properties>
        <project.build.source.Encoding>UTF-8</project.build.source.Encoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>2.11.8</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <version>2.15.2</version>
                <executions>
                    <execution>
                        <id>scala-compile-first</id>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <includes>
                                <include>**/*.scala</include>
                            </includes>
                        </configuration>
                    </execution>
                    <execution>
                        <id>scala-test-compile</id>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

 

●创建scala文件夹,并标记为source

 

 

 

●创建scala class了

 

 

 

●选择创建object

 

 

 

 

4)编写代码

●在Scala中编写Java代码

object HelloScala {
  def main(args: Array[String]): Unit = {
    println("hello scala")
    System.out.println("hello java")
  }
}

●在Java中编写Scala代码

public class HelloJava {
    public static void main(String[] args){
      System.out.println("hello java");
      Console.print("hello scala");
    }
}

 

四、Scala基础语法

(一)变量、类型、操作符

1.声明变量

1)语法详解

●Java中:

数据类型 变量名 = 值;

 

●在Scala中:

val/var 变量名:变量类型 = 变量值

 

●说明

var声明的变量可以重新赋值

val声明的变量不可以重新赋值,或称之为不可变变量/只读变量。相当于java里用final修饰的变量

 

●注意:

Scala中变量声明类型可以省略,解析器会根据值进行推断

Scala中语句最后不需要添加分号

val和var声明变量时都必须初始化

为了减少可变性引起的bug,应该尽可能地使用不可变变量。

如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号反引号: val `val` = 123

 

●总结

 

 

 

 

2)代码演示

package cn.itcast.basic

/**
  * Author caowei
  * Date 2019/7/3 11:35
  * Desc 演示变量定义
  */
object VariableDemo {

  def main(args: Array[String]): Unit = {
    //变量定义格式:
    //val/var 变量名:变量类型 = 变量值
    var v1:Int = 10000

    v1 = 20000 //var修饰的变量可以重新被赋值

    val v2:String = "itcast"

    //v2 = "heima" //val修饰的变量不可以被重新赋值,类似于Java中的final修饰的变量

    var v3 = "hello" //类型可以省略,编译器会自动推断
  }

}

3)扩展:type关键字

Scala 里可以通过type 关键字来声明类型。

type 相当于声明一个类型别名:

// 把String 类型用S 代替

type S = String

val name: S = "bigdata"

println (name)

 

4)扩展:懒值

●应用场景

当val被声明为lazy时,初始化将被推迟,只有当这个变量真正被使用到的时候,变量的赋值代码才会真正的执行

lazy适用于初始化开销较大的场景

 

●代码演示

package cn.itcast.basic

object LazyDemo {
  def main(args: Array[String]): Unit = {
    lazy val msg = init()

    println("init方法调用了,但是没有执行")
    println(msg)
  }
  def init(): String = {
    println("init方法执行")
    "嘿嘿嘿,我来了~"
  }
}

 

 

2.字符串

1)语法详解

scala提供多种定义字符串的方式,我们可以根据需要来选择最方便的定义方式。

●双引号

val/var 变量名 = "字符串"

●三引号

如果有大段的文本需要保存,就可以使用三引号来定义字符串。例如:保存一大段的SQL语句。三个引号中间的所有字符串都将作为字符串的值。  
   val/var 变量名 = """字符串1字符串2"""

●使用插值表达式

scala中,可以使用插值表达式来定义字符串,有效避免大量字符串的拼接。

val/var 变量名 = s"${变量/表达式}字符串"

 

2)代码演示

package cn.itcast.basic

/**
  * Author caowei
  * Date 2019/7/3 12:27
  * Desc
  */
object StringDemo {

  def main(args: Array[String]): Unit = {
    //a.双引号
    var s1 = "jack"


    //b.三引号
    var s2 =

      """
        |select *
        |from student
        |where name = 'jack'
      """.stripMargin

    //c.使用插值表达式
    var s3 = s"my name is ${s1}"


    var tableName = "student"
    var s4 =
      s"""
         |select *
         |from ${tableName}
         |where name = ${s1}
       """.stripMargin

    println(s1)
    println(s2)
    println(s3)
    println(s4)
  }
}

 

 

3.数据类型

 

1)数值类型

Scala和Java一样,有多种数值类型Byte、Char、Short、Int、Long、Float、Double类型和1个Boolean类型。

Boolean

true 或者 false

Byte

8位, 有符号

Short

16位, 有符号

Int

32位, 有符号

Long

64位, 有符号

Char

16位, 无符号

Float

32位, 单精度浮点数

Double

64位, 双精度浮点数

String

其实就是由Char数组组成

 

2)Scala继承体系

 

 

●Any

在scala中,所有的类,包括值类型,都最终继承自一个统一的根类型Any,Any类是根节点

Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等。AnyVal和AnyRef都扩展自Any类。

 

●AnyVal-所有值类型的基类,所有的值都是类类型都是AnyVal的子类

  - scala.Double

  - scala.Float

  - scala.Long

  - scala.Int

  - scala.Char

  - scala.Short

  - scala.Byte

上面是数字类型。

还包括scala.Unit 和 scala.Boolean 是非数字类型。

 

●AnyRef-是所有引用类型的基类。

除了值类型,所有其他类型都继承自AnyRef

 

●Null

是所有引用类型的子类型,Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型,但是不能赋值给值类型。

 

●Nothing

是所有类型的子类型。Nothing类型没有实例。它对于泛型结构是有用处的,举例:

空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。

Nothing可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。

 

●Unit

用来标识过程,也就是没有明确返回值的函数。

由此可见,Unit类似于Java里的void。Unit只有一个对象实例(),这个实例也没有实质的意义。

 

●注意

1.Scala并不刻意区分基本类型和引用类型,所以这些类型都是对象,可以调用相对应的方法。

2.每一种数值类型都有对应的Rich* 类型,如RichInt、RichChar等,为基本类型提供了更多的有用操作。

3.String直接使用的是java.lang.String类,另外在scala.collection.immutable.StringOps中还定义了更多的操作。在需要时String能隐式转换为StringOps,因此不需要任何额外的操作,String就可以使用这些方法。

 

 

4.操作符

1)语法详解

Scala中的+ - * / %等操作符的作用与Java一样,位操作符 & | ^ >> <<也一样。

●注意:

1.Scala中的操作符实际上是方法

2.Scala中没有++、--操作符,需要通过+=、-=来实现同样的效果

3.+ - * / %是方法,那么就可以进行操作符重载,完成特殊的运算(也就是自己在类中定义+ - * / %方法表示特殊的运算)

2)代码演示

package cn.itcast.basic

object OperationDemo {
  def main(args: Array[String]): Unit = {
    var a = 1 + 2
    var b = 1.+(2)
    println(a)//3
    println(b)//3.0

    //var c = a++ //错误
    a += 1

    a.+=(1)
    println(a)//5
  }

}

 

 

(二)判断和循环

1.块表达式

1)语法详解

定义变量时可以使用 {} 包含一系列表达式,其中{}块的最后一个表达式的值就是整个块表达式的值。

2)代码演示

package cn.itcast.basic

object BlockExpressionDemo {
  def main(args: Array[String]) {
    val a = 1
    val b = 2
    val sum = {
      val c = a + b
      val d = c + a
      d
    }
    println(sum)
  }
}

 

 

2.条件表达式

1)语法详解

Scala条件表达式的语法和Java一样,只是更加简洁,且Scala中if else表达式是有返回值的

●注意:

Scala中没有三元表达式

如果if或者else返回的类型不一样,就返回Any类型

对于没有返回值的,使用Unit,写做(),相当于java中的void

2)代码演示

package cn.itcast.basic

object ConditionDemo {
  def main(args: Array[String]) {
    val sex = "male"
    val result1: Int = if(sex == "male") 1 else 0
    println(result1)

    //支持混合类型
    val result2:Any = if(sex == "male") "男" else 0

    println(result2)

    //如果缺失else,相当于if(sex == "female") "" else ()
    val result3 = if(sex == "female") "女"

    println(result3)

    //Unit表示不返回任何结果,只有一个实例值,写成(),相当于Java中的void,。
    val result4 = if(sex == "female") "女" else ()

    println(result4)

    //ifelse if
    val result5 = if (sex == "male") 1

            else if (sex == "female") 0
            else "性别未知"
    println(result5)
  }
}

 

 

3.循环表达式

1)语法详解

在scala中,可以使用for循环和while循环,但一般推荐使用for表达式,因为for表达式语法更简洁

 

●简单for循环:

for (变量 <- 表达式/数组/集合) {循环体}

for(i <- 1 to 10) println(i)

 

●嵌套for循环

for (变量1 <- 表达式/数组/集合; 变量2 <- 表达式/数组/集合) {循环体}

for(i <- 1 to 9; j <- 1 to 9){

      if(i >= j ) print(s"${j} * ${i} = ${j*i}")

      if(j == 9) println()

    }

●守卫

for表达式中,可以添加if判断语句,这个if判断就称之为守卫。我们可以使用守卫让for表达式更简洁。

for(i <- 表达式/数组/集合 if 表达式) {循环体}

for(i <- 1 to 10 if i % 3 == 0) println(i)

 

●for推导式

在for循环体中,可以使用yield表达式构建出一个集合,我们把使用yield的for表达式称之为推导式

即可以使用for推导式生成一个新的集合(一组数据)

//该for表达式会构建出一个集合

val l = for(i <- 1 to 10) yield i * 10

 

●注意:

while语句本身没有值,即整个while语句的结果是Unit类型的()

var n = 1;

    val result = while(n <= 10){

      n += 1

    }

    println(result)

    println(n)

2)代码演示

package cn.itcast.basic

/**
  * Author caowei
  * Date 2019/7/3 15:06
  * Desc
  */
object LoopDemo {

  def main(args: Array[String]): Unit = {
    //生成1~10,to返回值为Range[],untilRange[)
    println("==========")

    val l1 = 1 to 10  //[1,10]
    val l2 = 1 until 10 //[1,10)
    val l3 = 1 to 10 by 2 //[1,10]步长为2
    println(l1)

    println(l2)
    println(l3)

    println("==========")
    //1.简单for循环
    //for(i <- 表达式/数组/集合) 循环体
    //每次循环将区间中的一个值赋给i
    for (i <- 1 to 10) println(i)


    println("==========")
    val arr = Array("a", "b", "c")
    for (i <- arr) println(i)

    println("==========")
    //2.嵌套for循环
    for(i <- 1 to 9 ;j <- 1 to 9){

     if(i >= j) print(s"${j} * ${i} = ${j * i} ")
     if(j == 9) println()
    }


    println("==========")
    //3.守卫

//每个生成器都可以带一个条件,注意:if前面没有分号
    for(i <- 1 to 10 if i % 3 == 0) println(i)


    println("==========")
    //4.for推导式:
    val l = for (i <- 1 to 10) yield i * 10

    println(l) // 10,20,30..100

    println("==========")

    //了解:for关键字后面跟{} ()都可以
    //for 推导式仅包含单一表达式时使用()括号,当其包含多个表达式时使用{}括号
    for(

      i <- 1 to 3;//[1,3]
      j = 4 - i//[3,1]
    )

      print(i * j + " ")
    println()

    for {
      i <- 1 to 3;
      j = 4 - i
    }
      print(i * j + " ")
    println()
    
    println("==========")
    //注意:while循环没有返回值
    var n = 1;

    val result = while(n <= 10){
      n += 1
    }
    println(result)
    println(n)
  }
}

 

 

3)扩展:breakcontinue

scala并没有提供break和continue关键字来退出循环,

如果需要使用break/continue,可以使用scala.util.control.Breaks对象的breakable和break方法。

 

●实现break

导入Breaks包import scala.util.control.Breaks._

使用breakable将for表达式包起来

for表达式中需要退出循环的地方,添加break()方法调用

//打印1-100的数字,如果数字到达20,退出for表达式

    import scala.util.control.Breaks._

    breakable{

      for(i <- 1 to 100) {

        if(i >= 20)

          break()

        else

          println(i)

      }

    }

 

●实现continue

continue的实现与break类似,但有一点不同:

实现break是用breakable{}将整个for表达式包起来,

实现continue是用breakable{}将for表达式的循环体包含起来

//打印1-100的数字,如果数字能整除10,不打印

    for(i <- 1 to 100 ) {

      breakable{

        if(i % 10 == 0)

          break()

        else println(i)

      }

    }

 

●代码演示

package cn.itcast.basic

/**
  * Author caowei
  * Date 2019/7/3 15:06
  * Desc
  */
object LoopDemo2 {

  def main(args: Array[String]): Unit = {
    //实现break
    //打印1-100的数字,如果数字到达20,退出for表达式
    import scala.util.control.Breaks._

    breakable{
      for(i <- 1 to 100) {
        if(i >= 20)
          break()
        else
          println(i)

      }
    }
    
    println("===================")
    //实现continue
    //打印1-100的数字,如果数字能整除10,不打印 
    for(i <- 1 to 100 ) {

      breakable{
        if(i % 10 == 0)
          break()
        else println(i)
      }
    }
  }
}

 

(三)方法和函数

1.定义方法

1)基本语法

def 方法名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 = {方法体}

 

 

 

 

2)小练习

定义一个方法,求n~m的累加和

package cn.itcast.basic

/**
  * Author caowei
  * Date 2019/7/3 15:54
  * Desc
  */
object MethodAndFunctionDemo {

  def main(args: Array[String]): Unit = {
    //定义一个方法,n~m的累加和
    val n = 1

    val m = 100
    val result: Int = getSum(1,100)
    println(result)
  }

  //def 方法名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 = {方法体}
  def getSum(start:Int,end:Int):Int = {

    var sum = 0;
    for(i <- start to end) sum += i
    sum
  }
}

 

 

3)语法细节

  1. 方法的返回值类型和return可以不写,编译器可以自动推断出来
  2. 对于递归方法,必须指定返回类型
  3. 如果方法没有返回值,返回Unit类型(类似于void,也可以不写)
  4. 返回值类型有多种情况则返回Any
  5. 带有默认值参数的方法,调用时,可以给定新值,也可以使用默认值
  6. 可以通过参数名来指定传递给哪一个参数,这样传递参数时就可以不按照顺序传递
  7. 方法没有参数,调用时可以省略(),如果定义时()省略,调用时则必须省略
  8. 可变参使用 变量名:类型* (类似Java的...)

 

4)代码演示

package cn.itcast.basic

object MethodAndFunctionDemo2 {
  def main(args: Array[String]): Unit = {
    val r1 = add(2,3)
    println(r1)

    val r2 = factorial(4)
    println(r2)//4*3*2*1==24

    method4("abc")

    method4(content="abc")
    method4(content="abc",leg=3)
    method4(leg=3,content="abc")
    method5
    method6


    val s1: Int = getSum(1,2)

    println(s1)
    val s2: Int = getSum(1,2,3)
    println(s2)
  }

  //1.方法的返回值类型和return可以不写,编译器可以自动推断出来
  //def 方法名(参数名:参数类型,参数名:参数类型):返回值类型={方法体}
  def add(x:Int, y:Int):Int = {

    x+y
  }

  //2.对于递归方法,必须指定返回类型
  def factorial(n: Int): Int = {

    if(n <= 0)
      1
    else
      n * factorial(n - 1)

  }
  //f(3) --> 3*f(2) -->3*2*f(1) -->3*2*1*f(0) -->3*2*1*1 ==> 6

  //3.如果方法没有返回值,返回Unit类型(类似于void,也可以不写)
  def method1(content: String) : Unit = {

    println(content)
  }

  def method2(content: String) = {
    println(content)
  }

  //4.返回值类型有多种情况则返回Any
  def method3(content: String):Any = {

    if(content.length >= 3)
      content + "喵喵喵~"
    else
      3

  }

  //5.带有默认值参数的方法,调用时,可以只给无默认值的参数传递值,也可以传递给定值,给定值覆盖默认值;
  //6.可以通过参数名来指定传递给哪一个参数,这样传递参数时就可以不按照顺序传递
  def method4(content: String, leg: Int = 4) = {

    println(content + "," + leg)
  }


  //7.方法没有参数,调用时可以省略,如果定义时()省略,调用时则必须省略
  def method5()={

    println("method5")
  }
  def method6={
    println("method6")
  }

  //8.可变参使用 变量名:类型* (类似Java...
  def getSum(args: Int*) = {

    var sum = 0
    for(i <- args)
      sum += i
    sum
  }
}

 

 

2.定义函数

1)语法详解

●完整语法:

val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>函数体

●简写语法:

val函数名称 = (参数名称:参数类型) => 函数体

●符号解释

=  表示将右边的函数赋给左边的变量

=> 左面表示输入参数名称和类型,右边表示函数的实现和返回值类型

 

2)代码演示

定义一个函数,实现两个数相加

package cn.itcast.basic

object MethodAndFunctionDemo3 {
  def main(args: Array[String]): Unit = {
    //定义一个函数,实现两个数相加
    //1.完整语法:
    //val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>函数体
    val add:(Int,Int)=>Int = (a:Int,b:Int) => {a + b}

    val r1 = add(1,2)//调用函数
    println(r1)


    //2.简写语法:
    //val函数名名称 = (参数名称:参数类型) => 函数体
    val add2 = (a:Int,b:Int) => {a + b}

    val r2 = add2(1,2)//调用函数
    println(r2)

  }
}

 

 

3.方法和函数的区别

1)区别详解

●方法:

和之前学习Java时理解的方法一样,是封装了完成某些功能的代码块,所属于某一个类或对象

 

●函数:

在Scala中,函数是头等公民,函数是一个对象,那么既然是对象的话,函数就可以赋值给变量或者当作参数被传递,还可以使用函数打点调用方法

Scala中函数继承自FuctionN,带有一个参数的函数的类型是function1,带有两个是function2,以此类推

 

2)代码演示:

i 函数是对象

函数对象有apply、curried、toString、tupled这些方法。而方法不具有这些特性。

package cn.itcast.basic


object MethodAndFunctionDemo4{
  def main(args: Array[String]): Unit = {
    val f1 = (x:Int) => x
    val f2 = (x:Int,y:Int) => x + y
    val f3 = (x:Int,y:Int,z:Int) => x + y + z
    println(f1)
    println(f2)
    println(f3)
  }
}

 

 

 

 

ii 函数可以赋值给变量并将可以当作参数进行传递

定义一个函数,接收两个参数,返回两个数的和

定义一个方法,接收两个参数和一个函数,并在方法体中调用函数,将两个参数传递给函数

package cn.itcast.basic

object MethodAndFunctionDemo6 {
  def main(args: Array[String]): Unit = {
    //定义一个函数,接收两个参数,返回两个数的和
    val fun = (a:Int,b:Int) => a + b  //可以理解成将函数对象赋值给fun变量

    val r1: Int = operator(1, 2, fun)//调用方法,并将函数当作参数进行传递
    println(r1)


    val r2: Int = operator(1,2,(a,b)=>a+b)//直接把函数当作参数进行传递
    println(r2)

  }
  //定义一个方法,接收两个参数和一个函数,并在方法体中调用函数,将两个参数传递给函数
  def operator(a:Int,b:Int,fun:(Int,Int)=>Int):Int = {

    fun(a,b)
  }
}

 

 

iii 方法可以转换成函数

将方法转换成函数,只需要在方法的后面加上一个下划线

package cn.itcast.basic

object MethodAndFunctionDemo5{
  def main(args: Array[String]): Unit = {
    val fun = method1 _  //将方法转换成了函数
    println(fun)//<function1>
    fun(1)//调用函数
  }


  def method1(i:Int)={
    println("接收到参数"+i)
  }
}

 

 

 

4.扩展:类比java函数式编程

 

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Demo {

    public static void main(String[] args){
        //需求:定义一个方法,接收两个参数和一个函数,并在方法体中,将参数传递给函数
        int result = operator(1, 2, (a, b) -> a + b);

        System.out.println(result);

    }
    public static int operator(int a,int b,Fun fun){
        return fun.exec(a,b);
    }
}

@FunctionalInterface
interface Fun{

    int exec(int a,int b);
}

 

 

 

5.总结

●定义方法:

def 方法名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 = {方法体}

●定义函数完整语法:

val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>函数体

●定义函数简写语法:

val函数名名称 = (参数名称:参数类型) => 函数体

 

●注意:

严格的来说,函数与方法是不同的东西。定义方式上也有很多的区别

但是在Scala中,函数本身是对象,方法可以通过下划线_转换为函数。

 

 

五、Scala常用数据结构/集合

(一)Scala集合分类和继承体系

1.集合分类

●集合分类-按照数据结构特点分

Scala的集合都扩展自Iterable特质(先理解为接口)

有三大类:Seq序列、Set、Map映射

 

●集合分类-按照可变和不可变分

大部分的集合Scala都同时提供了可变和不可变的版本。

开发时建议Scala优先采用不可变集合(默认即为不可变),满足不了需求是再使用可变集合

 

●可变集合和不可变集合相应的包为:

不可变集合:scala.collection.immutable (默认)

可变集合:  scala.collection.mutable

 

●注意

val和可变不可变

var和val指的是:变量能否被重新赋值(引用可不可变)

集合可不可变指的是:集合长度或内容可不可变

 

对于数组: 长度可不可变

不可变数组Array:长度不可变,元素可变(定长数组)

可变数组ArrayBuffer:长度可变,但里面的元素可变(变长数组)

 

对于其他集合:内容可不可边

不可变集合immutable:长度不可变,内容不可变,如果调用添加或者删除方法,会产生新的集合,原集合不变

可变集合mutable:长度可变,内容可变

 

2.继承体系

●不可变集合(immutable )继承层次:

 

 

 

●可变集合(mutable)继承层次:

 

 

 

 

(二)数组

1.语法详解

●不可变/定长数组:

  val/var 变量名= new Array[T](数组长度)

val/var 变量名 = Array(元素1, 元素2, 元素3...)

●可变/变长数组:

  val/var 变量名 = ArrayBuffer[T]() //需要导入import scala.collection.mutable.ArrayBuffer包

val/var 变量名 = ArrayBuffer(元素1, 元素2, 元素3...)

 

●数组操作

指定分隔符  mkString

将数组转换成数组缓冲 toBuffer(打印Buffer可以看到数组内容)

根据索引获取元素 ()

添加元素 +=

删除元素 -=

追加一个数组到变长数组 ++=

往指定角标插入元素 insert

删除指定角标的元素 remove

定长=>>变长 toBuffer

变长=>>定长 toArray  

多维数组 Array.ofDim[Double](3,4)

 

●遍历数组

1.可以for循环直接遍历数组

2.可以遍历下标再根据下标获取元素

3.回忆一下生成指定范围的序列

0 to n   生成[0,n]

0 until n 生成[0,n)

 

●数组其他常用方法

在Scala中,数组上的某些方法对数组进行相应的操作非常方便!

sum求和

max求最大值

min求最小值

sorted排序

reverse反转

 

2.代码演示

●基本操作

package cn.itcast.collection

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

object ArrayDemo {
  def main(args: Array[String]): Unit = {
    //1.定长数组定义格式:
    //val/var 变量名= new Array[T](数组长度)
    //val/var 变量名 = Array(元素1, 元素2, 元素3...)
    val arr1 = new Array[Int](8)//初始化一个长度为8的定长数组,其所有元素均为0
    println(arr1)//直接打印定长数组,内容为数组的地址值
    println(arr1.mkString(","))

    println(arr1.toBuffer)//toBuffer方法会将数组转换成数组缓冲,就可以看到原数组中的内容了

    val arr2 = Array[Int](10)//注意:初始化一个长度为1,内容为10的定长数组
    println(arr2.toBuffer) //ArrayBuffer(10)

    val arr3 = Array[String]("Hadoop","Storm","Spark")//定义一个长度为3的定长数组
    //使用()来访问元素
    println(arr3(1))

    println("========================================================")


    //2.变长数组/数组缓冲定义格式:
    //val/var 变量名 = ArrayBuffer[T]()
    //val/var 变量名 = Array(元素1, 元素2, 元素3...)
    //或者导入import scala.collection.mutable.ArrayBuffer
    val ab = scala.collection.mutable.ArrayBuffer[Int]()//定义一个长度为0的变长数组

    ab += 1 //+=尾部追加一个元素
    ab += (2,3,4,5)  //追加多个元素
    ab ++= Array(6,7) //追加一个定长数组++=
    ab ++= ArrayBuffer(8,9) //追加一个变长数组
    println(ab)//打印数组缓冲ab,结果: 1,2,3,4,5,6,7,8,9
    ab.insert(0,-100,200)//使用insert往指定角标插入元素,在0角标插入100200
    println(ab)//-100,200,1,2,3,4,5,6,7,8,9
    ab.remove(0) //使用remove删除指定角标的元素
    println(ab)//200,1,2,3,4,5,6,7,8,9
    println("==========================================")


    //3.互相转换
    //toBuffer 定长=>>变长
    val buffer: mutable.Buffer[Int] = arr1.toBuffer

    println(buffer)
    //toArray  变长=>>定长
    val array: Array[Int] = ab.toArray

    println(array)
    println("==========================================")
    
    //4.多维数组(了解)
    //创建一个3*4的二维数组
    val multiArr = Array.ofDim[Double](3,4)

    multiArr(1)(1) = 11.11
    println(multiArr(1).toBuffer)


  }
}

 

●遍历

package cn.itcast.collection

object ArrayDemo2 {
  def main(args: Array[String]): Unit = {
    //初始化一个数组
    val arr = Array(1, 2, 3, 4, 5, 6, 7, 8)


    //1.可以for循环直接遍历数组
    for (i <- arr)

      println(i)
    println("===========")

    //逆序遍历
    for (i <- arr.reverse)

      println(i)
    println("===========")

    //2.可以遍历下标再根据下标获取元素
    for(i <-(0 until arr.length))

      println(arr(i))

    println("===========")
    for (i <- (0 until arr.length).reverse)
      println(arr(i))
  }
}

 

●其他操作

package cn.itcast.collection

object ArrayDemo3{
  def main(args: Array[String]): Unit = {
    val arr = Array(1, 2, 3, 4, 5, 6, 7, 8)
    println(arr.sum)
    println(arr.max)
    println(arr.min)
    val sorted = arr.sorted
    println(arr.toBuffer)
    println(sorted.toBuffer)
    println(sorted.reverse.toBuffer)
  }
}

 

(三)元组

1.语法详解

元组也是可以理解为一个容器,可以用来存放各种相同或不同类型的数据。例如:姓名,年龄,性别,出生年月。

元组的元素是不可变的。

●创建元组

使用括号来定义元组

val/var 元组 = (元素1, 元素2, 元素3....)

使用箭头来定义元组(元组只有两个元素)

val/var 元组 = 元素1->元素2 //对偶/二元组是最简单的元组(k,v)

 

●获取元组中的值

使用下划线加脚标 ,例如 tuple._1  tuple._2  tuple._3

注意:元组中的元素脚标是从1开始的

 

●将对偶(二元组)组成的数组转换成映射(映射就相当于Java中的Map,后面会学)

将对偶/二元组的集合转换成映射:

调用其toMap 方法

 

 

 

●遍历

可以调用元组的productIterator方法获取迭代器对象进行遍历

 

2.代码演示

package cn.itcast.collection

object TupleDemo {
  def main(args: Array[String]): Unit = {
    //定义
    val t1 = ("hadoop",3.14,10086)//元组中的多个元素用逗号,分割
    val t2 = ("hadoop"->3.14) //如果元组中只有两个元素可以使用->分割
    println(t1)

    println(t2)
    println(t1.getClass)
    println(t2.getClass)

    //获取元组中的元素,注意元组中的下标从1开始
    println(t1._1)

    println(t1._2)
    println(t1._3)

    //对偶/二元组集合转映射
    val arr = Array(("tom",85),("jerry",99),("kitty",90))

    val map = arr.toMap
    println(arr.toBuffer)
    println(map)

    //遍历
    //方式1
    for (elem <- t1.productIterator) {

      print(elem)
    }
    println("===========")
    //方式2
    t1.productIterator.foreach(print)

  }
}

 

3.扩展:拉链操作

使用zip将多个值绑定在一起,如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数组的元素个数

使用zipAll将多个值绑定在一起,缺少的用默认的元素填充

 

package com.bigdata.collection

object OtherOperationDemo {
  def main(args: Array[String]): Unit = {
    val names = Array("zhangsan","lisi")

    val ages = Array(20,30)

//使用zip将多个值绑定在一起
    val tuples = names.zip(ages)
    println(tuples.toBuffer)//ArrayBuffer((zhangsan,20), (lisi,30))

    val names2 = Array("zhangsan","lisi","wangwu")

    //如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数组的元素个数
    val tuples2 = names2.zip(ages)

    println(tuples2.toBuffer)//ArrayBuffer((zhangsan,20), (lisi,30))

    //使用zipAll将多个值绑定在一起,缺少的用默认的元素填充
    val tuples3 = names2.zipAll(ages,"zhaoliu",18)

    println(tuples3.toBuffer)//ArrayBuffer((zhangsan,20), (lisi,30), (wangwu,18))

  }
}

 

 

(四)List

1.语法详解

●高能预警

List操作的API方法和符号特别特别多,不用刻意去记,后续学习中会使用一些常见的,用的多了就掌握了!

 

●List介绍

列表是scala中最重要的、也是最常用的数据结构。在scala中,也有两种列表,一种是不可变列表、另一种是可变列表

但都具备以下性质:

可以保存重复的值

有先后顺序

 

●不可变列表(默认) 

import scala.collection.immutable._

创建方式1.使用List(元素1, 元素2, 元素3, ...)来创建一个不可变列表

    val/var 变量名 = List(元素1, 元素2, 元素3...)

 

创建方式2.使用::方法创建一个不可变列表

    val/var 变量名 = 元素1 :: 元素2 :: Nil

 

注意:

使用::拼接方式来创建列表,必须在最后添加一个Nil

 

●可变列表 

import scala.collection.mutable._

创建方式1.使用ListBuffer[元素类型]()创建空的可变列表

    val/var 变量名 = ListBuffer[Int]()

 

创建方式2.使用ListBuffer(元素1, 元素2, 元素3...)创建可变列表

    val/var 变量名 = ListBuffer(元素1,元素2,元素3...)

 

●head和tail

在Scala中列表要么为Nil(Nil表示空列表)

要么是一个head元素加上一个tail列表。

 

●::操作符

:: 操作符是将给定的头和尾创建一个新的列表,原列表不变

:: 操作符是右结合的,如1 :: 5 :: 2 :: Nil相当于 1 :: (5 :: (2 :: Nil))   ==> List(1,5,2)

 

●可变列表操作

- 获取/更改元素(使用括号访问(索引值))

- 添加元素(+=)

- 追加一个列表(++=)

- 删除元素(-=)

- 转换为List(toList)

- 转换为Array(toArray)

 

●扩展:list其他操作符(了解)

::  (x: A): List[A]      在列表的头部添加一个元素或列表

+:  (elem: A): List[A] 在列表的头部添加一个元素

:+  (elem: A): List[A] 在列表的尾部添加一个元素

++  [B](that: GenTraversableOnce[B]): List[B] 从列表的尾部添加另外一个列表

:::  (prefix: List[A]): List[A] 在列表的头部添加另外一个列表

 

●扩展:等价操作(了解)

val left = List(1,2,3)

val right = List(4,5,6)

//以下操作等价

left ++ right      // List(1,2,3,4,5,6)

right.:::(left)     // List(1,2,3,4,5,6)

//以下操作等价

0 +: left    //List(0,1,2,3)

left.+:(0)   //List(0,1,2,3)

//以下操作等价

left :+ 4    //List(1,2,3,4)

left.:+(4)   //List(1,2,3,4)

//以下操作等价

0 :: left      //List(0,1,2,3)

left.::(0)     //List(0,1,2,3)

 

 

2.代码演示

package cn.itcast.collection

import scala.collection.mutable.ListBuffer

object ListDemo {
  def main(args: Array[String]): Unit = {
    //创建一个不可变的集合
    val list1 = List(1, 2, 3, 4)

    //list1 = List(1,2)//错误list1val修饰,不可重新赋值
    println(list1)


    val head1 = list1.head //获取集合的第一个元素
    println(head1) //1

    val tail1 = list1.tail //获取集合中除第一个元素外的其他元素集合,
    println(tail1) //List(2, 3, 4)

    val list2 = List(1)

    println(list2) //List(1)
    val head2 = list2.head //如果 List 中只有一个元素,它的 head 就是这个元素,
    println(head2) //1

    val tail2 = list2.tail //如果 List 中只有一个元素,它的 tail 就是 Nil
    println(tail2) //List()

    val list3 = 1 :: List(2, 3)

    println(list3) //List(1, 2, 3)

    val list4 = list1 :: list2

    println(list4) //List(List(1, 2, 3, 4), 1)

    val list5 = 1 :: Nil
    println(list5) //List(1)

    val list6 = list1.+:(1) //+:是在头部添加,等价于1 +: list1
    println(list6) //List(1, 1, 2, 3, 4)

    val list7 = list1.:+(1) //:+是在尾部添加,等价于list1 :+ 1
    println(list7) //List(1, 2, 3, 4, 1)

    val list8 = list1 ++ list2 //++是尾部拼接
    println(list8) //List(1, 2, 3, 4, 1)

    val list9 = list1.:::(list2) //:::是头部拼接
    println(list9) //List(1, 1, 2, 3, 4)
    
    println("===========================================================")

    //构建一个可变列表,初始有3个元素1,2,3
    val lst0 = ListBuffer[Int](1, 2, 3)

    println(lst0) //ListBuffer(1, 2, 3)

    //创建一个空的可变列表
    val lst1 = new ListBuffer[Int]

    //lst1中追加元素,注意:没有生成新的集合
    lst1 += 4

    lst1.append(5)
    println(lst1) //ListBuffer(4, 5)

    //lst1中的元素追加到lst0中, 注意:没有生成新的集合
    lst0 ++= lst1

    println(lst0) //ListBuffer(1, 2, 3, 4, 5)

    //lst0lst1合并成一个新的ListBuffer 注意:生成了一个集合
    val lst2 = lst0 ++ lst1

    println(lst2) //ListBuffer(1, 2, 3, 4, 5, 4, 5)

    //将元素追加到lst0的后面生成一个新的集合
    val lst3 = lst0 :+ 5

    println(lst3) //ListBuffer(1, 2, 3, 4, 5, 5)

    //删除元素,注意:没有生成新的集合
    val lst4 = ListBuffer[Int](1, 2, 3, 4, 5)

    lst4 -= 5
    //lst4.remove(5)
    println(lst4) //ListBuffer(1, 2, 3, 4)

    //删除一个集合列表,生成了一个新的集合
    val lst5 = lst4 -- List(1, 2)

    println(lst5) //ListBuffer(3, 4)

    //把可变list 转换成不可变的list 直接加上toList
    val lst6 = lst5.toList

    println(lst6) //List(3, 4)

    //把可变list转变数组用toArray
    val lst7 = lst5.toArray

    println(lst7) //[I@7f63425a

  }

}

 

 

(五)Queue

1.语法详解

●说明

队列数据存取符合先进先出的策略

有 scala.collection.mutable.Queue 和 scala.collection.immutable.Queue

一般来说我们在开发中队列通常使用可变队列

 

●常见操作

+=追加

enqueue入队

dequeue出队

 

2.代码演示

package cn.itcast.collection

/**
  * Author caowei
  * Date 2019/7/3 20:16
  * Desc
  */
object QueueDemo {

  def main(args: Array[String]): Unit = {
    //队列的创建
    import scala.collection.mutable.Queue

    val q1 = new Queue[Int]
    println(q1) //Queue()
    //队列元素的追加
    q1 += 1;

    println(q1)//Queue(1)
    //向队列中追加List
    q1 ++= List(2, 3, 4)

    println(q1)//Queue(1, 2, 3, 4)
    //按照进入队列的顺序删除元素
    q1.dequeue()

    println(q1)//Queue(2, 3, 4)
    //塞入数据
    q1.enqueue(9, 8, 7)

    println(q1)//Queue(2, 3, 4, 9, 8, 7)
    //返回队列的第一个元素
    println(q1.head)//2
    //返回队列最后一个元素
    println(q1.last)//7
    //返回除了第一个以外的元素
    println(q1.tail)//Queue(3, 4, 9, 8, 7)
  }

}

 

 

 

(六)Set

1.语法详解

●Set说明

Set代表一个没有重复元素的无序集合;即无法加入重复元素且不保证插入顺序的。

●不可变Set(默认)

import scala.collection.immutable._

1.创建一个空的不可变集,语法格式:

    val/var 变量名 = Set[类型]()

2.给定元素来创建一个不可变集,语法格式:

    val/var 变量名 = Set(元素1, 元素2, 元素3...)

 

●可变Set

import scala.collection.mutable._

格式相同,导包不同

 

Set操作

方法

描述

def +(elem: A): Set[A]

为集合添加新元素,并创建一个新的集合,除非元素已存在

def -(elem: A): Set[A]

移除集合中的元素,并创建一个新的集合

def contains(elem: A): Boolean

如果元素在集合中存在,返回 true,否则返回 false。

def &(that: Set[A]): Set[A]

返回两个集合的交集

def &~(that: Set[A]): Set[A]

返回两个集合的差集

def ++(elems: A): Set[A]

合并两个集合

 

2.代码演示

●不可变Set

package cn.itcast.collection

object SetDemo {
  def main(args: Array[String]): Unit = {
    //定义一个不可变的Set集合
    val set1 = Set(1, 2, 3, 4, 5, 6, 7)

    println(set1) //Set(5, 1, 6, 2, 7, 3, 4)

    //将元素和set1合并生成一个新的set,原有set不变
    val set2 = set1 + 8

    println(set1) //Set(5, 1, 6, 2, 7, 3, 4)
    println(set2) //Set(5, 1, 6, 2, 7, 3, 8, 4)

    val set3 = Set(7, 8, 9)

    println(set3) //Set(7, 8, 9)

    //两个集合的交集
    val set4 = set1 & set2

    println(set4) //Set(5, 1, 6, 2, 7, 3, 4)

    //两个集合的并集
    val set5 = set1 ++ set2

    println(set5) //Set(5, 1, 6, 2, 7, 3, 8, 4)

    //在第一个set基础上去掉第二个set中存在的元素
    val set6 = set2 -- set3

    println(set6) //Set(5, 1, 6, 2, 3, 4)

    //返回第一个不同于第二个set的元素集合
    val set7 = set2 &~ set3

    println(set7) //Set(5, 1, 6, 2, 3, 4)


    //计算符合条件的元素个数
    val set8 = set1.count(_ > 5)

    println(set8) //2

    //返回第一个不同于第二个的元素集合
    val set9 = set2.diff(set3)

    println(set9) //Set(5, 1, 6, 2, 3, 4)


    //取子set(2,5为元素位置, 0开始,包含头不包含尾)
    val set10 = set1.slice(2, 5)

    println(set10) //Set(6, 2, 7)

    //遍历
    for(x <- set10) {

      println(x)
    }

    //迭代所有的子set,取指定的个数组合
    val set11 = set3.subsets(2).foreach(x => println(x))

    //Set(7, 8)
    //Set(7, 9)
    //Set(8, 9)
  }

}

 

●可变Set

package cn.itcast.collection

import scala.collection.mutable.Set

object SetDemo2 {
  def main(args: Array[String]): Unit = {
    //定义一个可变的Set
    val set1 = Set[Int]()

    println(set1)//Set()
    //添加元素
    set1 += 1

    println(set1)//Set(1)

    //添加元素  add
    val bool = set1.add(2)

    println(bool)//true
    println(set1)//Set(1, 2)

    //向集合中添加元素集合
    set1 ++= Set(1, 4, 5)

    println(set1)//Set(1, 5, 2, 4)

    //删除一个元素
    set1 -= 5

    println(set1)//Set(1, 2, 4)

    //删除一个元素
    set1.remove(1)

    println(set1)//Set(2, 4)
  }

}

 

(七)映射

1.语法详解

●说明

在Scala中,把哈希表这种数据结构叫做映射。类比Java的map集合

 

●不可变Map

import scala.collection.immutable.Map

格式一:使用箭头val/var map = Map(键->值, 键->值, 键->值...) // 推荐,可读性更好

格式二:利用元组val/var map = Map((键, 值), (键, 值), (键, 值), (键, 值)...)

 

●可变Map

import scala.collection.mutable.Map

格式相同,导包不同

 

●获取值

map(键)

map.getOrElse(键,默认值)

 

●修改值

map(键)=值

 

 

2.代码演示

●基本操作

package cn.itcast.collection

import scala.collection.mutable.Map

object MapDemo {
  def main(args: Array[String]): Unit = {
    //格式一:使用箭头val map=Map(->值,键->....)
    val scores = Map("tom" -> 85, "jerry" -> 99, "kitty" -> 90)

    println(scores)//Map(tom -> 85, kitty -> 90, jerry -> 99)
    //格式二:利用元组val map=Map((键,值),(键,值),(键,值)....)
    val scores2 = Map(("tom", 85), ("jerry", 99), ("kitty", 90))

    println(scores2)//Map(tom -> 85, kitty -> 90, jerry -> 99)

    //根据key获取值
    println(scores("jerry")) //99
    println(scores.getOrElse("jack", 0)) //0

    //如果是immutable包下的Map报错
    //如果是mutable包下的Map不抱错
    //添加值
    //val类似于final,引用不可变,但是内容可不可变由是否是可变类型决定
    scores += (("lily", 99))

    println(scores)//Map(lily -> 99, tom -> 85, kitty -> 90, jerry -> 99)

    //添加多个键值对
    scores += ("zhangsan" -> 30, "lisi" -> 20)

    println(scores)//Map(lisi -> 20, zhangsan -> 30, lily -> 99, tom -> 85, kitty -> 90, jerry -> 99)

    //更新
    scores("tom") = 88

    println(scores("tom"))//88
    //更新多个键值对
    scores += ("zhangsan" -> 60, "lisi" -> 50)

    println(scores)//Map(lisi -> 50, zhangsan -> 60, lily -> 99, tom -> 88, kitty -> 90, jerry -> 99)

    //删除key
    scores -= ("zhangsan")

    println(scores)//Map(lisi -> 50, lily -> 99, tom -> 88, kitty -> 90, jerry -> 99)

    //删除key
    scores.remove("lisi")

    println(scores)//Map(lily -> 99, tom -> 88, kitty -> 90, jerry -> 99)

    //val指的是变量不能被重新赋值
    //scores =  Map("jerry"->99,"kitty"->90)
    //可变不可变 指的是,内容或长度是否可变
  }

}

 

 

●遍历

package cn.itcast.collection

object MapDemo2 {
  def main(args: Array[String]): Unit = {
    val users = Map("zhangsan" -> 50, "lisi" -> 100)

    //通过key
    for (k <- users.keySet) println(k, users(k))

    for (k <- users.keys) println(k,users(k))
    println("=======================")

    //单独遍历value
    for (v <- users.values) println(v)

    println("=======================")

    //通过tuple
    for (t <- users) println(t._1,t._2)

    println("=======================")

    //模式匹配
    for ((k, v) <- users) println(k,v)

    println("=======================")

    //通过foreach和模式匹配
    users.foreach {

      case (k, v) => println(k,v)
    }
    println("=======================")

    //通过foreachtuple
    users.foreach(

      t => println(t._1,t._2)
    )
  }
}

 

(八)函数式编程

1.代码演示

 

遍历-foreach

过滤-filter

排序-sorted-sortBy-sortWith

映射-map

扁平化映射-flatMap

分组-groupBy

规约/聚合-reduce

折叠-fold

 

package cn.itcast.collection

/**
  * Author caowei
  * Date 2019/7/3 22:45
  * Desc
  */
object FunctionDemo {

  def main(args: Array[String]): Unit = {
    //定义一个集合
    val list = List(1,2,3,4,5,6,7,8,9)

    println("============遍历集合-foreach===========")
    for (i <- list) print(i)
    println("****************")
    val fun = (x:Int) => {print(x)}
    list.foreach(fun)
    println("****************")
    list.foreach((x:Int) => {print(x)})
    println("****************")
    list.foreach(x => print(x))
    println("****************")
    list.foreach(print(_))
    println("****************")
    list.foreach(print)
    println("****************")

    println("============过滤-filter===========")
    //过滤出list中的偶数
    list.filter(_%2 == 0).foreach(println)


    println("============排序-sorted-sortBy-sortWith===========")
    val list1 = List(3,4,1,7,8,9,2,5,6)
    list1.sorted.foreach(println)
    println("****************")
    list1.sortBy(x => x).foreach(println)
    println("****************")
    list1.sortWith(_<_).foreach(println)

    println("============映射-map=========")
    //list中的每一个元素*10
    list.map(_*10).foreach(println)


    println("============扁平化映射-flatMap=========")
    val lines = List("hadoop hive spark flink flume", "kudu hbase sqoop storm")
    //val linesArr: List[Array[String]] = lines.map(_.split(" "))
    //val words: List[String] = linesArr.flatten
    val words: List[String] = lines.flatMap(_.split(" "))

    words.foreach(println)

    println("============分组-groupBy=========")
    val students = List("张三"->"男", "李四"->"女", "王五"->"男")
    val genderAndList: Map[String, List[(String, String)]] = students.groupBy(_._2)
    val genderAndCount: Map[String, Int] = genderAndList.mapValues(_.length)
    genderAndCount.foreach(println)

    println("============规约/聚合-reduce=========")
    //val sum1: Int = list.reduce((agg,cur) => agg + cur)
    val sum1: Int = list.reduce(_+_)

    println(sum1)
    val sum2: Int = list.reduceLeft(_+_)
    println(sum2)
    val sum3: Int = list.reduceRight(_+_)
    println(sum3)

    println("============折叠-fold=========")
    val reuslt1: Int = list.fold(10)(_+_)
    println(reuslt1)
    val reuslt2: Int = list.foldLeft(10)(_+_)
    println(reuslt2)
    val reuslt3: Int = list.foldRight(10)(_+_)
    println(reuslt3)
  }
}

 

 

2.API详解-学生自行阅读

1)遍历-foreach

●说明

之前,我们学习过了使用for表达式来遍历集合。

我们接下来将学习scala的函数式编程,使用foreach方法来进行遍历、迭代。它可以让代码更加简洁。

●方法签名

foreach(f: (A) ⇒ Unit): Unit

foreach

API

说明

参数

f: (A) ⇒ Unit

接收一个函数对象,函数的输入参数为集合的元素,返回值为空

返回值

Unit

2)过滤-filter

●说明

过滤符合一定条件的元素

●方法签名
  def filter(p: (A) ⇒ Boolean): TraversableOnce[A]

filter方法

API

说明

参数

p: (A) ⇒ Boolean

传入一个函数对象,接收一个集合类型的参数,返回布尔类型,满足条件返回true, 不满足返回false

返回值

TraversableOnce[A]

列表

●图解

 

 

 

 

3)排序-sorted-sortBy-sortWith

●说明

在scala集合中,可以使用以下几种方式来进行排序

sorted默认排序

sortBy指定字段排序

sortWith自定义排序

 

1.默认排序 | sorted

定义一个列表,包含以下元素: 3, 1, 2, 9, 7

对列表进行升序排序

List(3,1,2,9,7).sorted

 

2.指定字段排序 | sortBy

●说明

根据传入的函数转换后,再进行排序

●方法签名
 def sortBy[B](f: (A) ⇒ B): List[A]

sortBy方法

API

说明

泛型

[B]

按照什么类型来进行排序

参数

f: (A) ⇒ B

传入函数对象<br />接收一个集合类型的元素参数<br />返回B类型的元素进行排序

返回值

List[A]

返回排序后的列表

●示例

有一个列表,分别包含几下文本行:"01 hadoop", "02 flume", "03 hive", "04 spark"

请按照单词字母进行排序

val a = List("01 hadoop", "02 flume", "03 hive", "04 spark")
  a.sortBy(_.split(" ")(1))//List(02 flume, 01 hadoop, 03 hive, 04 spark)

 

3.自定义排序 | sortWith

●说明

自定义排序,根据一个函数来进行自定义排序

●方法签名
 def sortWith(lt: (A, A) ⇒ Boolean): List[A]

sortWith方法

API

说明

参数

lt: (A, A) ⇒ Boolean

传入一个比较大小的函数对象,接收两个集合类型的元素参数,返回两个元素大小,小于返回true,大于返回false

返回值

List[A]

返回排序后的列表

●示例

有一个列表,包含以下元素:2,3,1,6,4,5

使用sortWith对列表进行降序排序
  val a = List(2,3,1,6,4,5)
val r = a.sortWith((x,y) => if(x<y)true else false)
   ​r.reverse//List(6, 5, 4, 3, 2, 1)
    //a.sortWith(_ < _).reverse

4)映射-map

●说明

集合的映射操作是将来在编写Spark/Flink用得最多的操作,是我们必须要掌握的。因为进行数据计算的时候,就是一个将一种数据类型转换为另外一种数据类型的过程

map方法接收一个函数,将这个函数应用到每一个元素,返回一个新的列表

●方法签名
  def map[B](f: (A) ⇒ B): TraversableOnce[B]

map方法

API

说明

泛型

[B]

指定map方法最终返回的集合泛型

参数

f: (A) ⇒ B

传入一个函数对象<br />该函数接收一个类型A(要转换的列表元素),返回值为类型B

返回值

TraversableOnce[B]

B类型的集合

●图解

 

 

 

 

5)扁平化映射-flatMap

●说明

扁平化映射也是将来用得非常多的操作,也是必须要掌握的。

以把flatMap,理解为先map,然后再flatten

 

●方法签名
  def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): TraversableOnce[B]

flatmap方法

API

说明

泛型

[B]

最终要转换的集合元素类型

参数

f: (A) ⇒ GenTraversableOnce[B]

传入一个函数对象,函数的参数是集合的元素,函数的返回值是一个集合

返回值

TraversableOnce[B]

B类型的集合

●图解

 

 

 

●需求

有一个包含了若干个文本行的列表:

"hadoop hive spark flink flume", "kudu hbase sqoop storm"

获取到文本行中的每一个单词,并将每一个单词都放到列表中

●先map操作,在进行flatten压扁

 

 

 

●参考代码
// 定义文本行列表
val a = List("hadoop hive spark flink flume", "kudu hbase sqoop storm")
// 使用map将文本行转换为单词数组
a.map(x=>x.split(" "))//List(Array(hadoop, hive, spark, flink, flume), Array(kudu, hbase, sqoop, storm))
//扁平化,将数组中的
a.map(x=>x.split(" ")).flatten//List(hadoop, hive, spark, flink, flume, kudu, hbase, sqoop, storm)

 

●使用flatMap简化操作

 

 

 

●参考代码
val a = List("hadoop hive spark flink flume", "kudu hbase sqoop storm")
a.flatMap(_.split(" "))//List(hadoop, hive, spark, flink, flume, kudu, hbase, sqoop, storm)

 

 

 

 

 

  

6)分组-groupBy

●说明

我们如果要将数据按照分组来进行统计分析,就需要使用到分组方法

groupBy表示按照函数将列表分成不同的组

●方法签名
def groupBy[K](f: (A) ⇒ K): Map[K, List[A]]

groupBy方法

API

说明

泛型

[K]

分组字段的类型

参数

f: (A) ⇒ K

传入一个函数对象,接收集合元素类型的参数,返回一个K类型的key,这个key会用来进行分组,相同的key放在一组中

返回值

Map[K, List[A]]

返回一个映射,K为分组字段,List为这个分组字段对应的一组数据

●图解

 

 

 

 

●案例

有一个列表,包含了学生的姓名和性别:
List("张三"->"男", "李四"->"女", "王五"->"男")

按照性别进行分组,统计不同性别的学生人数:

List(("男" -> 2), ("女" -> 1))

 

●参考代码
val a = List("张三"->"男", "李四"->"女", "王五"->"男")
val r1 =a.groupBy(_._2)
//Map[String,List[(String, String)]] = Map(男 -> List((张三,男), (王五,男)),女 -> List((李四,女)))
r1.map(x => x._1 -> x._2.size)
//Map[String,Int] = Map(男 -> 2, 女 -> 1)

 

 

7)规约/聚合-reduce

●说明

规约:将二元函数应用于集合中的函数/将集合,传入一个函数进行聚合计算

●方法签名
def reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1

reduce方法

API

说明

泛型

[A1 >: A]

(下界)A1必须是集合元素类型的子类

参数

op: (A1, A1) ⇒ A1

传入函数对象,用来不断进行聚合操作,第一个A1类型参数为:当前聚合后的变量,第二个A1类型参数为:当前要进行聚合的元素

返回值

A1

列表最终聚合为一个元素

●图解

 

 

 

●reduce、reduceLeft和reduceRight

reduce和reduceLeft效果一致,表示从左到右计算,reduceRight表示从右到左计算

.reduceLefft(_ - _)这个函数的执行逻辑如图所示:

val list = List(1, 2, 3, 4, 5)

val i1 = list.reduceLeft(_ - _)//-13

 

 

 

.reduceRight(_ - _)反之同理

 

●案例
val a = List(1,2,3,4,5,6,7,8,9,10)
a.reduce((x,y) => x + y)//55
// 第一个下划线表示第一个参数,就是历史的聚合数据结果
// 第二个下划线表示第二个参数,就是当前要聚合的数据元素
a.reduce(_ + _)//55
// 与reduce一样,从左往右计算
a.reduceLeft(_ + _)//55
// 从右往左聚合计算
a.reduceRight(_ + _)//55


 

8)折叠-fold

fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历

fold与reduce很像,但是多了一个指定初始值参数

foldLeft和foldRight有一种缩写方法对应分别是:/:和:

●方法签名
  def fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1

reduce方法

API

说明

泛型

[A1 >: A]

(下界)A1必须是集合元素类型的子类

参数1

z: A1

初始值

参数2

op: (A1, A1) ⇒ A1

传入函数对象,用来不断进行折叠操作<br />第一个A1类型参数为:当前折叠后的变量<br />第二个A1类型参数为:当前要进行折叠的元素

返回值

A1

列表最终折叠为一个元素

 

●fold和foldLet效果一致,表示从左往右计算,foldRight表示从右往左计算

 

●案例
val a = List(1,2,3,4,5,6,7,8,9,10)
a.fold(10)(_ + _)//65

 

 

六、综合练习-WordCount

(一)需求

使用Scala读取指定文件夹下的文件并完成WordCount

(二)准备知识-IO操作

package cn.itcast.review

import scala.io.Source

object FileDemo {
  def main(args: Array[String]): Unit = {
    //文件读取
    val file = Source.fromFile("D:\data\mr\wordcount\input\words.txt")

    val lines = file.getLines
    for (line <- lines) {
      println(line)
    }
    file.close

    //文件写入
    import java.io.{File, PrintWriter}

    val writer = new PrintWriter(new File("D:\data\mr\wordcount\output\result.txt"))
    for (i <- 1 to 100)
      writer.println(i)
    writer.close()
  }
}

 

 

(三)完成WordCount

●初级程序员版

package cn.itcast.review

import java.io.{File, PrintWriter}

import scala.io.Source
import scala.collection.mutable.Map

/**
  * 读取指定文件夹下的所有文件,并统计单词数量
  */
object WordCount1 {

  def main(args: Array[String]): Unit = {
    //0.准备一个Map用来存放单词和数量
    val map:Map[String,Int] = Map[String,Int]()

    //1.获取文件夹下的所有文件 Array<file>
    val dir:File = new File("D:\data\mr\wordcount\input")

    val files: Array[File] = dir.listFiles()
    //2.遍历并读取所有的文件,获得文件中的每一行
    files.foreach(file=>{

        val lines: Iterator[String] = Source.fromFile(file).getLines()
        //3.针对每一行," "切分出每一个单词
        val words: Iterator[String] = lines.flatMap(_.split(" "))

        //4.words进行遍历,取出每一个单词
        for (word <- words){

          //5.判断map中是否有这个单词,有则+1,没有则map.put(word,1)
          if(map.contains(word)){

            //map.put(word,map(word)+1)
            map(word)+=1

          }else{
            map.put(word,1)
          }

//map.put(word, map.getOrElse(word,0) + 1)
        }
      }
    )
    println(map)
    //6.把结果写入到文件中
    val writer:PrintWriter = new PrintWriter(new File("D:\data\mr\wordcount\output\result.txt"))

    for((k,v) <- map ){
      writer.println(k + " : " + v)
    }
    writer.close()
  }
}

 

●中级程序员版

import java.io.{File, PrintWriter}

import scala.io.Source

/**
  * 读取指定文件夹下的所有文件,并统计单词数量
  */
object WordCount2 {

  def main(args: Array[String]): Unit = {
    //1.使用JavaAPI获取指定文件夹下的所有文件
    val dir = new File("D:\data\mr\wordcount\input")

    val files: Array[File] = dir.listFiles()
    //2.循环遍历所有的文件
    //[(andy,2),(andy,2)....]
    val wordAndCountMaps: Array[Map[String, Int]] = files.map(file => {

      //3.读取每一个文件
      val words: Iterator[String] = Source.fromFile(file).getLines().flatMap(_.split(" "))

      //4.把每一个单词记为1
      //[(hadoop,1),(hadoop,1),(andy,1),(andy,1)...]
      val wordAndOnes: Iterator[(String, Int)] = words.map(word => (word, 1))

      //5.根据key进行分组
      //[(hadoop,[(hadoop,1),(hadoop,1)]),(andy,[(andy,1),(andy,1)])...]
      val wordAndList: Map[String, List[(String, Int)]] = wordAndOnes.toList.groupBy(_._1)

      val wordAndCount: Map[String, Int] = wordAndList.mapValues(_.length)
      wordAndCount
    })
    val wordAndCounts: Array[(String, Int)] = wordAndCountMaps.flatten
    val stringToTuples: Map[String, Array[(String, Int)]] = wordAndCounts.groupBy(_._1)
    //0表示初始值,1_表示上一次的值,2个下划线表示数组里面的元组, _._2表示元组中的第2个元素
    val result: Map[String, Int] = stringToTuples.mapValues(_.foldLeft(0)(_ + _._2))

    println(result)

    //循环遍历mapkv写入文件
    val writer = new PrintWriter(new File("D:\data\mr\wordcount\output\result.txt"))

    for((k,v)<-result){
      writer.println(k +":"+ v)
    }
    writer.close()
  }
}

 

 

七、Scala面向对象

(一)

Scala的面向对象(类、对象、继承、特质)与Java、C++类似,但是更加简洁和强大,学完之后你会更爱Scala!

 

1.类class

1)语法详解

●说明

在Scala中,类并不用声明为public类型的(默认就是public)

Scala源文件中可以包含多个类,所有这些类都具有共有可见性

调用无参方法时,可以加(),也可以不加;如果方法定义中不带括号,那么调用时就不能带括号

 

●扩展-访问权限

Java 中的访问控制权限,同样适用于 Scala,可以在成员前面添加private/protected关键字来控制成员的可见性。

但在scala中,没有public关键字,任何没有被标为private或protected的成员都是公共的

回忆:Java中的访问权限修饰符

关键字

类内部

本包

子类

外部包

public

protected

×

default

×

×

private

×

×

×

 

 

●扩展- @BeanProperty:

JavaBeans规范定义了Java的属性是像getXXX()和setXXX()的方法。许多Java工具都依赖这个命名习惯。

为了Java的互操作性。将Scala字段加上@BeanProperty,getter和setter方法会自动生成。

 

●扩展-自定义getter/setter(一般不使用):

如果自己手动创建变量的getter和setter方法需要遵循以下原则:

1) 字段属性名以_作为前缀,如:_leg

2) getter方法定义为:def leg = _leg

3) setter方法定义时,方法名为属性名去掉前缀,并加上后缀,后缀是:leg_=

 

 

2)代码演示

package cn.itcast.class_demo

import scala.beans.BeanProperty

//Scala中,类并不用声明为public类型的(默认就是public)
class Person{

  //val修饰的变量是可读属性,有getter但没有setter(相当与Java中用final修饰的变量)
  val id = "12345"


  //var修饰的变量都既有getter,又有setter
  var age: Int = 18


  //类私有字段,伴生对象可以访问
  private var name: String = "唐伯虎"


  //对象私有字段,访问权限更加严格的,Person类的方法只能访问到当前对象的字段
  //在伴生对象里面也不可以访问
  private[this] var pet = "小强"


  //字段加上@BeanPropertygettersetter方法会自动生成
  @BeanProperty
  var money:Double = _



  //定义无参方法可以带()也可以不带
  def m1() = println("m1")

  def m2 =  println("m2")

}

//Scala源文件中可以包含多个类,所有这些类都具有共有可见性
class Person2{}



//伴生对象(这个名字和类名相同,叫伴生对象,后面学习)
object Person {

  //main方法为程序入口,要写在object里面
  def main(args: Array[String]): Unit = {

    val p = new Person
    //p.id = "123"//报错,val类型的不支持重新赋值,但是可以获取到值
    println(p.id)

    println(p.age)

    //伴生对象中可以在访问private变量
    println(p.name)


    //字段加上@BeanPropertygettersetter方法会自动生成
    p.setMoney(100.0)

    println(p.getMoney)

    //调用无参方法时,可以加(),也可以不加;如果方法定义中不带括号,那么调用时就不能带括号
    p.m1()

    p.m1
    p.m2

    //由于pet字段用private[this]修饰,伴生对象中访问不到pet变量
    //p.pet//报错,访问不到
  }

}

 

 

 

2.构造器constructor

1)语法详解     阿善看到

●回忆Java

构造方法/构造器是用来创建并初始化对象的

public class Person {

    //构造方法/构造器用来创建并初始化对象

    public Person(String name,int age){

        //...

    }

    //构造方法/构造器用来创建并初始化对象

    public Person(){

        //...

    }

 

}

 

●主构造器和辅助构造器

Scala中分为主构造器和辅助构造器:

主构造器:每个类都有,与类交织在一起,主构造器的参数直接放置类名后面,且主构造器会执行类定义中的所有语句。

辅助构造器:需要调用主构造器或者其他辅助构造器

 

●扩展:构造器参数

构造器参数可以带或者不带val/var

如果带val/var的参数会被作为字段(一般带上)

如果不带val/var的参数,不会被作为字段,仅仅能被主构造器中的语句使用,或者被方法所使用,如果被方法使用了,那么它将会被提升为private[this]字段

 

●扩展:私有化主构造器

如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了

class Person private () {...}

 

2)代码演示

●主构造器和辅助构造器

package cn.itcast.class_demo

//主构造器:每个类都有,与类交织在一起,主构造器的参数直接放置类名后面
class Student(val name: String, var age: Int) {

  //主构造器会执行类定义的所有语句
  println("执行主构造器")

  private var gender = "male"

  //定义一个辅助构造器
  def this(name: String, age: Int, gender: String) {

    //每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
    this(name, age)

    println("执行辅助构造器")
    this.gender = gender
  }

  //又定义一个辅助构造器
  def this(name: String) {

    this(name, 30)
  }
}

object Student {
  def main(args: Array[String]): Unit = {
    val s1=new Student("zhangsan",20)
    //val s2 = new Student("zhangsan", 20, "female")
  }

}

 

 

●构造器参数

package cn.itcast.class_demo
//构造器参数可以带或者不带val/var
//如果带val/var的参数会被作为字段(一般带上)
//如果不带val/var的参数,不会被作为字段,仅仅能被主构造器中的语句使用,或者被方法所使用,
//如果被方法使用了,那么它将会被提升为private[this]字段
class Queen(val name: String, prop: Array[String], private var age: Int = 18) {

  //prop被下面的方法使用后,prop就变成了不可变的对象私有字段,等同于private[this] val prop
  //如果没有被方法使用该参数将不被保存为字段,仅仅是一个可以被主构造器中的代码访问的普通参数
  println(name+ prop + age)

  def description():String = {
    return name + " is " + age + " years old with " + prop.length
  }
}

object Queen {
  def main(args: Array[String]) {
    val q = new Queen("bigdata", Array("大数据", "人工智能"), 10)
    println(q.description())
  }
}

 

 

●总结

主构造器参数

生成的字段/方法

name: String

对象私有字段。如果没有方法使用name, 则没有该字段

private val/var name: String

私有字段,私有的getter和setter方法

var name: String

私有字段,公有的getter和setter方法

@BeanProperty val/var name: String

私有字段,公有的Scala版和Java版的getter和setter方法

 

(二)对象object

1.作为程序入口

1)语法详解

在 Scala 中和Java一样也必须要有一个 main 方法作为程序的入口,而且必须定义在 object 中

除了自己实现 main 方法之外,还可以通过继承 App Trait(类似接口)来实现

1.编写object继承App

2.将需要写在 main 方法中运行的代码,直接作为 object 的 constructor 代码

3.使用 args 接收传入的参数

2)代码演示

package cn.itcast.object_demo

//1.object中定义main方法
object Main_Demo1 {

  def main(args: Array[String]):Unit= {
    if(args.length > 0){
      println("Hello, " + args(0))
    }else{
      println("Hello World1!")
    }
  }
}
//2.使用继承App Trait ,将需要写在 main 方法中运行的代码
// 直接作为 object  constructor 代码即可,
// 而且也可以使用 args 接收传入的参数。
object Main_Demo2 extends App{

    if(args.length > 0){
      println("Hello, " + args(0))
    }else{
      println("Hello World2!")
    }
}

 

2.作为工具类

1)语法详解

在Scala中没有静态方法和静态字段,但是可以使用object来达到同样的目的。可以类比与Java中的工具类

2)代码演示

package cn.itcast.object_demo

import java.text.SimpleDateFormat
import java.util.Date

object DateUtils {

  // 相当于Java中的静态变量
  val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")


  // 相当于Java中的静态方法
  def format(date: Date) = simpleDateFormat.format(date)

}

object Test{
  // main是一个静态方法,所以必须要写在object
  def main(args: Array[String]): Unit = {

    val str: String = DateUtils.format(new Date())
    println(str)
  }
}

 

 

3.作为伴生对象-和伴生类的相互访问

1)语法详解

●类比Java

在Java中,经常会有一些类,同时有实例成员又有静态成员。例如:

public class CustomerService {
    private static String SERVICE_NAME = "CustomerService";
    public void save() {
        // 保存客户
        System.out.println(SERVICE_NAME + ":保存客户");
  }
    public static void main(String[] args) {
        new CustomerService().save();
  }
}

 

●Scala中的伴生对象

在scala中,要实现类似的效果,可以使用伴生对象来实现。

一个class和object具有同样的名字。这个object称为伴生对象,这个class称为伴生类

- 伴生对象必须要和伴生类一样的名字

- 伴生对象和伴生类在同一个scala源文件中

- 伴生对象和伴生类最大特点是,可以相互访问,包括private属性

注意:如果某个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问

 

2)代码演示

package cn.itcast.object_demo

/**
  * Author caowei
  * Date 2019/7/4 12:35
  * Desc
  */

class CustomerService {

  private val money = 998

  private[this] val id = 111;

  //伴生类中访问伴生对象的私有属性
  def service() = {

    println(CustomerService.service_name)
  }
}

object CustomerService {
  private val service_name = "全套品质服务"

  def main(args: Array[String]): Unit = {
    val cs = new CustomerService()
    cs.service()
    //伴生对象中访问伴生类的私有属性
    println(cs.money)

    //注意:如果某个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问
    //println(cs.id)
  }

}

 

 

 

4.apply和updata方法

1)语法详解

●apply方法

我们之前使用过这种方式来创建一个Array对象:val a = Array(1,2,3,4)

这种写法非常简便,可以不使用new class/class() 的方式,而是直接使用 class()就可以创建对象

它的底层其实是隐式的调用了伴生对象的 apply 方法

 

●update方法

另外在进行数组赋值的时候,之所以没有采用Java中的方括号myStrArr[0],而是采用圆括号的形式,myStrArr(0),是因为存在Array伴生对象中存在update方法。

 

●注意

伴生对象名.apply   ==> 执行object中的apply方法

实例对象.apply   ==> 执行class中的apply方法

 

2)代码演示

●源码中的apply方法和update方法

package cn.itcast.object_demo

object ApplyAndUpdateDemo{
  def main(args: Array[String]): Unit = {
    //1.调用apply方法时可以省略方法名。apply方法用于构造对象和获取元素:
    //1.1 apply方法构造对象
    //Array(1,2,3) 等价于 Array.apply(1,2,3),
    val arr1 = Array(1,2,3)

    println(arr1.mkString(","))
    val arr2 = Array.apply(1,2,3)
    println(arr2.mkString(","))
    println("=================")

    //1.2 apply方法获取元素
    //arr1(0) 等价于 arr1.apply(0)
    val i1: Int = arr1(0)

    println(i1)
    val i2: Int = arr1.apply(0)
    println(i2)
    println("=================")

    //2.调用update方法时也可以省略方法名,update方法用于元素的更新
    //arr1(1) = 4等同于arr2.update(1,4)
    arr1(1) = 4

    println(arr1.mkString(","))
    arr2.update(1,4)
    println(arr2.mkString(","))
  }
}

 

 

●自己在obejct中定义apply方法

package cn.itcast.object_demo

class Car(name: String) {
  println("主构造器执行了")

  def info() {
    println("Car name is " + name)
  }

  def apply(name: String) = {
    println("class中的apply方法执行了")
    new Car(name)
  }
}

object Car {
  def apply(name: String) = {
    println("object中的apply方法执行了")
    new Car(name) //apply方法会调用伴生类Car的构造方法创建一个Car类的实例化对象
  }

}

object MyTest {
  def main(args: Array[String]) {
    val mycar1 = Car("BMW")//object中的apply方法执行了
    val mycar2 = Car.apply("BMW")//object中的apply方法执行了
    mycar1.info()

    mycar2.info()

    mycar1.apply("奔驰")//class中的apply方法执行了
  }

}

 

 

(三)继承extends

1.extends、override、super

1)语法详解

●extends

Scala 中的继承与 Java 一样也是使用 extends 关键字;

子类可以继承或覆盖父类的 field 和 method ,也可以实现子类特有的 field 和method

 

●override

Scala 中,如果子类要覆盖父类中的字段或方法,要使用 override 关键字;

 

●super

在子类中要调用父类中被覆盖的方法,要使用 super 关键字,显示的指出要调用的父类方法。

 

●注意

final修饰的类不能被继承

final修饰的字段或方法不能被覆盖

private 修饰的 field 和 method 不可以被子类继承

val字段使用 override 关键字覆盖

var字段使用override 关键字重新赋值(注意不是重新定义)

override 关键字可以帮助开发者尽早的发现代码中的错误,如方法名拼写错误,参数错误等等,所以建议覆盖时加上override关键字;

 

2)代码演示

 

package cn.itcast.extends_demo

class Person1 {
  val name = "super"

  var age = 18

  def sayName = {
    println("Person1--sayName")
  }

  def sayHello={
    println("hello")
  }
}

class Student1 extends Person1 {
  //val变量使用override关键字覆盖
  override
  val name = "sub"


  //var变量子类可以重新赋值
  age = 18


  //子类可以使用override关键字覆盖父类方法
  override
  def sayName = {

    //使用super表示调用父类的
    super.sayName

    println("Student1--sayName")
  }

  //子类可以定义自己的field
  val score = "A"

  //子类可以自己的method
  def printScore = {

    println(this.score)
  }

}

object Test1 {
  def main(args: Array[String]): Unit = {
    val s = new Student1
    s.sayHello
    s.printScore
    s.sayName
  }
}

 

 

3)扩展:protected

●说明

了解即可,一般不用

和Java一样Scala 中也可以使用 protected 关键字来修饰 field 和 method。

但是比Java要更严格一点:只有继承关系才可以访问,同一个包下,也是不可以的

在子类中,可直接访问父类protected 修饰的 field 和 method,而不需要使用 super 关键字;

还可以使用 protected[this] 关键字, 只允许在当前类中访问父类的 field 和 method,不允许通过其他子类对象访问父类的 field 和 method。

●代码示例

package cn.itcast.extends_demo

class Person2 {
  protected var name: String = "tom"
  //protected[this]子类的伴生对象访问不到
  protected[this] var hobby: String = "game"


  def sayBye:Unit = println("再见..." + hobby)
}

class Student2 extends Person2 {
  //父类使用protected 关键字来修饰 field可以直接访问
  def sayHello:Unit  = println("Hello " + name)


  //父类使用protected 关键字来修饰method可以直接访问
  def sayByeBye:Unit  = sayBye


  def makeFriends(s: Student2) = {
    println("My hobby is " + hobby + ", your hobby is UnKnown")
  }
}

object Student2 {
  def main(args: Array[String]) {
    val s: Student2 = new Student2
    s.sayHello
    s.makeFriends(s)
    s.sayByeBye
    //s.hobby//错误,子类的对象无法访问
  }

}

 

2.类型判断、转换、获取

1)语法详解

●isInstanceOf 和 asInstanceOf

obj.isInstanceOf[XX类名]

判断 obj 是否为 XX 类型的实例(判断对象是否为指定类以及其子类的实例)

类似于Java中的: obj instanceof  xx

obj.asInstanceOf[XX类名]

把 obj 转换成 XX 类型的实例

类似于Java中的:  (XX)obj

 

●getClass 和 classOf

obj.getClass

可以精确地获取对象的类型

就是Java中的: 对象.getClass

classOf[XX类名]

可以精确的获取类的类型

类似于Java中的: 类名.class

 

●注意:

如果对象是 null,

isInstanceOf 一定返回 false,

asInstanceOf 一定返回 null;

 

●Scala与Java类型检查和转换类比

Scala

Java

obj.isInstanceOf[XX类名]

obj instanceof XX类名

obj.asInstanceOf[XX类名]

(XX类名)obj

obj.getClass

obj.getClass

classOf[XX类名]

XX类名.class

 

2)代码演示

package cn.itcast.extends_demo

class Person3 {}

class Student3 extends Person3

object Student3 {
  def main(args: Array[String]) {
    val p: Person3 = new Student3
    var s: Student3 = null
    //如果对象是 null
    //isInstanceOf 一定返回 false
    //asInstanceOf 一定返回 null
    println(s.isInstanceOf[Student3])//false
    println(s.asInstanceOf[Student3])//null

    //获取p的真实类型并判断
    println(p.getClass == classOf[Student3])//true
    println(p.getClass == classOf[Person3])// false

    // 判断 p 是否为 Student3 对象的实例
    if (p.isInstanceOf[Student3]) {

      println(true)
      //p转换成 Student3 对象的实例并赋给s
      s = p.asInstanceOf[Student3]

    }
    println(s.isInstanceOf[Student3])//true
  }

}

 

3)扩展:使用模式匹配进行类型判断

●说明

在使用模式匹配进行类型判断(后面会讲模式匹配)

功能上来说,与 isInstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断

语法上来说,类似于 Java 中的 switch case

实际的开发中,比如 spark 源码中,大量的使用了模式匹配进行类型判断,这种方式更加简洁明了、可维护、可扩展

●代码示例

package cn.itcast.extends_demo

class Person4 {}

class Student4 extends Person4

object Student4{
  def main(args: Array[String]) {
    val p: Person4 = new Student4
    //下面的模式匹配的代码相当于使用了isInstanceOf
    println(p.isInstanceOf[Person4])

    p match {
      // 匹配是否为Person类或其子类
      case per: Person4 => println("This is a Person4's Object!")

      // 匹配所有剩余情况
      case _ => println("Unknown type!")

    }
  }
}

 

 

3.调用父类的构造器

1)语法详解

Scala中每个类都可以有一个主构造器和任意多个辅助构造器,

辅助构造器的第一行都必须调用其他辅助构造器或者主构造器代码;

子类的辅助构造器是一定不可能直接调用父类的构造器的,只能在子类的主构造器中调用父类的构造器。

父类的构造函数已经定义过的字段,子类在使用时,不需要使用 val/var 来修饰,否则会被认为,子类要覆盖父类的字段

2)代码演示

 

package cn.itcast.extends_demo

class Person5(val name: String, val age: Int) {
  println("父类的主构造器")
  var score: Double = 0.0
  var address: String = "beijing"

  def this(name: String, score: Double) = {
    //每个辅助构造器的第一行都必须调用其他辅助构造器或者主构造器代码
    //constructor代码
    this(name, 30)

    println("父类的第一个辅助构造器")
    this.score = score
  }

  //其他辅助构造器
  def this(name: String, address: String) = {

    this(name, 100.0)
    println("父类的第二个辅助构造器")
    this.address = address
  }
}

class Student5(name: String, score: Double) extends Person5(name, score){
  println("子类的主构造器")
}

object Test5{
  def main(args: Array[String]): Unit = {
    val s = new Student5("jack",100.0)
  }
}

 

4.抽象类

1)语法详解

●抽象类、抽象方法和抽象字段

一个类中,如果含有一个抽象方法或抽象字段,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的

如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法

如果在父类中,定义了字段,但是没有给出初始值,则此字段为抽象字段

 

●注意

在子类中覆盖抽象类的抽象方法时,可以不加override关键字,但建议加上;重写父类的非抽象方法,必须使用override关键字

抽象类中可以有普通字段和方法

 

2)代码演示

 

 

package cn.itcast.extends_demo

// 定义一个抽象类-形状类
abstract class Shape {

  //定义一个抽象字段
  val name: String


  //定义一个抽象方法,
  //不需要关键字abstract,只要把方法体空着,不写方法体就可以
  //必须指出返回类型,不然默认返回为Unit
  def area: Double

}

// 创建正方形类
class Square(var edge: Double) extends Shape {

  override val name: String = "Square"

  override //可以省略override,但不建议
  def area: Double = edge * edge

}

// 创建长方形类
class Rectangle(var length: Double, var Double) extends Shape {

  override val name: String = "Rectangle"

  override def area: Double = length * width
}

// 创建圆形类
class Cirle(var radius: Double) extends Shape {

  override val name: String = "Cirle"

  override def area: Double = Math.PI * radius * radius
}


object Test6 {
  def main(args: Array[String]): Unit = {
    val s1: Shape = new Square(2)
    val s2: Shape = new Rectangle(2, 3)
    val s3: Shape = new Cirle(2)
    println(s1.name)
    println(s2.name)
    println(s3.name)
    println(s1.area)
    println(s2.area)
    println(s3.area)
  }
}

 

 

 

3)扩展:匿名内部类

●说明

在Scala中匿名内部类是很常见的,Spark的源码中就大量的使用了匿名内部类;

匿名内部类,就是定义一个没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量

通常还会将将该匿名内部类对象传递给其他方法或函数使用。

●代码示例

 

package cn.itcast.extends_demo

abstract class Person6(val name: String) {
  def sayHello:String
}

class GreetDemo {
  //接受Person类型的对象作为参数,并可以规定对象中只含有一个返回StringsayHello方法
  def greeting(p: Person6 {def sayHello: String} ) = {

    //greeting方法中调用参数psayHello方法
    println(p.sayHello)

  }
}

object GreetDemo {
  def main(args: Array[String]) {
    //创建Person的匿名内部类对象并赋值给变量p
    val p = new Person6("tom") {

      override
      def sayHello:String= "bye! " + this.name

    }
    val g = new GreetDemo
    g.greeting(p)
  }
}

 

 

(四)特质trait

1.trait作为接口封装字段、方法

1)语法详解

●trait作为接口

Scala中的trait是一种特殊的概念,与Java中的接口 (interface)非常类似

(Java8中接口的新特性:默认方法、静态方法就抄袭Scala的!)

 

★注意:

类可以使用extends关键字继承trait

注意不是 implement,而是extends ,在Scala中没有 implement 的概念,无论继承类还是trait,统一都是 extends

Scala和Java一样不支持多继承类,但是支持多重继承 trait,使用 with 关键字即可

 

●在trait中定义抽象方法

在trait中可以定义抽象方法,类继承后,必须实现抽象方法,可以不使用 override 关键字,建议加上;

 

●在trait中定义抽象字段

在trait中trait也能定义抽象字段,继承trait的类,则必须覆盖抽象字段,提供具体的值

 

●在trait中定义具体方法

在trait中不仅可以定义抽象方法,还可以定义具体的方法

 

●在trait中定义具体字段

在trait中可以定义具体的字段,此时继承 trait 的子类就自动获得了 trait 中定义的字段

但是这种获取字段的方式与继承 class 的是不同的

如果是继承 class 获取的字段,实际上还是定义在父类中的

如果是继承 trait获取的字段,就直接被添加到子类中了

2)代码演示

package cn.itcast.triat_demo

trait HelloTrait {
  //抽象方法
  def sayHello(): Unit


  //具体方法
  //一般为子类都通用的方法,例如打印日志或其他工具方法等
  //spark就使用trait定义了通用的日志打印方法;
  def log(message: String): Unit = println(message)


  //抽象字段
  val id:Long


  //具体字段
  val age:Int = 18


}

trait MakeFriendsTrait {
  def makeFriends(c: Children): Unit
}

//继承 trait使用extends关键字,多重继承使用with关键字
class Children(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable {

  override
  def sayHello() = println("Hello, " + this.name)


  //子类中可以直接访问trait中的具体字段
  def makeFriends(c: Children) = println(s"Hello, my name is ${this.name }, my age is ${this.age}, your name is ${c.name}")


  //覆盖抽象字段
  override val id: Long = 1

}

object Children {
  def main(args: Array[String]) {
    val c1 = new Children("tom")
    val c2 = new Children("jim")
    c1.sayHello() //Hello, tom
    c1.makeFriends(c2) //Hello, my name is tom, your name is jim
    c1.log("logging..........")

    println(c1.id)
    println(c1.name)
  }
}

 

 

2.trait继承class

1)语法详解

trait也可以继承class且会将class中的成员都继承下来

2)代码演示

package cn.itcast.triat_demo

class MyUtil {
  def printMsg(msg: String): Unit = println(msg)
}

trait Logger_Two extends MyUtil {
  //trait继承了class且会将class中的成员都继承下来
  def log(msg: String): Unit = this.printMsg("log: " + msg)

}

class Person(val name: String) extends Logger_Two {
  def sayHello {
    this.log("Hi, I'm " + this.name)
    this.printMsg("Hello, I'm " + this.name)
  }
}

object Person {
  def main(args: Array[String]) {
    val p = new Person("Tom")
    p.sayHello
    // 执行结果:
    // log: Hi, I'm Tom
    // Hello, I'm Tom
  }

}

 

 

3.对象混入trait

1)语法详解

在实例化对象时可以使用 with 关键字混入某个trait,这样就可以为该对象动态的添加额外的功能

val/var 对象名 = new 类 with 特质

2)代码演示

package cn.itcast.triat_demo

/**
  * Author caowei
  * Date 2019/7/4 20:07
  * Desc
  */
object ObjectWithTraitDemo {

  def main(args: Array[String]): Unit = {
    val service = new UserService with Printer
    service.myPrint("add")
  }
}

class UserService

trait Printer {

  def myPrint(msg:String) = println("trait:" +msg)
}

 

 

4.trait的构造机制

1)语法详解

trait也是有构造代码的,即在trait中,不包含在任何方法中的代码

trait不能有构造器参数,每个trait都有一个无参数的构造器(缺少构造器参数是trait与类之间主要的差别)

继承了trait的子类,其构造机制如下:

-父类的构造函数先执行,即 class 类必须放在最左边;

-trait的构造代码后执行,多个trait从左向右依次执行;

-构造trait时,先构造父 trait,如果多个trait继承同一个父trait,则父trait只会构造一次;

-所有trait构造完毕之后,子类的构造函数最后执行。

2)代码演示

package cn.itcast.triat_demo

class Plane {
  println("Plane's constructor!")
}

trait Fly {
  println("Fly's constructor!")
}

trait LowFly extends Fly {
  println("LowFly's constructor!")
}

trait HighFly extends Fly {
  println("HighFly's contructor!")
}

class Boeing extends Plane with LowFly with HighFly {
  println("Boeing's constructor!")
}

object Test {
  def main(args: Array[String]): Unit = {
    val b = new Boeing
    // 执行结果为:
    //Plane's constructor!
    //Fly's constructor!
    //LowFly's constructor!
    //HighFly's contructor!
    //Boeing's constructor!
  }

}

 

5.扩展:模版方法设计模式:混合使用抽象方法-具体方法

1)语法详解

在 trait 中,可以混合使用具体方法和抽象方法;

可以让具体方法依赖于抽象方法,而抽象方法则可放到继承 trait的子类中去实现

这种 trait 特性,其实就是设计模式中的模板设计模式的体现;

2)代码演示

 

package cn.itcast.triat_demo

/**
  * Author caowei
  * Date 2019/7/4 19:40
  * Desc
  */
object TemplateMethodDemo {

  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger
    logger.info("信息日志")
    logger.warn("警告日志")
    logger.error("错误日志")
  }
}
trait Logger {
  //抽象方法
  def log(msg:String)

  //具体方法
  def info(msg:String) = log("INFO:" + msg)

  //具体方法
  def warn(msg:String) = log("WARN:" + msg)

  //具体方法
  def error(msg:String) = log("ERROR:" + msg)

}

class ConsoleLogger extends Logger {
  override def log(msg: String): Unit = {
    println(msg)
  }
}

 

 

6.扩展:责任链设计模式:trait 调用链

1)语法详解

类在继承多个trait后,可依次调用多个trait中的同一个方法,在方法体最后执行 super.方法名即可

调用多个trait中都有的方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条;

这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现

2)代码演示

 

package cn.itcast.triat_demo

trait HandlerTrait {
  def handle(data: String) = {
    println("last one")
  }
}
trait SignatureValidHandlerTrait extends HandlerTrait {
  override
  def handle(data: String) = {

    println("check signature: " + data)
    super.handle(data)
  }
}

trait DataValidHandlerTrait extends HandlerTrait {
  override
  def handle(data: String) = {

    println("check data: " + data)
    super.handle(data)
  }
}



class PersonForRespLine(val name: String) extends DataValidHandlerTrait with SignatureValidHandlerTrait {
  def sayHello = {
    println("Hello, " + this.name)
    this.handle(this.name)
  }
}

object PersonForRespLine {
  def main(args: Array[String]) {
    val p = new PersonForRespLine("tom")
    p.sayHello
    // 执行结果:
    // Hello, tom
    // check signature: tom
    // check data: tom
    // last one
  }

}

 

 

 

 

八、Scala高级特性

(一)样例类和模式匹配

1.样例类

1)语法详解

●说明

在Scala中样例类是一中特殊的类,一般用于模式匹配和数据封装(类似于Java中的pojo类)

case class是多例的,后面要跟构造参数,

case object是单例的.

当你声明样例类时,如下几件事情会自动发生:

-构造器中的每一个参数都成为val,除非它被显式地声明为var(不建议这样做)

-在伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象

-提供unapply方法让模式匹配可以工作

-将生成toString、equals、hashCode和copy方法

除上述外,样例类和其他类型完全一样。你可以添加方法和字段,扩展它们。

 

●定义格式

    case class 样例类名([var/val] 成员变量名1:类型1, 成员变量名2:类型2, 成员变量名3:类型3)

 

2)代码演示

package cn.itcast.case_demo

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    val p1 = Person("张三", 20)
    val p2 = Person("张三", 20)
    println(p1)
    println(p1.toString)
    println(p1.hashCode())
    println(p1.copy("李四",18))
    println(p1.equals(p2))
  }
}
case class Person(name: String, age: Int)

 

 

 

3)扩展:样例类模拟枚举

样例类可以模拟出枚举类型

package cn.itcast.case_demo

object EnumDemo {
  def main(args: Array[String]): Unit = {
    for (color <- Array(Red, Yellow, Green))
      println(
        //模式匹配
        color match {

          case Red => "stop"
          case Yellow => "slowly"
          case Green => "go"
        })
  }
}
abstract class TrafficLightColor
case object Red extends TrafficLightColor

case object Yellow extends TrafficLightColor
case object Green extends TrafficLightColor

 

 

2.模式匹配-各种花式匹配

1)语法详解

●说明

java中有switch-case语句,但是,只能按顺序匹配简单的数据类型和表达式(支持:byte、short、int、char、String、枚举,不支持long)

相对而言,Scala中的模式匹配math-case的功能则要强大得多,几乎可以在match中使用任何类型、应用到更多场合

并且Scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配。

 

●注意

case语句中不需要使用break语句

case_ 相当于Java中的default

 

2)代码演示

package cn.itcast.case_demo

import scala.util.Random

/**
  * Author caowei
  * Date 2019/7/4 22:19
  * Desc
  */
object CaseDemo {

  def main(args: Array[String]): Unit = {
    //1.匹配字符串
    println("***1.匹配字符串***")

    val arr1 = Array("hadoop", "zookeeper", "spark", "storm")
    val name = arr1(Random.nextInt(arr1.length))
    println(name)
    name match {
      case "hadoop" => println("大数据分布式存储和计算框架...")
      case "zookeeper" => println("大数据分布式协调服务框架...")
      case "spark" => println("大数据分布式内存计算框架...")
      case _ => println("我不认识你...")
    }

    //2.匹配类型
    println("***2.匹配类型***")

    //这样做的意义在于避免了使用isInstanceOfasInstanceOf方法
    val arr2 = Array("hello", 1, 2.0, CaseDemo)

    val value = arr2(Random.nextInt(4))
    println(value)
    value match {
      case x: Int => println("Int " + x)
      //模式匹配的时候还可以添加守卫条件
      case y: Double if (y >= 0) => println("Double " + y)

      case z: String => println("String " + z)
      case a: CaseDemo.type => println("CaseDemo " + a)
      case _ => throw new Exception("not match exception")
    }

    //3.匹配集合
    println("***3.匹配集合***")

    val arr3 = Array(1, 3, 5)
    arr3 match {
      case Array(1, x, y) => println(x + " " + y) //3 5
      case Array(0) => println("only 0")

      case Array(0, _*) => println("0 ...")
      case _ => println("something else")
    }

    val li = List(3, -1)
    li match {
      case 0 :: Nil => println("only 0")
      case x :: y :: Nil => println(s"x: $x y: $y") //x: 3 y: -1
      case 0 :: tail => println("0 ...")

      case _ => println("something else")
    }

    val tup = (1, 3, 7)
    tup match {
      case (1, x, y) => println(s"1, $x , $y") //1, 3 , 7
      case (_, z, 5) => println(z)

      case _ => println("else")
    }

    //4.变量声明中隐藏的模式匹配
    println("***4.变量声明中隐藏的模式匹配***")

    val (x, y) = (1, 2)
    println(x) //1
    println(y) //2
    val (q, r) = BigInt(10) /% 3

    println(q) //3
    println(r) //1
    val arr4 = Array(1, 7, 2, 9)

    val Array(first, second, _*) = arr4
    println(first, second) //(1,7)

    //5.for循环中隐藏的模式匹配
    println("***5.for循环中隐藏的模式匹配***")

    val kvs = Map("k1" -> "v1", "k2" -> "v2", "k3" -> "v3")
    //val map = Map(("k1","v1"),("k2","v2"),("k3","v3"))
    for ((k, v) <- kvs) {

      println(k + ":" + v)
    }

    //6.函数式操作中的模式匹配
    println("***6.函数式操作中的模式匹配***")

    kvs.foreach {
      case (k, v) => println(k, v)
    }

    //7.匹配样例类
    println("***7.匹配样例类***")

    val arr = Array(CheckTimeOutTask, HeartBeat(10), SubmitTask("001", "task-001"), StopTask)
    val obj =arr(Random.nextInt(arr.length))
    println(obj)
    obj match {
      case SubmitTask(id, name) => println(s"$id, $name")
      case HeartBeat(time) => println(time)
      case CheckTimeOutTask =>println("check")
      case StopTask => println("stop")
    }
  }
}

case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask
case object StopTask

 

3)补充:Option类型

在Scala中为单个值提供了对象的包装器,Option类型用样例类来表示可能存在或也可能不存在的值

Option的子类有Some和None,Some包装了某个值,None表示没有值

通过Option的使用,避免了使用null、空字符串等方式来表示缺少某个值的做法

package cn.itcast.case_demo

object OptionDemo {
  def main(args: Array[String]) {
    val map = Map("a" -> 1, "b" -> 2)

    //使用模式匹配:map中根据k获取v,如果获取到了直接返回,没获取到返回默认值0
    val v = map.get("c") match {

      //Option是一个装有1个或0个元素的容器
      //SomeNone都是Option的子类
      case Some(i) => i //Some里面有1个元素
      case None => 0 //None里面有0个元素
    }

    println(v)

    //使用getOrElse
    val v2: Int = map.getOrElse("a",0)

    println(v2)
  }
}

 

4)补充:异常处理

●说明

受检异常在编译期被检查,在Java中必须在方法上声明该方法可能会发生的受检异常

Scala的异常的工作机制和Java一样,但是Scala没有checked受检异常,你不需要声明函数或者方法可能会抛出某种异常。

 

●抛出异常:

用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。

throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。

 

●捕捉异常: 阿善看到

在catch的代码里,使用一系列case子句(借用了模式匹配的思想来做异常的匹配)

异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。

如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。

finally字句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作。

 

●代码示例

package cn.itcast.case_demo

object ExceptionDemo {
  def main(args: Array[String]): Unit = {
    try {
      println(divider(10, 0))
    } catch {
      case ex: Exception => println("捕获了异常:" + ex)
    } finally {
      println("释放资源")
    }
    println("处理了异常,代码继续往下执行")
  }

  def divider(x: Int, y: Int): Float = {
    if (y == 0) throw new Exception("0作为了除数")
    else x / y
  }
}

 

5)扩展:偏函数

被包在花括号内没有match的一组case语句是一个偏函数

偏函数是PartialFunction[A, B]的一个实例,A代表参数类型,B代表返回类型,常用作输入模式匹配

偏函数最大的特点就是它只接受和处理其参数定义域的一个子集

package cn.itcast.case_demo

object PartialFuncDemo {
  //方法
  def func1(num: String): Int ={

    num match {
      case "one" => 1
      case "two" => 2
      case _ => -1
    }
  }
  //偏函数
  val func2: PartialFunction[String, Int] = {

    case "one" => 1
    case "two" => 2
    case _ => -1
  }

  def main(args: Array[String]) {
    //1.调用方法
    println(func1("one"))


    //2.调用偏函数
    println(func2("one"))


    val list = List(1,6,8)
    //3.传入偏函数
    val list2 = list.map{

      case x if x >= 1 && x <= 5 => "工作日"
      case x if x >= 6 && x <= 7 => "休息日"
      case x if x > 7 => "不认识"
    }
    println(list2)
  }
}

 

 

6)扩展:提取器

之前我们学习过了,实现一个类的伴生对象中的apply方法,可以用类名来快速构建一个对象

伴生对象中,还有一个unapply方法。与apply相反,unapply是将该类的对象,拆解为一个个的元素,常用于模式匹配

样例类中自动提供apply方法和unapply方法

 

class Student(var name: String, var age: Int)

object Student {
  def apply(name: String, age: Int) = {
    println("apply方法被调用")
    new Student(name, age)
  }

  def unapply(s: Student) = {
    println("unapply方法被调用")
    if(s != null ) {
      Some(s.name, s.age)
    }
    else {
      None
    }

  }
}

object TestStudent {
  def main(args: Array[String]): Unit = {
    //调用apply
    val s = Student("张三", 20)


    //调用unapply
    s match {

      case Student(name, age) => println(s"${name} => ${age}")
    }
  }
}

 

 

(二)Scala泛型

1.泛型方法-泛型类

1)语法详解

在scala中,使用方括号来定义类型参数

●泛型方法

def 方法名[泛型名称](..) = {

    //...

}

●泛型类

class 类[T](val 变量名: T)

2)代码演示

package cn.itcast.generic

/**
  * Author caowei
  * Date 2019/7/5 10:38
  * Desc
  */
object GenericDemo {

  //普通方法
  def getMiddle(arr:Array[Int]) = arr(arr.length / 2)


  //泛型方法
  def getMiddleElement[T](arr:Array[T]) = arr(arr.length / 2)


  def main(args: Array[String]): Unit = {
    println(getMiddle(Array(1,2,3,4,5)))
    println(getMiddleElement(Array(1, 2, 3, 4, 5)))
    println(getMiddleElement(Array("a", "b", "c", "d", "e")))

    val pairList = List(
      Pair("Hadoop", "Storm"),
      Pair("Hadoop", 2019),
      Pair(1.0, 2.0),
      Pair("Hadoop", Some(3.0))
    )
    println(pairList)
  }
}
//定义一个带有泛型的样例类
case class Pair[A,B](var a:A, var b:B)

 

 

 

2.协变、逆变、非变

1)语法详解

●引入

由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?

Java中这种情况下是不可泛化的.

在Java中,如果有 A是 B的子类,但 Card[A] 却不是 Card[B] 的子类

而在Scala 中只要灵活使用协变与逆变,就可以解决此类 Java 泛型问题;且Scala提供了三个选择,即协变(+)、逆变(-)和非变。

Scala 中的协变和逆变主要是用来解决参数化类型的泛化问题,解决了Java中泛型的一大缺憾

 

●协变(+)、逆变(-)和非变

协变:有子父关系的类型,作为泛型时,所属的类也有子父关系

C[+T]:如果A是B的子类,那么C[A]是C[B]的子类。

逆变:有子父关系的类型,作为泛型时,所属的类的子父关系颠倒了

C[-T]:如果A是B的子类,那么C[A]是C[B]的父类。

非变:有子父关系的类型,作为泛型时,所属的类的无子父关系

C[T]: 无论A和B是什么关系,C[A]和C[B]没有从属关系。

 

了解:函数的参数类型是逆变的,而函数的返回类型是协变的

 

2)代码演示

package cn.itcast.covariance

class Super

class Sub extends Super


//协变
class Temp1[+A](title: String)//Temp1[Super]Temp1[Sub]的父类

//逆变
class Temp2[-A](title: String)//Temp2[Super]Temp2[Sub]的子类

//非变
class Temp3[A](title: String)//Temp3[Super]Temp3[Sub]没有子父关系

object Covariance_demo {

  def main(args: Array[String]) {
    //支持协变 Temp1[Sub]Temp1[Super]的子类
    val t1: Temp1[Super] = new Temp1[Sub]("hello scala!!!")

    //支持逆变 Temp1[Super]Temp1[Sub]的子类
    val t2: Temp2[Sub] = new Temp2[Super]("hello scala!!!")

    //支持非变 Temp3[Super]Temp3[Sub]没有从属关系,如下代码会报错
    //val t3: Temp3[Sub] = new Temp3[Super]("hello scala!!!")
    //val t4: Temp3[Super] = new Temp3[Sub]("hello scala!!!")
    println(t1.toString)

    println(t2.toString)
  }
}

 

 

 

3.类型上下界

1)语法详解

●引入

在指定泛型类型时,有时需要界定泛型类型的范围,而不是接收任意类型。

比如,要求某个泛型类型,必须是某个类的子类,这样在程序中就可以放心的调用父类的方法,程序才能正常的使用与运行。

此时,就可以使用上下边界Bounds的特性;

Scala的上下边界特性和Java中一样,允许泛型类型是某个类的子类,或者是某个类的父类;

 

●上下界

S <: T  类似于Java中的 ? extends T

类型上界,也就是S必须是类型T的子类(或本身)

U >: T  类似于Java中的 ?  super T

类型下界,也就是U必须是类型T的父类(或本身)

 

●源码中有很多上下界

 

2)代码演示

package cn.itcast.generic

object BoundsDemo {
  def main(args: Array[String]): Unit = {
    m1(Array(new Person))
    m1(Array(new Student))
    //出错,必须为Person类型或其子类
    //m1(Array("hadoop"))

    m2(Array(new Person))

    m2(Array(new Policeman))
    //出错,必须为Policeman类型或其父类
    //m2(Array(new Trafficpolice))
  }


  //定义一个方法限定泛型上界为Person,即类型必须为Person类型或其子类
  def m1[T <: Person](arr: Array[T]) = println(arr)


  //定义一个方法限定泛型下界为Policeman,即类型必须为Policeman类型或其父类
  def m2[T >: Policeman](arr: Array[T]) = println(arr)

}

class Person
class Student extends Person

class Policeman extends Person
class Trafficpolice extends Policeman

 

 

(三)隐式转换和隐式参数

1.语法详解

●概述

Scala提供的隐式转换和隐式参数功能非常具有特色,也是Java等编程语言所没有的功能。

可以将某种类型的对象转换成指定类型或者是给一个类型增加新的方法(可以不修改原来的代码而为代码增加新功能)

后面在学习Akka、Spark、Flink等都会看到隐式转换和隐式参数的身影,可以实现非常强大且特殊的功能。

 

●隐式转换

所谓隐式转换,是指以implicit关键字声明的带有单个参数的方法。

定义的隐式转换方法,只要在编写的程序内引入(import),就会被Scala自动调用。

Scala会根据隐式转换方法的签名,在程序中使用到隐式转换方法接收的参数类型定义的对象时,会自动将其传入隐式转换方法,转换为另外一种类型的对象并返回。

 

●隐式参数

所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时编译器会尝试找到一个指定类型的,用implicit修饰的隐式值并设置给该参数。

Scala会在两个范围内查找:当前作用域内可见的val或var定义的隐式变量、隐式参数类型的伴生对象内的隐式值

 

●隐式转换的时机

当方法中的参数的类型与目标类型不一致时

当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换

 

●隐式转换查找与导入

(1)Scala默认会使用两种隐式转换,

一种是源类型,或者目标类型的伴生对象内的隐式转换函数或隐式值;

一种是当前程序作用域内的可以用唯一标识符表示的隐式转换方法或隐式值。

(2)如果不在上述两种情况下的话,

那么就必须手动使用import语法引入某个包下的隐式转换,比如import test._

通常建议:仅仅在需要进行隐式转换的地方,用import导入隐式转换,这样可以缩小隐式转换的作用域,避免不需要的隐式转换

 

●注意

1.其中所有的隐式值和隐式方法必须放到object中。

2.implicit关键字只能用来修饰方法、变量(参数)和伴随对象。

3.Scala程序会自动导入以下包:

import java.lang._

import scala._

import Predef._

 

2.代码演示

 

●源码中的运用

package cn.itcast.implicit_demo

/**
  * Author caowei
  * Date 2019/7/5 13:00
  * Desc
  */
object ImplicitDemo {

  def main(args: Array[String]): Unit = {
    //1 to 10 等价于 1.to(10)PreDef.scala中定义的隐式转换将Int转换为RichInt调用其to方法实现的
    val ran1 = 1 to 10

    println(ran1)
    val ran2 =  1.to(10)
    println(ran2)
  }
}

 

●查看PreDef.scala源码

 

 

 

 

●隐式转换方法

package cn.itcast.implicit_demo

import java.io.File

import scala.io.Source


class RichFile(val file: File) {
  def read() = {
    Source.fromFile(file).mkString
  }
}

object RichFile {
  // 定义隐式转换方法
  implicit def file2RichFile(file: File) = new RichFile(file)

}

object RichFileTest {
  def main(args: Array[String]): Unit = {
    // 或者在当前作用域定义隐式转换方法,则编译器会自动导入
    //implicit def file2RichFile(f:File) = new RichFile(f)

    val file = new File("RichFile.scala")

    // 导入隐式转换,将File转换为RichFile,那么file对象就有了read方法
    import RichFile.file2RichFile
    println(file.read())

  }
}

 

 

●隐式参数

package cn.itcast.implicit_demo

// 隐式参数
object ImplicitParam {

  implicit val DEFAULT_DELIMITERS = ("<<", ">>")
}

object ImplicitParamTest {
  // 定义一个方法需要使用一个隐式参数
  def quote(str:String)(implicit delimiter:(String, String)) = {

    delimiter._1 + str + delimiter._2
  }

  def main(args: Array[String]): Unit = {
    //导入隐式参数
    import ImplicitParam.DEFAULT_DELIMITERS
    //调用quote方法,传入普通参数,而隐式参数需要导入或由编译器自动寻找并导入
    println(quote("Scala从入门到精通"))

  }
}

 

(四)正则表达式

1.语法详解

●Regex类

scala中提供了Regex类来定义正则表达式

要构造一个Regex对象可以使用new Regex的方式,或者直接使用String类的r方法即可

val regex1 = new Regex("""正则表达式""")

val regex2 = """正则表达式""".r  //建议使用三个双引号来表示正则表达式,不然就得对正则中的反斜杠来进行转义

 

●findAllMatchIn方法

使用findAllMatchIn方法可以获取到所有正则匹配到的字符串

 

●注意

一般正则表达式的规则我们上网查找别人写好的就可以,因为正则表达式灵活多变,我们自己写的不够专业!

 

2.代码演示

package cn.itcast.regex

import scala.util.matching.Regex

/**
  * Author caowei
  * Date 2019/7/5 14:12
  * Desc
  */
object RegexDemo {

  def main(args: Array[String]): Unit = {
    //定义一个正则用来匹配邮箱
    //val regex: Regex = new Regex(""".+@.+..+""")
    val regex: Regex = """.+@.+..+""".r


    val matches: Iterator[Regex.Match] = regex.findAllMatchIn("1234aa@qq.com")
    println(matches) //non-empty iterator
    println(matches.size) //1

    //定义一个待验证的邮箱列表
    val emails = List("1234aa@qq.com", "1234bb@gmail.com", "abcd@163.com", "1234aa.com")


    //过滤出非法邮箱
    val invalidEmails = emails.filter {

      email =>
        //如果匹配结果长度<1,说明不匹配,即为非法邮箱
      if (regex.findAllMatchIn(email).size < 1) {

        true
      } else {

        false
      }

    }
    println(invalidEmails)
  }
}

 

(五)高阶函数

●高阶函数

Scala混合了面向对象和函数式的特性,在函数式编程语言中,函数是头等公民

我们通常将可以作为参数传递到方法中的表达式叫做函数,

将能够接收函数作为参数或者返回函数的函数,叫做高阶函数

高阶函数包含:作为值的函数、作为参数的函数、匿名函数、闭包、柯里化等。

 

1.函数回顾-函数作为值、作为参数、匿名函数

1)语法详解

●完整语法:

val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>函数体

●简写语法:

val函数名称 = (参数名称:参数类型) => 函数体

●符号解释

=  表示将右边的函数赋给左边的变量

=> 左面表示输入参数名称和类型,右边表示函数的实现和返回值类型

●匿名函数

没有将函数赋给变量的函数叫做匿名函数。

●方法转函数

val 函数名 = 方法名 _

 

2)代码演示

package cn.itcast.highfunction

/**
  * Author caowei
  * Date 2019/7/5 15:21
  * Desc
  */
object FunctionDemo {

  def main(args: Array[String]): Unit = {
    val arr1 = Array(1,2,3,4,5)
    println(arr1.toBuffer)
    //1.定义一个函数赋值给变量fun1
    val fun1 = (x:Int) => x * 2

    //2.将函数作为参数传递给map方法
    val arr2 = arr1.map(fun1)

    println(arr2.toBuffer)
    //3.直接传入匿名函数
    val arr3 = arr1.map((x:Int) => x * 2)

    println(arr3.toBuffer)
    //4.简写
    val arr4 = arr1.map(_ * 2)

    println(arr4.toBuffer)

    //定义一个方法
    def m(x:Int):Int = x * 3

    //5.可以将方法转为函数
    val fun = m _   

    val arr5 = arr1.map(fun)
    println(arr5.toBuffer)
    //map方法的参数需要一个函数,但是我们直接将方法作为参数进行传递,说明编译器自动将方法-->函数
    val arr6 = arr1.map(m)

    println(arr6.toBuffer)
  }
}

 

 

 

 

 

2.闭包

1)语法详解

●什么是闭包

闭包其实就是一个函数,只不过该函数依赖于声明在函数外部的一个或多个变量

就是一个函数把外部的那些不属于自己的变量也包含(闭合)进来。

 

2)代码演示

●例子

package cn.itcast.function_demo

/**
  * scala中的闭包
  * 闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
  */
object ClosureDemo {

  def main(args: Array[String]): Unit = {
    var y = 10
    //函数在变量不处于其有效作用域时,还能够对变量进行访问
    val add = (x: Int) => {

      x + y
      //在函数里面访问原本不属于函数的变量,就称作闭包
      //因为闭包现象的存在,函数可以访问到不属于自己变量
      //那么就导致了,多次调用同一个函数,传递同一个参数,最后的结果可能不一样,因为闭包中访问的不属于函数的变量值可能发生变化
      //那么为了避免这种情况:1.不要使用闭包 2.把闭包中不属于函数的变量设置为val
    }
    println(add(5)) // 结果15
    y = 100

    println(add(5))// 结果105
  }

}

 

 

●对比Java中的匿名内部类

package cn.itcast.review;

import java.util.Map;
import java.util.Properties;


public class Test {

   public static void main(String[] args){
       int b = 2;
       TestInterface ter = new TestInterface() {
           @Override
           public int add(int a) {

               return a + b;
           }
       };
       int result = tsetAdd(ter, 1);
       System.out.println(result);
       //b = 3;//Java,匿名内部类访问的外部变量,外部变量默认就是final修饰
       int result2 = tsetAdd(ter, 1);

       System.out.println(result2);
   }

   public static int tsetAdd(TestInterface ter,int a){
      return ter.add(a);
   }
}
interface TestInterface{
    int add(int a);
}

 

3.柯里化

1)语法详解

●高能预警

了解即可,更多思想上的东西需要有众多编程语言的实际使用经验的积累才能有真正的理解和领悟!

 

●引入

Scala中一个方法/函数可以多次调用,每次只传递部分参数,返回一个函数,后面接着调用返回的函数传递其他的参数

这样就可以方便地绑定一些参数,其余的参数可稍后再补上,这其实就是柯里化的思想

在scala和spark的源代码中,大量使用到了柯里化。为了后续方便阅读源代码,我们需要来了解一下柯里化。

 

●什么是柯里化Currying

柯里化指的是:把函数/方法的一个参数列表接受多个参数 变换成 多个参数列表的过程(每次传入一个参数会返回一个新函数接受余下的参数)

 

●柯里化的意义

https://www.zhihu.com/question/20037482

函数柯里化之后,可以只提供一个参数,其他的参数作为这个函数的“环境”来创建。这就能让函数回归到原始的:一个参数进去一个值出来的状态。

柯里化又可叫部分求值。一个柯里化的函数接收一些参数,接收了这些参数之后,该函数并不是立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数真正需要求值的时候,之前传入的所有参数都能用于求值。

使用柯里化可以简化主函数的复杂度,提高主函数的自闭性,使代码模块化,减少耦合增强其可维护性,提高功能上的可扩张性、灵活性。可以编写出更加抽象、功能化和高效的代码。

柯里化是以函数为主体的编程语言思想发展的必然产生的结果。

Scala中的很的地方都用到了柯里化,如fold

 

2)代码演示

 

 

 

package cn.itcast.highfunction

object CurryingDemo {
  def main(args: Array[String]): Unit = {
    //1.定义一个普通方法实现加法:
    def sum(x: Int, y: Int): Int = x + y

    //调用
    val r1= sum(1, 2)

    println(r1)

    //2.定义一个柯里化的方法实现加法,
    //原来函数使用一个参数列表(),柯里化则需要把函数定义为多个参数列表()()
    def curriedSum(x: Int)(y: Int) = x + y

    //调用
    val r2= curriedSum(1)(2)

    println(r2)

    //当你调用curriedSum (1)(2)时,实际上是依次调用两个普通函数(非柯里化函数),
    //1次调用传入x,返回一个函数,
    //2次调用传入y,返回最终结果值。
    val temp: Int => Int = curriedSum(1) //(y:Int) => 1 + y
    val r3: Int = temp(2)

    println(r3)
  }
}

 

 

 

九、Actor并发编程

(一)Actor引入

1.Actor介绍

●概述

Scala中的Actor并发编程模型可以用来开发比Java线程效率更高的并发程序。

Actor可以看作是一个个独立的实体,可以理解为Java中的Thread

但是Java中的Thread之间的通信使用的是等待唤醒机制,而Actor之间的通信使用的是消息

消息的类型可以是任意的,消息的内容也可以是任意的。

一个Actor收到其他Actor的信息后,它可以根据需要作出各种响应。

综上,Actor是一种基于事件模型的并发机制,是运用消息的发送、接收来实现高并发的。

 

●说明

Scala在2.11.x版本中加入了Akka并发编程框架,老版本Actor已经废弃。我们这里学习Actor的目的是为了后续学习Akka做准备。

 

2.Java并发编程的问题

在Java并发编程中,每个对象都有一个逻辑监视器(monitor),可以用来控制对象的多线程访问。

我们添加sychronized关键字来标记,需要进行同步加锁访问。

这样,通过加锁的机制来确保同一时间只有一个线程访问共享数据。

但这种方式存在资源争夺、以及死锁问题,程序越大问题越麻烦。

●共享数据

 

 

 

●线程死锁

 

 

 

 

 

3.Actor并发编程模型

处理并发问题就是如何保证共享数据的一致性和正确性,为什么会有保持共享数据正确性这个问题呢?

无非是我们的程序是多线程的,多个线程对同一个数据进行修改,若不加同步条件,势必会造成数据污染。

Actor 模型的出现解决了这个问题,简化并发编程,提升程序性能。

Actor是一种与Java并发编程完全不一样的并发编程模型

Actor使用一种基于事件模型的并发机制,是一种不共享数据,依赖消息传递的一种并发编程模式,有效避免资源争夺、死锁等情况。

从图中可以看到:

Actor 与Actor 之前只能用消息进行通信,当某一个Actor 给另外一个Actor发消息,只需要将消息投寄的相应的邮箱,至于对方Actor 怎么处理你的消息你并不知道,当然你也可等待它的回复。

 

 

 

 

 

 

 

 

4.与Java并发编程的对比

对于Java,我们都知道它的多线程实现需要对共享资源(变量、对象等)使用synchronized 关键字进行代码块同步、对象锁互斥等等。而且,常常一大块的try…catch语句块中加上wait方法、notify方法、notifyAll方法是让人很头疼的。

出现这样的问题的原因就在于Java中多数使用的是可变状态的对象资源,对这些资源进行共享来实现多线程编程的话,控制好资源竞争与防止对象状态被意外修改是非常重要的,而对象状态的不变性也是较难以保证的。

Java的基于共享数据和锁的线程模型不同,Scala的actor包则提供了另外一种不共享任何数据、依赖消息传递的模型,从而进行并发编程。

Java内置线程模型

scala Actor模型

"共享数据-锁"模型 (share data and lock)

share nothing

每个object有一个monitor,监视线程对共享数据的访问

不共享数据,Actor之间通过Message通讯

加锁代码使用synchronized标识

 

死锁问题

 

每个线程内部是顺序执行的

每个Actor内部是顺序执行的

 

 

5.Actor发送消息的方式

!

发送异步消息,没有返回值。

!?

发送同步消息,等待返回值。

!!

发送异步消息,返回值是 Future[Any]。

注意:

Future 表示一个异步操作的结果状态,可能还没有实际完成的异步任务的结果

Any是所有类的超类,Future[Any]的泛型是异步操作结果的类型。

 

(二)Actor实战

1.Demo1-创建Actor

●添加本地SDK支持

注意:如果要使用Scala的actor,需要将Scala的SDK添加到我们的项目当中去

 

●需求

创建两个Actor,分别打印1-10,11-20

●步骤:

1、定义一个class/object继承Actor特质,注意导包import scala.actors.Actor

2、重写对应的act方法

3、调用Actor的start方法执行Actor

4、当act方法执行完成,整个程序运行结束

●注意:

调用start()方法,Actor的act()方法会被执行,类似于在java中开启了两个线程,线程的run()方法会被执行

Actor是并行执行的

●代码实现

package cn.itcast.actor

import scala.actors.Actor

/**
  * Author caowei
  * Date 2019/7/5 17:39
  * Desc
  */
object ActorDemo1 {

  //使用class继承Actor创建
  class Actor1 extends Actor {

    override def act() ={
      (1 to 10).foreach(println)
    }
  }
  class Actor2 extends Actor {
    override def act()= {
      (11 to 20).foreach(println)
    }
  }

  //使用object继承Actor创建
  object Actor3 extends Actor {

    override def act() =
      for(i <- 1 to 10) {
        println(i)
      }
  }
  object Actor4 extends Actor {
    override def act() =
      for(i <- 11 to 20) {
        println(i)
      }
  }
  def main(args: Array[String]): Unit = {
    new Actor1().start()
    new Actor2().start()
    Actor3.start()
    Actor4.start()
  }
}

 

2.Demo2-发送接收消息

●需求

使用Actor发送消息和接收消息

●步骤:

1、创建两个Actor(ActorSender、ActorReceiver)

2、ActorSender发送一个异步字符串消息给ActorReceiver

3、ActorReceive接收到该消息后,打印出来

 

●发送消息的方式

!

发送异步消息,没有返回值。

!?

发送同步消息,等待返回值。

!!

发送异步消息,返回值是 Future[Any]。

 

●接收消息

Actor中使用receive方法来接收消息,需要给receive方法传入一个偏函数

注意:receive方法只接收一次消息,接收完后继续执行act方法

    {

        case 变量名1:消息类型1 => 业务处理1,

        case 变量名2:消息类型2 => 业务处理2,

        ...

    }

●总结

发送消息可以使用 !(异步无返回)、!?(同步等待返回)、!!(异步有返回)。

接受消息可以使用关键字receive。

还可以使用模式匹配的方式,对接受到的不同消息的指令作出对应的操作。

 

●代码实现

package cn.itcast.actor

import scala.actors.Actor

/**
  * Author caowei
  * Date 2019/7/5 17:57
  * Desc
  */
object AcotrDemo2 {

  object ActorSender extends Actor {
    override def act() = {
      //发送一条消息
      ActorReceiver ! "你好"

      //再次发送一条消息,ActorReceiver无法接收到
      ActorReceiver ! "你叫什么名字?"

    }
  }
  object ActorReceiver extends Actor {
    override def act() ={
      receive {
        case msg: String => println(s"接收Actor: 接收到$msg")
      }
    }
  }
  def main(args: Array[String]): Unit = {
    ActorSender.start()
    ActorReceiver.start()
  }
}

 

3.Demo3-循环接收消息

●需求

实现actor可以不断地接受消息

●思路

在act方法中可以使用while(true)的方式,不断的发送、接受消息。

●代码演示

package cn.itcast.actor

import scala.actors.Actor

/**
  * Author caowei
  * Date 2019/7/5 17:51
  * Desc
  */
object ActorDemo3 {

  object ActorSender extends Actor {
    override def act() = {
      // 发送消息
      while (true) {

        ActorReceiver ! "hello!"
        Thread.sleep(2000)
      }
    }
  }
  object ActorReceiver extends Actor {
    override def act() = {
      // 持续接收消息
      while (true) {

        receive {
          case msg: String => println("接收到消息:" + msg)
        }
      }
    }
  }

  def main(args: Array[String]): Unit = {
    ActorReceiver.start()
    ActorSender.start()
  }
}

 

4.Demo4-使用loop-react复用线程

●问题

上述代码中使用while循环来不断接收消息,存在如下问题

- 如果当前Actor没有接收到消息,线程就会处于阻塞状态

- 如果有很多的Actor,就有可能会导致很多线程都是处于阻塞状态

- 每次有新的消息来时,重新创建线程来处理

- 频繁的线程创建、销毁和切换,会影响运行效率

●解决

可以使用loop + react来复用线程,比while + receive更高效

●代码演示

package cn.itcast.actor

import scala.actors.Actor

/**
  * Author caowei
  * Date 2019/7/5 17:51
  * Desc
  */
object ActorDemo4 {

  object ActorSender extends Actor {
    override def act() = {
      // 发送消息
      while (true) {

        ActorReceiver ! "hello!"
        Thread.sleep(2000)
      }
    }
  }
  object ActorReceiver extends Actor {
    override def act() = {
      // 持续接收消息
      loop {

        react {
          case msg: String => println("接收到消息:" + msg)
        }
      }
    }
  }

  def main(args: Array[String]): Unit = {
    ActorReceiver.start()
    ActorSender.start()
  }
}

 

 

5.Demo5-使用样例类发送接收自定义消息

●需求

结合case class样例类发送消息和接受消息

●思路

1、将消息封装在一个样例类中

2、通过匹配不同的样例类去执行不同的操作

3、Actor可以返回消息给发送方。通过sender方法向当前消息发送方返回消息

●步骤

1、创建一个MsgActor,向MsgActor发送一个同步消息SyncMessage,该消息包含两个字段(id、message)

2、MsgActor回复一个消息ReplyMessage,该消息包含两个字段(message、name)

3、向MsgActor发送一个异步无返回消息AsyncMessage,该消息包含两个字段(id、message)

4、向MsgActor发送一个异步有返回消息AsyncMessage,该消息包含两个字段(id、message)

5、MsgActor回复一个消息ReplyMessage,该消息包含两个字段(message、name)

 

 

 

 

●说明

- 使用!?来发送同步消息

- 使用!发送异步无返回消息

- 使用!!发送异步有返回消息

- 在Actor的act方法中,可以使用sender获取发送者的Actor引用

- 返回类型为Future[Any]的对象,Future表示异步返回数据的封装,虽获取到Future的返回值,但不一定有值,可能在将来某一时刻才会返回消息

- Future的isSet()可检查是否已经收到返回消息,apply()方法可获取返回数据

 

●代码演示

package cn.itcast.actor

import scala.actors.{Actor, Future}

/**
  * Author caowei
  * Date 2019/7/5 17:51
  * Desc
  */
object ActorDemo5 {

  case class SyncMessage(id:Int, msg:String)//同步消息
  case class AsyncMessage(id:Int, message:String)//异步消息
  case class ReplyMessage(msg:String, name:String)//返回结果消息

  object MsgActor extends Actor {

    override def act(): Unit = {
      loop {
        react {
          case SyncMessage(id, msg) => {
            println(s"MsgActor接收到消息:${id}/${msg}")
            Thread.sleep(2000)
            sender ! ReplyMessage("不太好", "Tom")
          }
          case AsyncMessage(id, msg) => {
            Thread.sleep(2000)
            println(s"MsgActor接收到消息:${id}/${msg}")
          }
          case AsyncMessage(id, message) =>{
            println(s"MsgActor接收到消息:${id}/${message}")
            Thread.sleep(2000)
            sender ! ReplyMessage("不想说话!", "Tom")
          }
        }
      }
    }
  }

  def main(args: Array[String]): Unit = {
    //开启线程
    MsgActor.start()


    //1.发送同步有返回消息
    val replyMessage1: Any = MsgActor !? SyncMessage(1, "你好")

    println("回复消息:" + replyMessage1.asInstanceOf[ReplyMessage])
    println("===============1同步发送并等待返回================")

    //2.发送异步无返回消息
    MsgActor ! AsyncMessage(2, "怎么不好了?")

    println("===============2异步发送并立即返回================")

    //3.发送异步有返回消息
    val future: Future[Any] = MsgActor !! AsyncMessage(3, "说话啊!")

    println("===============3异步发送并立即返回,后面可以异步处理返回的消息================")
    while(!future.isSet) {}
    val replyMessage2 = future.apply().asInstanceOf[ReplyMessage]
    println(replyMessage2)
  }
}

 

 

6.综合案例-WordCount

●需求

用Actor并发编程写一个多线程版的WorldCount,将多个文件作为输入(文件内容都是以空格分隔的),计算完成后将多个任务汇总,得到最终的结果

●图解

 

 

●步骤:

1. MainActor获取要进行单词统计的文件列表

2. 根据文件数量创建对应的WordCountActor

3. 将文件名封装为消息发送给WordCountActor

4. WordCountActor使用loop +react接收消息,并统计单个文件的单词计数

5. 将单词计数结果进行局部汇总,发送给MainActor

6. MainActor等待所有的WordCountActor都已经成功返回消息,然后进行全局汇总

 

●代码实现

package cn.itcast.wordcount

import java.io.File

import scala.actors.{Actor, Future}

import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source

/**
  * Author caowei
  * Date 2019/7/5 19:22
  * Desc
  */
//创建MainActor用来分发任务并做全局汇总
object MainAcotr {

  def main(args: Array[String]): Unit = {
    //0.定义一个可变Set集合,存放每次异步返回的局部统计结果
    val futureSet = new mutable.HashSet[Future[Any]]()

    //0.定义一个可变List集合,用来存放真正的结果
    val resultList = new ListBuffer[Map[String, Int]]() //[Map[String, Int],Map[String, Int]]

    //1.读取文件列表
    val fileDir = new File("ScalaDemo01/data")

    val files: Array[File] = fileDir.listFiles()
    //2.遍历文件
    for(file <- files){

      //3.针对每一个文件,创建一个Acotr对象去统计
      val actor = new WordCountActor

      //4.开启线程
      actor.start()

      //5.提交任务-发送异步有返回消息
      val result: Future[Any] = actor !! SubmitTask(file.getAbsolutePath)

      //6.将局部结果存入集合等待全局汇总
      //futureSet += result
      futureSet.add(result)

    }
    //循环结束,所以文件统计任务已发送给Actor,接下来开始处理返回的Future
    //8.等待所有的Actor都已经真正返回结果
    while(futureSet.filter(_.isSet).size != futureSet.size){}

    //9.取出结果放入到list
    futureSet.foreach(

      f => {
        //每个文件的局部结果
        val message: ResultMessage = f.apply().asInstanceOf[ResultMessage]

        //resultList += result
        resultList.append(message.result)

      }
    )
    //10.接下来开始全局汇总
    val allWordAndCount: ListBuffer[(String, Int)] = resultList.flatten

    val allWordAndList: Map[String, ListBuffer[(String, Int)]] = allWordAndCount.groupBy(_._1)
                                        //_+_._2
                                        //第一个_,表示上次的累加值,
                                        //第二个_,表示List中的每一个元组
                                        //第三个_2,表示元组中的第二个元素即Int
    val finalResult: Map[String, Int] = allWordAndList.mapValues(_.foldLeft(0)(_+_._2))

    println("最后的统计结果为:" + finalResult)
  }
}
//创建一个线程任务类用来进行局部统计,接收文件名,返回局部统计结果Map[String,Int]
class WordCountActor extends Actor{

  override def act(): Unit = {
    loop{
      react{
        //1.循环接收发送过来的要进行统计的文件
        case SubmitTask(filePath) => {

          //测试ok,可以接收到文件名
          //println(filePath)
          //2.读取文件,并获取所有行
          val lines: List[String] = Source.fromFile(filePath).getLines().toList

          //3.对每一行按空格切分并压平
          val words: List[String] = lines.flatMap(_.split(" "))

          //4.对每个单词记为1
          val wordAndOnes: List[(String, Int)] = words.map((_,1))

          //5.按照单词进行分组
          val wordAndList: Map[String, List[(String, Int)]] = wordAndOnes.groupBy(_._1)

          //6.局部聚合
          val wordAndCount: Map[String, Int] = wordAndList.mapValues(_.length)

          //7.把局部聚合结果返回
          sender ! ResultMessage(wordAndCount)

        }
      }
    }
  }
}
//创建样例类用来封装消息
case class SubmitTask(filePath:String) //封装任务消息--文件路径
case class ResultMessage(result:Map[String,Int])//封装局部聚合结果

 

 

十、Akaa并发编程-模拟Spark的RPC通信

(一)概述

1.RPC

目前大多数的分布式架构底层通信都是通过RPC实现的,RPC框架非常多,比如前我们学过的Hadoop项目的RPC通信框架,但是Hadoop在设计之初就是为了运行长达数小时的批量而设计的,在某些极端的情况下,任务提交的延迟很高,所以Hadoop的RPC显得有些笨重。

Spark 1.6之前,Spark 的RPC是基于Akaa来实现的。Akka是一个基于scala语言的异步的消息框架,基于Actor并发模型实现,Akka具有高可靠、高性能、可扩展等特点,使用Akka可以轻松实现分布式RPC功能。

Spark 1.6后,Spark借鉴Akka的设计自己实现了一个基于Netty的rpc框架。

 

2.Akka简介

●什么是Akka

https://www.iteblog.com/archives/1154.html

Akka是一个基于Actor模型的用于构建高并发、分布式和可扩展的基于事件驱动的应用的工具包

Akka提供了一个用于构建可扩展的(Scalable)、弹性的(Resilient)、快速响应的(Responsive)应用程序的平台。

 

●再次理解Actor

Actor模型是一个并行计算(Concurrent Computation)模型,它把actor作为并行计算的基本元素来对待:为响应一个接收到的消息,一个actor能够自己做出一些决策,如创建更多的actor,或发送更多的消息,或者确定如何去响应接收到的下一个消息。

Actor是Akka中最核心的概念,它是一个封装了状态和行为的对象,Actor之间可以通过交换消息的方式进行通信,每个Actor都有自己的收件箱(Mailbox)。通过Actor能够简化锁及线程管理,可以非常容易地开发出正确地并发程序和并行系统

 

●Akka的特性

提供了一种高级抽象,能够简化在并发(Concurrency)/并行(Parallelism)应用场景下的编程开发

提供了异步非阻塞的、高性能的事件驱动编程模型

内置容错机制,允许Actor在出错时进行恢复或者重置操作

超级轻量级事件处理(每GB堆内存几百万Actor)

使用Akka可以在单机上构建高并发程序,也可以在网络中构建分布式程序。

 

3.Akka通信过程

 

以下图片说明了Akka Actor的并发编程模型的基本流程:

 

1. 学生创建一个ActorSystem

2. 通过ActorSystem来创建一个ActorRef(老师的引用),并将消息发送给ActorRef

3. ActorRef将消息发送给Message Dispatcher(消息分发器)

4. Message Dispatcher将消息按照顺序保存到目标Actor的MailBox中

5. Message Dispatcher将MailBox放到一个线程中

6. MailBox按照顺序取出消息,最终将它递给TeacherActor接受的方法中

 

4.API介绍

●实现Actor

注意:继承Actor要导入akka.actor包下的Actor

在Akka中,Actor负责通信,在Actor中有一些重要的生命周期方法

preStart()方法:该方法在Actor对象构造方法执行后执行,整个Actor生命周期中仅执行一次

receive()方法:该方法在Actor的preStart方法执行完成后执行,用于接收消息,会被反复执行。不需要添加loop和react方法调用

 

●ActorSystem:

在Akka中,ActorSystem是一个重量级的结构,他需要分配多个线程

所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个ActorSystem创建很多Actor

ActorSystem是一个进程中的老大,它负责创建和监督actor

val actorSystem: ActorSystem = ActorSystem("MasterSystem", config)

 

●加载Akka Actor

要创建Akka的Actor,必须要先获取创建一个ActorSystem。创建需要给ActorSystem指定一个名称,并可以去加载一些配置项

ActorSystem.actorOf(Props(Actor对象), "Actor名字")来加载Actor

 

●Actor中获取其他Actor的引用

val 名称: ActorSelection  = context.actorSelection(s"akka.tcp://ActorSystem名称@ip:端口/user/名称")

 

●Akka定时任务

import context.dispatcher

context.system.scheduler.schedule(延迟多久执行0 millis, 每隔多久执行15000 millis, 给谁发self, 定期执行的函数CheckTimeOutWorker)

 

●Actor Path

每一个Actor都有一个Path,这个路径可以被外部引用。路径的格式如下:

Actor类型

路径

示例

本地Actor

akka://actorSystem名称/user/Actor名称

akka://MasterSystem/user/master

远程Actor

akka.tcp://sys名称@ip地址:端口/user/Actor名称

akka.tcp://sys名称@192.168.1.100:8888/user/master

 

 

●Actor中获取ActorSystem

直接使用context.system就可以获取到管理该Actor的ActorSystem的引用

 

 

(二)案例1--模拟简单RPC通信

1.架构图

利用Akka的actor编程模型,实现2个进程间的通信

 

2.步骤分析

●Master

  1. 编写Master继承Actor(akka包下的Actor)
  2. 重写preStart方法,preStart会在构造代码块之后执行一次,用于初始化
  3. 重写receive方法,receive会在preStart方法执行后循环执行,用于接收消息和回复sender ! "reply"
  4. 编写Master伴生对象
  5. 准备配置信息
  6. 创建ActorSystem,负责创建和监督actor
  7. 获取Master的Actor的引用
  8. 等待结束

 

●Worker

  1. 编写Worker继承Actor(akka包下的Actor)
  2. 在preStart方法中与master建立链接
  3. 使用context.actorSelection方法查找master

val 名称: ActorSelection  = context.actorSelection(s"akka.tcp://ActorSystem名称@ip:端口/user/名称")

  1. 给master发送信息master ! "connect"
  2. 在receive方法中接收消息
  3. 编写Master伴生对象
  4. 准备配置信息
  5. 创建ActorSystem,负责创建和监督actor
  6. 获取Worker的Actor的引用
  7. 等待结束

 

 

3.代码实现

1)创建maven工程

 

 

2)pom

<properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <encoding>UTF-8</encoding>
        <scala.version>2.11.8</scala.version>
        <scala.compat.version>2.11</scala.compat.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_2.11</artifactId>
            <version>2.3.14</version>
        </dependency>
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-remote_2.11</artifactId>
            <version>2.3.14</version>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <args>
                                <arg>-dependencyfile</arg>
                                <arg>${project.build.directory}/.scala_dependencies</arg>
                            </args>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>reference.conf</resource>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass></mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

 

3)Master类

package cn.itcast.rpc

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}

/**
  *利用akka中的actor模型,实现2个进程之间的通信-------Master
  */
class Master extends Actor {

   println("constructor invoked")

  //1.preStart会在构造代码块之后执行一次,用于初始化
  override def preStart(): Unit = {

    println("PreStart invoked")
  }

  //2.receive会在preStart方法执行后循环执行,用于接收消息和回复
  override def receive: Receive = {

    case "connect" => {
      println("a client connected")
      sender ! "reply"
    }
  }
}

object Master {
  def main(args: Array[String]): Unit = {
    //0.准备配置信息
    val host = "127.0.0.1" //args(0)        //masterIP地址
    val port = 10086       //args(1).toInt  //masterport端口
    val configStr =

    s"""
       |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
       |akka.remote.netty.tcp.hostname = "$host"
       |akka.remote.netty.tcp.port = "$port"
       """.stripMargin
    val config: Config = ConfigFactory.parseString(configStr)

    //1.创建ActorSystem,负责创建和监督actor
    val actorSystem: ActorSystem = ActorSystem("MasterSystem", config)

    //2.获取MasterActor的引用
    val master: ActorRef = actorSystem.actorOf(Props[Master], "Master")

    //3.等待结束
    actorSystem.awaitTermination()

  }
}

 

4)Worker类

package cn.itcast.rpc

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
/**
  *利用akka中的actor模型,实现2个进程之间的通信-------Worker
  */
class Worker(val masterHost: String, val masterPort: Int) extends Actor {

  //1.preStart方法中与master建立链接
  override def preStart(): Unit = {

    //2.使用context.actorSelection方法查找master,需要:通信协议、masterip地址、master的端口、master对应的ActorSystem,actor层级
    var master: ActorSelection  = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")

    //3.master发送信息
    master ! "connect"

  }

  //4.receive方法中接收消息
  override def receive: Receive = {

    case "reply" => {
      println("a reply from master")
    }
  }
}

object Worker {
  def main(args: Array[String]): Unit = {
    //0.准备配置信息
    val host = "127.0.0.1"       //args(0)         //workerip地址
    val port = 10010             //args(1).toInt   //worker的端口,可以修改端口启动多次,模拟多个worker请求连接
    val masterHost = "127.0.0.1" //args(2)         //masterip地址
    val masterPort = 10086       //args(3).toInt   //master的端口
    val configStr =

      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
       """.stripMargin
    val config: Config = ConfigFactory.parseString(configStr)

    //1.创建ActorSystem对象,负责创建和监督actor
    val actorSystem: ActorSystem = ActorSystem("WorkerSystem", config)

    //2.获取WorkerActor引用
    val worker: ActorRef = actorSystem.actorOf(Props(new Worker(masterHost, masterPort)), "Worker")

    //3.等待结束
    actorSystem.awaitTermination()

  }
}

 

 

(三)案例2--模拟Spark通信

 

1.架构图

使用Akka实现一个简易版的spark通信框架

 

2.步骤分析

●准备工作

  1. 定义一些样例类用来封装消息
  2. 定义一个类用于封装worker的资源信息,包括内存、CPU等

●Master

class Master(val host: String, val port: Int) extends Actor {

  //0.定义一个可变map,用于存放worker的完整信息,key:workerid  value:WorkerInfo

  //0.定义一个可变list,用于存放worker的资源信息,方便后期按照worker的资源进行排序、筛选

  //0.定义一个常量,表示每隔多久定时检查

 

  //1.重写preStart方法

  //1.1导入隐式转换

  //1.2使用定时器定时调用CheckTimeOutWorker任务,清除心跳超时的worker

   context.system.scheduler.schedule(0 millis, CHECK_INTERVAL millis, self, CheckTimeOutWorker)

 

  //2.重写receive方法

  //2.1接受worker的注册信息

  //判断当前worker是否注册,没有注册就添加到map中

  //把没有注册的Worker添加到map中

  //添加worker信息到list

//反馈注册成功信息给worker(返回Master路径)

sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master")

  //2.2接受worker的心跳信息

  //判断是否已经注册

  //如果已注册,根据id去map中获取work信息

  //获取当前系统时间并记录下当前worker发送心跳的时间

  //2.3判断超时的worker

  //获取当前的系统时间

  //如果当前时间-上次心跳时间 > master检查时间间隔,则为worker失效

  //currentTime - lastHeatBeatTime >检查时间间隔

  //清除掉超时的worker

  //在list中移除掉超时的worker信息

  //在map中移除掉超时的worker信息

  //将存活Worker按照WorkInfo中的内存大小排序---默认升序  reverse降序

    

object Master {

    //0.准备配置信息

    //1.创建ActorSystem

    //2.获取Master的Actor的引用

    //3.等待结束

 

●Worker

class Worker(val masterHost: String, val masterPort: Int, val memory: Int, val cores: Int) extends Actor {

  //0.定义master为全局变量,便于以后使用

  //0.定义workerID

   //0.定义每隔多久发送一次心跳

 

  //1.重写preStart方法向master发送注册消息

    //使用context.actorSelection方法查找master,需要:通信协议、master的ip地址、master的端口、master对应的ActorSystem,actor层级

    master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")

    //向master发送注册信息,包括:worker的id、memory、cores

    master ! RegisterWorker(workerId, memory, cores)

 

  //2.重写receive方法接收消息

      //接收到注册成功的消息后向master定时发送心跳(注册成功返回的消息为master的url)

      //导入隐式转换

      import context.dispatcher

      //2.2定时调用发送心跳任务

      context.system.scheduler.schedule(0 millis, HEATBEAT_INTERVAL millis, self, SendHeartBeat)

    //2.3在发送心跳的任务中给master发送自己的workerId

 

 

object Worker {

    //0.准备配置信息

    //1.创建ActorSystem对象

    //2.获取Worker的Actor的引用

    //3.等待结束

 

 

3.代码实现

1)样例类

package cn.itcast.spark

//定义一些样例类用来封装消息
trait RemoteMessage extends Serializable


//Worker---->Master//workermaster发送注册信息,由于不在同一进程中,需要实现序列化接口
case class RegisterWorker(id: String, memory: Int, cores: Int) extends RemoteMessage


//Master------>Worker//masterworker反馈注册信息,由于不在同一进程中,需要实现序列化
case class RegisteredWorker(masterUrl: String) extends RemoteMessage


//Worker------>self//worker自己给自己发消息,在同一进程内,不需要实现序列化
case object SendHeartBeat

//Worker------>Master//workermaster发送心跳,由于不在同一进程中,需要实现序列化
case class HeatBeat(id: String)


//Master------->self//master自己给自己发消息(定时检查),由于在同一进程中,不需要实现序列化
case object CheckTimeOutWorker

 

2)WorkerInfo

package cn.itcast.spark

//定义一个类用于封装worker的资源信息
class WorkerInfo(val id: String, val memory: Int, val cores: Int) {

  //定义一个变量,表示worker上一次发送心跳的时间
  var lastHeatBeatTime: Long = _


  //重写toString
  override def toString: String = {

    s"id:$id,memory:$memory,cores:$cores"
  }
}

 

3)Master

package cn.itcast.spark

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}

import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.concurrent.duration._

/**
  *利用akkaactor的模型实现简易版的spark通信框架---Master
  */
class Master(val host: String, val port: Int) extends Actor {

  println("Master constructor invoked")
  //0.定义一个可变map,用于存放worker的完整信息,key:workerid  value:WorkerInfo
  val idToWorker = mutable.HashMap[String, WorkerInfo]()

  //0.定义一个可变list,用于存放worker的资源信息,方便后期按照worker的资源进行排序、筛选
  val workers = new ListBuffer[WorkerInfo]()

  //0.定义一个常量,表示每隔多久定时检查
  val CHECK_INTERVAL = 15000 //15

  //1.重写preStart方法
  override def preStart(): Unit = {

    println("preStart方法被调用")
    //1.1导入隐式转换
    import context.dispatcher

    //1.2使用定时器定时调用CheckTimeOutWorker任务,清除心跳超时的worker
    context.system.scheduler.schedule(0 millis, CHECK_INTERVAL millis, self, CheckTimeOutWorker)

  }

  //2.重写receive方法
  override def receive: Receive = {

    //2.1接受worker的注册信息
    case RegisterWorker(id, memory, cores) => {

      //判断当前worker是否注册,没有注册就添加到map
      if (!idToWorker.contains(id)) {

        //把没有注册的Worker添加到map
        val workerInfo = new WorkerInfo(id, memory, cores)

        idToWorker(id) = workerInfo
        //添加worker信息到list
        workers.append(workerInfo)

      }
      //反馈注册成功信息给worker(返回Master路径)
      sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master")

    }

    //2.2接受worker的心跳信息
    case HeatBeat(id) => {

      //判断是否已经注册
      if (idToWorker.contains(id)) {

        //如果已注册,根据idmap中获取work信息
        val workerInfo = idToWorker(id)

        //获取当前系统时间并记录下当前worker发送心跳的时间
        workerInfo.lastHeatBeatTime = System.currentTimeMillis()

      }
    }
    //2.3判断超时的worker
    case CheckTimeOutWorker => {

      //获取当前的系统时间
      val currentTime: Long = System.currentTimeMillis()

      //如果当前时间-上次心跳时间 > master检查时间间隔,则为worker失效
      //currentTime - lastHeatBeatTime >检查时间间隔
      val toRemove: ListBuffer[WorkerInfo] = workers.filter(w => currentTime - w.lastHeatBeatTime > CHECK_INTERVAL)

      //清除掉超时的worker
      for (i <- toRemove) {

        //list中移除掉超时的worker信息
        workers -= i

        //map中移除掉超时的worker信息
        idToWorker -= i.id

        println("超时worker的id= " + i.id)
      }
      println("存活的worker的个数= " + workers.size)

      //将存活Worker按照WorkInfo中的内存大小排序---默认升序  reverse降序
      val sortWorkers: List[WorkerInfo] = workers.sortBy(x => x.memory).toList

      println("存活的Worker分别为: "+sortWorkers)
    }
  }
}

object Master {
  def main(args: Array[String]): Unit = {
    //0.准备配置信息
    val host = "127.0.0.1"    //args(0)       //masterIP地址
    val port = 10086          //args(1).toInt //masterport端口
    val str =

      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
          """.stripMargin
    val config: Config = ConfigFactory.parseString(str)

    //1.创建ActorSystem
    val masterSystem: ActorSystem = ActorSystem.create("MasterSystem", config)

    //2.获取MasterActor的引用
    val master: ActorRef = masterSystem.actorOf(Props(new Master(host, port)), "Master")

    //3.等待结束
    masterSystem.awaitTermination()

  }
}

 

4)Worker

package cn.itcast.spark

import java.util.UUID

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}

import com.typesafe.config.{Config, ConfigFactory}

import scala.concurrent.duration._
/**
  *利用akkaactor的模型实现简易版的spark通信框架---Worker
  */
class Worker(val masterHost: String, val masterPort: Int, val memory: Int, val cores: Int) extends Actor {

  println("Worker constructor invoked")
  //0.定义master为全局变量,便于以后使用
  var master: ActorSelection = _  //_表示后续再赋值
  //0.定义workerID
  val workerId = UUID.randomUUID().toString

  //0.定义每隔多久发送一次心跳
  val HEATBEAT_INTERVAL = 10000 //间隔10

  //1.重写preStart方法向master发送注册消息
  override def preStart(): Unit = {

    //使用context.actorSelection方法查找master,需要:通信协议、masterip地址、master的端口、master对应的ActorSystem,actor层级
    master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")

    //master发送注册信息,包括:workeridmemorycores
    master ! RegisterWorker(workerId, memory, cores)

  }

  //2.重写receive方法接收消息
  override def receive: Receive = {

    case RegisteredWorker(masterUrl) => {
      println("接收到master返回的注册成功的消息为url: "+masterUrl)
      //接收到注册成功的消息后向master定时发送心跳(注册成功返回的消息为masterurl)
      //导入隐式转换
      import context.dispatcher

      //2.2定时调用发送心跳任务
      context.system.scheduler.schedule(0 millis, HEATBEAT_INTERVAL millis, self, SendHeartBeat)

    }
    //2.3在发送心跳的任务中给master发送自己的workerId
    case SendHeartBeat => {

      println("send heartBeat to master")
      master ! HeatBeat(workerId)
    }
  }
}

object Worker {
  def main(args: Array[String]): Unit = {
    //0.准备配置信息
    val host = "127.0.0.1"        //args(0)       //workerip地址
    val port = 10011              //args(1).toInt //worker的端口,可以更改端口模拟多个worker
    val masterHost = "127.0.0.1"  //args(2)       //masterip地址
    val masterPort = 10086        //args(3).toInt //master的端口
    val memory = 64               //args(4).toInt //worker的内存
    val cores = 16                //args(5).toInt //worker的核数
    val str =

      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
          """.stripMargin
    val config: Config = ConfigFactory.parseString(str)

    //1.创建ActorSystem对象
    val workerSystem: ActorSystem = ActorSystem.create("WorkerSystem", config)

    //2.获取WorkerActor的引用
    val worker: ActorRef = workerSystem.actorOf(Props(new Worker(masterHost, masterPort, memory, cores)), "Worker")

    //3.等待结束
    workerSystem.awaitTermination()

  }
}

 

 

 

4.运行

java -cp Scala_Action-1.0.jar cn.itcast.spark.Master 127.0.0.1 10086

java -cp Scala_Action-1.0.jar cn.itcast.spark.Worker 127.0.0.1 10010 127.0.0.1 10086 16 8

java -cp Scala_Action-1.0.jar cn.itcast.spark.Worker 127.0.0.1 10011 127.0.0.1 10086 32 16

java -cp Scala_Action-1.0.jar cn.itcast.spark.Worker 127.0.0.1 10012 127.0.0.1 10086 64 32

 

十一、作业

●知识点主次

掌握基础语法

理解Scala的高级特性,代码不做强制要求,但是必须能看懂!

了解Actor编程模型

理解Akka实现Spark通信

Scala中重点掌握: 集合和函数

后面我们学习Spark大数据内存计算框架就是使用Scala语言编写的,而大数据计算的就是数据!!所以集合操作很重要,集合操作又需要传递函数,所有函数很重要!!!(当然Spark对Scala中的集合进行的封装!操作起来更加简单!)

●Scala练习题

https://blog.csdn.net/xiangxizhishi/article/details/79060732

https://www.cnblogs.com/steamedbundad/p/scalaExercise.html

 

day52实时统计Salar01

原文地址:https://www.cnblogs.com/shan13936/p/14195292.html