• 算法乐园 主页 笔记 刷题




                  Scala教程

                  转载自黑马程序员

                  Scala第一章节

                  章节目标

                  1. 理解Scala的相关概述
                  2. 掌握Scala的环境搭建
                  3. 掌握Scala小案例: 做最好的自己

                  1. Scala简介

                  1.1 概述

                  Scala(斯嘎拉)这个名字来源于"Scalable Language(可伸缩的语言)", 它是一门基于JVM的多范式编程语言, 通俗的说: Scala是一种运行在JVM上的函数式的面向对象语言. 之所以这样命名, 是因为它的设计目标是: 随着用户的需求一起成长. Scala可被广泛应用于各种编程任务, 从编写小型的脚本到构建巨型系统, 它都能胜任. 正因如此, Scala得以提供一些出众的特性, 例如: 它集成了面向对象编程和面向函数式编程的各种特性, 以及更高层的并发模型.

                  总而言之, Scala融汇了许多前所未有的特性, 而同时又运行于JVM之上, 随着开发者对Scala的兴趣日增, 以及越来越多的工具支持, 无疑Scala语言将成为你手上一门必不可少的工具.

                  基于JVM解释:Scala的运行环境和Java类似, 也是依赖JVM的.

                  多范式解释: Scala支持多种编程风格

                  1.2 Scala之父

                  Scala之父是: Martin·Odersky(马丁·奥德斯基), 他是EPFL(瑞士领先的技术大学)编程研究组的教授. 也是Typesafe公司(现已更名为: Lightbend公司)的联合创始人. 他在整个职业生涯中一直不断追求着一个目标:让写程序这样一个基础工作变得高效、简单、且令人愉悦. 他曾经就职于IBM研究院、耶鲁大学、卡尔斯鲁厄大学以及南澳大利亚大学. 在此之前,他在瑞士苏黎世联邦理工学院追随Pascal语言创始人Niklaus Wirth(1984年图灵奖获得者)学习,并于1989年获得博士学位.

                  1574652913536

                  1.3 语言特点

                  2. Scala程序和Java程序对比

                  2.1 程序的执行流程对比

                  Java程序编译执行流程

                  1556551819121

                  Scala程序编译执行流程

                  1556551904384

                  2.2 代码对比

                  需求:

                  定义一个学生类, 属性为: 姓名和年龄, 然后在测试类中创建对象并测试.

                  Java代码

                  Scala代码

                   

                  3. Scala环境搭建

                  3.1 概述

                  scala程序运行需要依赖于Java类库,那么必须要有Java运行环境,scala才能正确执行. 所以要编译运行scala程序,需要:

                  接下来,需要依次安装以下内容:

                  3.2 安装JDK

                  安装JDK 1.8 64位版本,并配置好环境变量, 此过程略.

                  3.3 安装Scala SDK

                  Scala SDK是scala语言的编译器,要开发scala程序,必须要先安装Scala SDK

                  本次安装的版本是: 2.11.12

                  步骤

                  1. 下载Scala SDK.

                    官方下载地址: scala-lang.org/download/

                  2. 安装Scala SDK.

                    2.1 双击scala-2.11.12.msi,将scala安装在指定目录, 傻瓜式安装, 下一步下一步即可. 2.2 安装路径要合法, 不要出现中文, 空格等特殊符号.

                  3. 测试是否安装成功

                    打开控制台,输入: scala -version

                  3.4 安装IDEA scala插件

                  IDEA默认是不支持scala程序开发的,所以需要在IDEA中安装scala插件, 让它来支持scala语言。

                  步骤

                  3.4.1 下载指定版本IDEA scala插件.

                  1. 下载的Scala插件必须和你安装的IDEA版本一致

                  2. 官方下载地址: http://plugins.jetbrains.com/plugin/1347-scala

                  3.4.2 IDEA配置scala插件
                  1. 选择配置 > 选择插件

                  1556513374152

                  1. 点击小齿轮 > 选择从本地安装插件

                    1556513515767

                  2. 找到下载的插件位置,点击OK

                    12517593653

                  3.4.3 重新启动IDEA

                  12517593181

                   

                  4. Scala解释器

                  4.1 概述

                  后续我们会使用scala解释器来学习scala基本语法,scala解释器像Linux命令一样,执行一条代码,马上就可以让我们看到执行结果,用来测试比较方便。

                  我们接下来学习:

                  4.2 启动scala解释器

                  要启动scala解释器,只需要以下几步:

                  125175951

                  4.3 执行scala代码

                  在scala的命令提示窗口中输入println("hello, world"),回车执行.

                  12351182840

                  4.4 退出解释器

                  方式一: 点击右上角的"×"

                  12352183102

                  方式二: 输入:quit退出

                  12352183454

                   

                  5. 案例: 做最好的自己.

                  5.1 需求

                  提示用户录入他/她最想对自己说的一句话, 然后将这句话打印到控制台上.

                  5.2 目的

                  测试Scala和Java之间可以无缝互调(即: Scala兼容Java,可以访问庞大的Java类库).

                  5.3 思路分析

                  5.4 参考代码

                  5.5 小彩蛋

                  其实Scala中也有自己独有的接收用户键盘录入数据的功能, 远比上边的写法要简单的多, 要优雅的多. 这种写法在后续章节我们也会学习到, 预知后事如何, 且听下回分晓.

                   

                  Scala第二章节

                  章节目标

                  1. 掌握变量, 字符串的定义和使用
                  2. 掌握数据类型的划分和数据类型转换的内容
                  3. 掌握键盘录入功能
                  4. 理解Scala中的常量, 标识符相关内容

                  1. 输出语句和分号

                  1.1 输出语句

                  方式一: 换行输出

                  方式二: 不换行输出

                  注意: 不管是println(), 还是print()语句, 都可以同时打印多个值.格式为: println(值1, 值2, 值3...)

                  1.2 分号

                  Scala语句中, 单行代码最后的分号可写可不写. 如果是多行代码写在一行, 则中间的分号不能省略, 最后一条代码的分号可省略不写.

                  示例:

                   

                  2. Scala中的常量

                  2.1 概述

                  常量指的是: 在程序的运行过程中, 其值不能发生改变的量.

                  2.2 分类

                  2.3 代码演示

                   

                  3. Scala中的变量

                  3.1 概述

                  我们将来每一天编写scala程序都会定义变量, 那什么是变量, 它又是如何定义的呢?

                  变量, 指的就是在程序的执行过程中, 其值可以发生改变的量. 定义格式如下:

                  3.2 语法格式

                  Java变量定义

                  在scala中,可以使用val或者var来定义变量,语法格式如下:

                  其中

                  注意: scala中定义变量时, 类型写在变量名后面

                  3.3 示例

                  需求:定义一个变量保存一个人的名字"tom"

                  步骤

                  1. 打开scala解释器
                  2. 定义一个字符串类型的变量用来保存名字

                  参考代码

                  3.4 val和var变量的区别

                  示例

                  给名字变量进行重新赋值为Jim,观察其运行结果

                  参考代码

                  示例

                  使用var重新定义变量来保存名字"tom",并尝试重新赋值为Jim,观察其运行结果

                  参考代码

                  注意: 优先使用val定义变量,如果变量需要被重新赋值,才使用var

                  3.5 使用类型推断来定义变量

                  scala的语法要比Java简洁,我们可以使用一种更简洁的方式来定义变量。

                  示例

                  使用更简洁的语法定义一个变量保存一个人的名字"tom"

                  参考代码

                  scala可以自动根据变量的值来自动推断变量的类型,这样编写代码更加简洁。

                   

                  4. 字符串

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

                  4.1 使用双引号

                  语法

                  示例

                  有一个人的名字叫"hadoop",请打印他的名字以及名字的长度。

                  参考代码

                  4.2 使用插值表达式

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

                  语法

                  注意:

                  • 在定义字符串之前添加s
                  • 在字符串中,可以使用${}来引用变量或者编写表达式

                  示例

                  请定义若干个变量,分别保存:"zhangsan"、23、"male",定义一个字符串,保存这些信息。

                  打印输出:name=zhangsan, age=23, sex=male

                  参考代码

                  4.3 使用三引号

                  如果有大段的文本需要保存,就可以使用三引号来定义字符串。例如:保存一大段的SQL语句。三个引号中间的所有内容都将作为字符串的值。

                  语法

                  示例

                  定义一个字符串,保存以下SQL语句

                  打印该SQL语句

                  参考代码

                  4.4 扩展: 惰性赋值

                  在企业的大数据开发中,有时候会编写非常复杂的SQL语句,这些SQL语句可能有几百行甚至上千行。这些SQL语句,如果直接加载到JVM中,会有很大的内存开销, 如何解决这个问题呢?

                  当有一些变量保存的数据较大时,而这些数据又不需要马上加载到JVM内存中。就可以使用惰性赋值来提高效率。

                  语法格式:

                  示例

                  在程序中需要执行一条以下复杂的SQL语句,我们希望只有用到这个SQL语句才加载它。

                  参考代码

                   

                  5. 标识符

                  5.1 概述

                  实际开发中, 我们会编写大量的代码, 这些代码中肯定会有变量, 方法, 类等. 那它们该如何命名呢? 这就需要用到标识符了. 标识符就是用来给变量, 方法, 类等起名字的. Scala中的标识符和Java中的标识符非常相似.

                  5.2 命名规则

                  5.3 命名规范

                   

                  6. 数据类型

                  6.1 简述

                  数据类型是用来约束变量(常量)的取值范围的. Scala也是一门强类型语言, 它里边的数据类型绝大多数和Java一样.我们主要来学习

                  6.2 数据类型

                  基础类型类型说明
                  Byte8位带符号整数
                  Short16位带符号整数
                  Int32位带符号整数
                  Long64位带符号整数
                  Char16位无符号Unicode字符
                  StringChar类型的序列(字符串)
                  Float32位单精度浮点数
                  Double64位双精度浮点数
                  Booleantrue或false

                  注意下 scala类型与Java的区别

                  [!NOTE]

                  1. scala中所有的类型都使用大写字母开头
                  2. 整形使用Int而不是Integer
                  3. scala中定义变量可以不写类型,让scala编译器自动推断
                  4. Scala中默认的整型是Int, 默认的浮点型是: Double

                  6.3 Scala类型层次结构

                  1556592270468

                  类型说明
                  Any所有类型的父类,它有两个子类AnyRef与AnyVal
                  AnyVal所有数值类型的父类
                  AnyRef所有对象类型(引用类型)的父类
                  Unit表示空,Unit是AnyVal的子类,它只有一个的实例{% em %}() {% endem %} 它类似于Java中的void,但scala要比Java更加面向对象
                  NullNull是AnyRef的子类,也就是说它是所有引用类型的子类。它的实例是{% em %}null{% endem %} 可以将null赋值给任何对象类型
                  Nothing所有类型的子类, 不能直接创建该类型实例,某个方法抛出异常时,返回的就是Nothing类型,因为Nothing是所有类的子类,那么它可以赋值为任何类型

                  6.4 思考题

                  以下代码是否有问题?

                  Scala会解释报错: Null类型并不能转换为Int类型,说明Null类型并不是Int类型的子类

                   

                  7. 类型转换

                  7.1 概述

                  当Scala程序在进行运算或者赋值动作时, 范围小的数据类型值会自动转换为范围大的数据类型值, 然后再进行计算.例如: 1 + 1.1的运算结果就是一个Double类型的2.1. 而有些时候, 我们会涉及到一些类似于"四舍五入"的动作, 要把一个小数转换成整数再来计算. 这些内容就是Scala中的类型转换.

                  Scala中的类型转换分为值类型的类型转换引用类型的类型转换, 这里我们先重点介绍:值类型的类型转换.

                  值类型的类型转换分为:

                  • 自动类型转换
                  • 强制类型转换

                  7.2 自动类型转换

                  1. 解释

                    范围小的数据类型值会自动转换为范围大的数据类型值, 这个动作就叫: 自动类型转换.

                    自动类型转换从小到大分别为:Byte, Short, Char -> Int -> Long -> Float -> Double

                  2. 示例代码

                  7.3 强制类型转换

                  1. 解释

                    范围大的数据类型值通过一定的格式(强制转换函数)可以将其转换成范围小的数据类型值, 这个动作就叫: 强制类型转换.

                    注意: 使用强制类型转换的时候可能会造成精度缺失问题!

                  2. 格式

                  1. 参考代码

                  7.4 值类型和String类型之间的相互转换

                  1. 值类型的数据转换成String类型

                  格式一:

                  格式二:

                  示例

                  将Int, Double, Boolean类型的数据转换成其对应的字符串形式.

                  参考代码:

                  2. String类型的数据转换成其对应的值类型

                  格式:

                  注意:

                  • String类型的数据转成Char类型的数据, 方式有点特殊, 并不是调用toChar, 而是toCharArray
                  • 这点目前先了解即可, 后续我们详细解释

                  需求:

                  将字符串类型的整数, 浮点数, 布尔数据转成其对应的值类型数据.

                  参考代码:

                   

                  8. 键盘录入

                  8.1 概述

                  前边我们涉及到的数据, 都是我们写"死"的, 固定的数据, 这样做用户体验并不是特别好. 那如果这些数据是由用户录入, 然后我们通过代码接收, 就非常好玩儿了. 这就是接下来我们要学习的Scala中的"键盘录入"功能.

                  8.2 使用步骤

                  1. 导包

                    格式: import scala.io.StdIn

                  2. 通过StdIn.readXxx()来接收用户键盘录入的数据

                    接收字符串数据: StdIn.readLine()

                    接收整数数据: StdIn.readInt()

                  8.3 示例

                   

                  9. 案例: 打招呼

                  9.1 概述

                  聊了这么久, 赶紧来和小伙伴儿们来打个招呼吧.

                  需求: 提示用户录入他/她的姓名和年龄, 接收并打印.

                  9.2 具体步骤

                  1. 提示用户录入姓名.
                  2. 接收用户录入的姓名.
                  3. 提示用户录入年龄.
                  4. 接收用户录入的年龄.
                  5. 将用户录入的数据(姓名和年龄)打印到控制台上.

                  9.3 参考代码

                  Scala第三章节

                  章节目标

                  1. 理解运算符的相关概述
                  2. 掌握算术, 赋值, 关系, 逻辑运算符的用法
                  3. 掌握交换变量案例
                  4. 理解位运算符的用法

                  1. 算术运算符

                  1.1 运算符简介

                  用来拼接变量或者常量的符号就叫: 运算符, 而通过运算符连接起来的式子就叫: 表达式. 实际开发中, 我们会经常用到它.

                  例如:

                  10 + 3 这个就是一个表达式, 而+号, 就是一个运算符.

                  注意: 在Scala中, 运算符并不仅仅是运算符, 也是函数的一种, 这点大家先了解即可, 后续我们详细讲解.

                  1.2 运算符的分类

                  1.3 算术运算符

                  算术运算符指的就是用来进行算术操作的符号, 常用的有以下几种:

                  运算符功能解释
                  +加号, 功能有3点. 1) 表示正数 2) 普通的加法操作 3) 字符串的拼接
                  -减号, 功能有2点. 1) 表示负数 2) 普通的减法操作
                  *乘号, 用于获取两个数据的乘积
                  /除法, 用于获取两个数据的商
                  %取余(也叫取模), 用于获取两个数据的余数

                  注意:

                  1. Scala中是没有++, --这两个算术运算符的, 这点和Java中不同.

                  2. 整数相除的结果, 还是整数. 如果想获取到小数, 则必须有浮点型数据参与.

                    例如: 10 / 3 结果是3 10 / 3.0 结果是: 3.3333(无限循环)

                  3. 关于+号拼接字符串: 任意类型的数据和字符串拼接, 结果都将是一个新的字符串.

                  4. 关于%操作, 假设求a % b的值, 它的底层原理其实是: a - a/b * b

                  1.4 代码演示

                  需求: 演示算术运算符的常见操作.

                  参考代码:

                   

                  2. 赋值运算符

                  2.1 概述

                  赋值运算符指的就是用来进行赋值操作的符号. 例如: 把一个常量值, 或者一个变量值甚至是某一段代码的执行结果赋值给变量, 这些都要用到赋值运算符.

                  2.2 分类

                  2.3 代码演示

                   

                  3. 关系运算符

                  3.1 概述

                  关系运算符指的就是用来进行比较操作的符号. 例如: 数据是否相等, 是否不等, 数据1大还是数据2大...等这些操作.

                  3.2 分类

                  运算符功能解释
                  >用来判断前边的数据是否大于后边的数据
                  >=用来判断前边的数据是否大于或者等于后边的数据
                  <用来判断前边的数据是否小于后边的数据
                  <=用来判断前边的数据是否小于或者等于后边的数据
                  ==用来判断两个数据是否相等
                  !=用来判断两个数据是否不等

                  注意:

                  1. 关系表达式不管简单还是复杂, 最终结果一定是Boolean类型的值, 要么是true, 要么是false.
                  2. 千万不要把==写成=, 否则结果可能不是你想要的.

                  3.3 代码演示

                  3.4 关系运算符延伸

                  学过Java的同学会发现, 上述的Scala中的关系运算符用法和Java中是一样的, 那有和Java不一样的地方吗?

                  答案是: 有.

                  需求描述Scala代码Java代码
                  比较数据值== 或者 !=equals()方法
                  比较引用值(地址值)eq方法== 或者 !=

                  示例

                  有一个字符串"abc",再创建第二个字符串,值为:在第一个字符串后拼接一个空字符串。

                  然后使用比较这两个字符串是否相等、再查看它们的引用值是否相等。

                  参考代码

                   

                  4. 逻辑运算符

                  4.1 概述

                  逻辑运算符指的就是用来进行逻辑操作的符号. 可以简单理解为它是: 组合判断. 例如: 判断多个条件是否都满足, 或者满足其中的某一个, 甚至还可以对某个判断结果进行取反操作.

                  4.2 分类

                  运算符功能解释
                  &&逻辑与, 要求所有条件都满足(即: 结果为true), 简单记忆: 有false则整体为false.
                  ||逻辑或, 要求只要满足任意一个条件即可, 简单记忆: 有true则整体为true.
                  !逻辑非, 用来进行取反操作的. 即: 以前为true, 取反后为false, 以前为false, 取反后为true.

                  注意:

                  1. 逻辑表达式不管简单还是复杂, 最终结果一定是Boolean类型的值, 要么是true, 要么是false.

                  2. 在Scala代码中, 不能对一个Boolean类型的数据进行连续取反操作, 但是在Java中是可以的.

                    • 即: !!false, 这样写会报错, 不支持这种写法.

                  4.3 代码演示

                   

                  5. 位运算符

                  5.1 铺垫知识

                  要想学好位运算符, 你必须得知道三个知识点:

                  1. 什么是进制
                  2. 什么是8421码
                  3. 整数的原码, 反码, 补码计算规则
                  5.1.1 关于进制

                  通俗的讲, 逢几进一就是几进制, 例如: 逢二进一就是二进制, 逢十进一就是十进制, 常用的进制有以下几种:

                  进制名称数据组成规则示例
                  二进制数据以0b(大小写均可)开头, 由数字0和1组成0b10001001, 0b00101010
                  八进制数据以0开头, 由数字0~7组成064, 011
                  十进制数据直接写即可, 无特殊开头, 由数字0~9组成10, 20, 333
                  十六进制数据以0x(大小写均可)开头, 由数字0~9, 字母A-F组成(大小写均可)0x123F, 0x66ABC

                  注意:

                  关于二进制的数据, 最前边的那一位叫: 符号位, 0表示正数, 1表示负数. 其他位叫: 数值位.

                  例如: 0b10001001 结果就是一个: 负数, 0b00101010 结果就是一个: 正数.

                  5.1.2 关于8421码

                  8421码就是用来描述二进制位和十进制数据之间的关系的, 它可以帮助我们快速的计算数据的二进制或十进制形式.

                  8421码对应关系如下:

                  二进制位 0 0 0 0 0 0 0 0

                  对应的十进制数据 128 64 32 16 8 4 2 1

                  注意:

                  5.1.3 关于整数的原反补码计算规则

                  所谓的原反补码, 其实指的都是二进制数据, 把十进制的数据转成其对应的二进制数据, 该二进制数据即为: 原码.

                  注意: 计算机底层存储, 操作和运算数据, 都是采用数据的二进制补码形式来实现的.

                  5.2 概述

                  位运算符指的就是按照位(Bit)来快速操作数据值, 它只针对于整型数据. 因为计算机底层存储, 操作, 运算采用的都是数据的二进制补码形式, 且以后我们要经常和海量的数据打交道, 为了提高计算效率, 我们就可以使用位运算符来实现快速修改数据值的操作.

                  5.3 分类

                  运算符功能解释
                  &按位与, 规则: 有0则0, 都为1则为1.
                  |按位或, 规则: 有1则1, 都为0则为0.
                  ^按位异或, 规则: 相同为0, 不同为1.
                  ~按位取反, 规则: 0变1, 1变0.
                  <<按位左移, 规则: 每左移一位, 相当于该数据乘2, 例如: 2 << 1, 结果为4
                  >>按位右移, 规则: 每右移一位, 相当于该数据除2, 例如: 6 >> 1, 结果为3

                  注意:

                  1. 位运算符只针对于整型数据.
                  2. 运算符操作的是数据的二进制补码形式.
                  3. 小技巧: 一个数字被同一个数字位异或两次, 该数字值不变. 即: 10 ^ 20 ^ 20, 结果还是10

                  5.4 代码演示

                   

                  6. 案例: 交换两个变量的值

                  6.1 需求

                  已知有两个Int类型的变量a和b, 初始化值分别为10和20, 请写代码实现变量a和变量b的值的交换.

                  即最终结果为: a=20, b=10.

                  注意: 不允许直接写a=20, b=10这种代码.

                  6.2 参考代码

                  Scala第四章节

                  章节目标

                  1. 掌握分支结构的格式和用法
                  2. 掌握for循环和while循环的格式和用法
                  3. 掌握控制跳转语句的用法
                  4. 掌握循环案例
                  5. 理解do.while循环的格式和用法

                  1. 流程控制结构

                  1.1 概述

                  在实际开发中, 我们要编写成千上万行代码, 代码的顺序不同, 执行结果肯定也会受到一些影响, 并且有些代码是满足特定条件才能执行的, 有些代码是要重复执行的. 那如何合理规划这些代码呢? 这就需要用到: 流程控制结构了.

                  1.2 分类

                  2. 顺序结构

                  2.1 概述

                  顺序结构是指: 程序是按照从上至下, 从左至右的顺序, 依次逐行执行的, 中间没有任何判断和跳转.

                  如图:

                  1575884799644

                  注意: 顺序结构是Scala代码的默认流程控制结构.

                  2.2 代码演示

                  2.3 思考题

                  下边这行代码的打印结果应该是什么呢?

                  提示: 代码是按照从上至下, 从左至右的顺序, 依次逐行执行的.

                   

                  3. 选择结构(if语句)

                  3.1 概述

                  选择结构是指: 某些代码的执行需要依赖于特定的判断条件, 如果判断条件成立, 则代码执行, 否则, 代码不执行.

                  3.2 分类

                  3.3 单分支

                  所谓的单分支是指: 只有一个判断条件的if语句.

                  3.3.1 格式

                  注意: 关系表达式不管简单还是复杂, 结果必须是Boolean类型的值.

                  3.3.2 执行流程
                  1. 先执行关系表达式, 看其结果是true还是false.

                  2. 如果是true, 则执行具体的代码, 否则, 不执行.

                  3. 如图:

                    1575884924641

                  3.3.3 示例

                  需求:

                  定义一个变量记录某个学生的成绩, 如果成绩大于或者等于60分, 则打印: 分数及格.

                  参考代码

                  3.4 双分支

                  所谓的双分支是指: 只有两个判断条件的if语句.

                  3.4.1 格式
                  3.4.2 执行流程
                  1. 先执行关系表达式, 看其结果是true还是false.
                  2. 如果是true, 则执行代码1. 如果是false, 则执行代码2.
                  3. 如图:

                  1575884877662

                  3.4.3 示例

                  需求:

                  定义一个变量记录某个学生的成绩, 如果成绩大于或者等于60分, 则打印: 分数及格, 否则打印分数不及格.

                  参考代码

                  3.5 多分支

                  所谓的多分支是指: 有多个判断条件的if语句.

                  3.5.1 格式
                  3.5.2 执行流程
                  1. 先执行关系表达式1, 看其结果是true还是false.
                  2. 如果是true, 则执行代码1, 分支语句结束. 如果是false, 则执行关系表达式2, 看其结果是true还是false.
                  3. 如果是true, 则执行代码2. 分支语句结束. 如果是false, 则执行关系表达式3, 看其结果是true还是false.
                  4. 以此类推, 直到所有的关系表达式都不满足, 执行最后一个else中的代码.
                  5. 如图:

                  1575885256325

                  3.5.3 示例

                  需求:

                  定义一个变量记录某个学生的成绩, 根据成绩发放对应的奖励, 奖励机制如下:

                  [90, 100] -> VR设备一套

                  [80, 90) -> 考试卷一套

                  [0, 80) -> 组合拳一套

                  其他 -> 成绩无效

                  参考代码

                  3.6 注意事项

                  if语句在使用时, 要注意的事项有以下三点:

                  1. 和Java一样, 在Scala中, 如果大括号{}内的逻辑代码只有一行, 则大括号可以省略.
                  2. 在scala中,条件表达式也是有返回值的
                  3. 在scala中,没有三元表达式,可以使用if表达式替代三元表达式

                  示例

                  定义一个变量sex,再定义一个result变量,如果sex等于"male",result等于1,否则result等于0

                  参考代码

                  3.7 嵌套分支

                  有些时候, 我们会涉及到"组合判断", 即一个分支结构中又嵌套了另一个分支结构, 这种写法就叫嵌套分支. 里边的那个分支结构叫: 内层分支, 外边的那个分支结构叫: 外层分支.

                  示例

                  定义三个变量a,b,c, 初始化值分别为: 10, 20, 30, 通过if分支语句, 获取其中的最大值.

                  思路分析

                  1. 定义三个变量a, b, c, 分别记录要进行操作的值.
                  2. 定义变量max, 用来记录获取到的最大值.
                  3. 先判断a是否大于或者等于b.
                  4. 条件成立, 说明 a大(或者等于b), 接着比较a和c的值, 获取最大值, 并将结果赋值给变量max
                  5. 条件不成立, 说明 b大, 接着比较b和c的值, 获取最大值, 并将结果赋值给变量max
                  6. 此时, max记录的就是a, b, c这三个变量的最大值, 打印即可.

                  参考代码

                  注意: 嵌套一般不超过3层.

                  3.8 扩展: 块表达式

                  问题

                  请问以下代码,变量a的值是什么?

                   

                  4. 循环结构

                  4.1 概述

                  循环,指的是事物周而复始的变化。而Scala中的循环结构,是指: 使一部分代码按照次数或一定的条件反复执行的一种代码结构。例如: 打印10次"Hello, Scala!", 如果纯写输出语句, 需要写10次, 而通过循环来实现的话, 输出语句只需要写1次, 这样就变得很简单了.

                  4.2 分类

                  注意: 这三种循环推荐使用for循环, 因为它的语法更简洁, 更优雅.

                  4.3 for循环

                  在Scala中, for的格式和用法和Java中有些差异, Scala中的for表达式功能更加强大.

                  4.3.1 格式

                  注意: 执行流程和Java一致

                  4.3.2 简单循环

                  需求:

                  打印10次"Hello, Scala!"

                  参考代码:

                  上述代码可以简写成:

                  4.3.3 嵌套循环

                  需求: 使用for表达式,打印以下字符, 每次只能输出一个"*"

                  步骤

                  1. 使用for表达式打印3行,5列星星
                  2. 每打印5个星星,换行

                  参考代码

                  4.3.4 守卫

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

                  语法

                  示例

                  使用for表达式打印1-10之间能够整除3的数字

                  参考代码

                  4.4.5 for推导式

                  Scala中的for循环也是有返回值的, 在for循环体中,可以使用yield表达式构建出一个集合(可以简单理解为: 就是一组数据),我们把使用yield的for表达式称之为推导式.

                  示例

                  生成一个10、20、30...100的集合

                  参考代码

                  4.4 while循环

                  scala中while循环和Java中是一致的, 所以学起来非常简单.

                  4.4.1 格式
                  4.4.2 执行流程
                  1. 执行初始化条件.
                  2. 执行判断条件, 看其结果是true还是false.
                  3. 如果是false则循环结束.
                  4. 如果是true则执行循环体.
                  5. 执行控制条件.
                  6. 返回第二步, 重复执行.
                  4.4.3 示例

                  需求:

                  打印1-10的数字

                  参考代码

                  4.5 do.while循环

                  scala中do.while循环和Java中是一致的, 所以学起来非常简单.

                  4.4.1 格式
                  4.4.2 执行流程
                  1. 执行初始化条件.
                  2. 执行循环体.
                  3. 执行控制条件.
                  4. 执行判断条件, 看其结果是true还是false.
                  5. 如果是false则循环结束.
                  6. 如果是true则返回第2步继续执行.

                  注意:

                  4.4.3 示例

                  需求:

                  打印1-10的数字

                  参考代码

                  4.6 break和continue

                  4.6.1 实现break

                  用法

                  1. 导包.

                    import scala.util.control.Breaks._

                  2. 使用breakable将for表达式包起来

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

                  示例

                  使用for表达式打印1-10的数字,如果遇到数字5,则退出for表达式

                  参考代码

                  4.6.2 实现continue

                  用法

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

                  注意:

                  1. 实现break是用breakable{}将整个for表达式包起来.
                  2. 而实现continue是用breakable{}将for表达式的循环体包含起来就可以了.

                  示例

                  用for表达式打印1~10之间, 所有不能整除3的数字.

                   

                  5. 综合案例

                  5.1 九九乘法表

                  需求:

                  打印九九乘法表, 如下图:

                  1575948461338

                  步骤

                  1. 通过外循环控制打印的行数.

                  2. 通过内循环控制每行打印的列数.

                    注意: 因为列数是随着行数递增的, 即:

                    行数该行的总列数
                    11
                    22
                    33
                    nn

                    结论: 如果用i表示行数, 那么该行的列数取值范围为: [1, i]

                  参考代码

                  5.2 模拟登陆

                  需求:

                  老王要登陆黑马官网学习Scala, 假设老王的账号和密码分别为"itcast", "heima", 且同一账号只有3次登陆机会, 如果3次都录入错误, 则提示账号被锁定. 请用所学模拟该场景.

                  步骤

                  1. 导包

                    • scala.io.StdIn
                    • scala.util.control.Breaks._
                  2. 定义变量, 记录用户录入的账号和密码.

                  3. 因为涉及到break的动作, 所以要用breakable{}把整个for表达式包裹起来

                  4. 因为只有3次登陆机会, 所以推荐使用for循环.

                  5. 提示用户录入他/她的账号和密码, 并接收.

                  6. 判断用户录入的账号和密码是否正确.

                  7. 如果录入正确, 则提示"登陆成功, 开始学习Scala!", 循环结束.

                  8. 如果录入错误, 则判断是否还有登陆机会

                    • 有, 则提示"用户名或者密码错误, 您还有*次机会", 然后返回第5步继续执行.
                    • 没有, 则提示"账号被锁定, 请与管理员联系", 循环结束.

                  参考代码

                   

                  Scala第五章节

                  章节目标

                  1. 掌握方法的格式和用法
                  2. 掌握函数的格式和用法
                  3. 掌握九九乘法表案例

                  1. 方法

                  1.1 概述

                  实际开发中, 我们需要编写大量的逻辑代码, 这就势必会涉及到重复的需求. 例如: 求10和20的最大值, 求11和22的最大值, 像这样的需求, 用来进行比较的逻辑代码需要编写两次, 而如果把比较的逻辑代码放到方法中, 只需要编写一次就可以了, 这就是方法. scala中的方法和Java方法类似, 但scala与Java定义方法的语法是不一样的。

                  1.2 语法格式

                  注意:

                  • 参数列表的参数类型不能省略
                  • 返回值类型可以省略,由scala编译器自动推断
                  • 返回值可以不写return,默认就是{}块表达式的值

                  1.3 示例

                  需求:

                  1. 定义一个方法getMax,用来获取两个整型数字的最大值, 并返回结果(最大值).
                  2. 调用该方法获取最大值, 并将结果打印到控制台上.

                  参考代码

                  1.4 返回值类型推断

                  scala定义方法可以省略返回值的数据类型,由scala自动推断返回值类型。这样方法定义后更加简洁。

                  注意: 定义递归方法,不能省略返回值类型

                  示例

                  定义递归方法, 求5的阶乘.

                  步骤

                  1. 定义方法factorial, 用来计算某个数字的阶乘

                    规律: 1的阶乘等于1, 其他数字的阶乘为: n! = n * (n - 1)!

                  2. 调用方法, 获取5的阶乘, 并将结果打印到控制台上.

                  参考代码

                  1.5 惰性方法

                  当记录方法返回值的变量被声明为lazy时, 方法的执行将被推迟, 直到我们首次使用该值时, 方法才会执行, 像这样的方法, 就叫: 惰性方法.

                  注意:

                  使用场景:

                  1. 打开数据库连接

                    由于表达式执行代价昂贵, 因此我们希望能推迟该操作, 直到我们确实需要表达式结果值时才执行它

                  2. 提升某些特定模块的启动时间.

                    为了缩短模块的启动时间, 可以将当前不需要的某些工作推迟执行

                  3. 确保对象中的某些字段能优先初始化

                    为了确保对象中的某些字段能优先初始化, 我们需要对其他字段进行惰性化处理

                  需求

                  定义一个方法用来获取两个整数和, 通过"惰性"技术调用该方法, 然后打印结果.

                  参考代码

                   

                  1.6 方法参数

                  scala中的方法参数,使用比较灵活。它支持以下几种类型的参数:

                  1.6.1 默认参数

                  在定义方法时可以给参数定义一个默认值。

                  示例

                  1. 定义一个计算两个整数和的方法,这两个值分别默认为10和20
                  2. 调用该方法,不传任何参数

                  参考代码

                  1.6.2 带名参数

                  在调用方法时,可以指定参数的名称来进行调用。

                  示例

                  1. 定义一个计算两个整数和的方法,这两个值分别默认为10和20
                  2. 调用该方法,只设置第一个参数的值

                  参考代码

                  1.6.3 变长参数

                  如果方法的参数是不固定的,可以将该方法的参数定义成变长参数。

                  语法格式:

                  注意:

                  示例一:

                  1. 定义一个计算若干个值相加的方法
                  2. 调用方法,传入以下数据:1,2,3,4,5

                  参考代码

                  1.7 方法调用方式

                  在scala中,有以下几种方法调用方式:

                  注意: 在编写spark、flink程序时,会经常使用到这些方法调用方式。

                  1.7.1 后缀调用法

                  这种方法与Java没有区别, 非常简单.

                  语法

                  示例

                  使用后缀法调用Math.abs, 用来求绝对值

                  参考代码

                   

                  1.7.2 中缀调用法

                  语法

                  例如:1 to 10

                  注意: 如果有多个参数,使用括号括起来

                  示例

                  使用中缀法调用Math.abs, 用来求绝对值

                  扩展: 操作符即方法

                  来看一个表达式, 大家觉得这个表达式像不像方法调用?

                  在scala中,+ - * / %等这些操作符和Java一样,但在scala中,

                   

                  1.7.3 花括号调用法

                  语法

                  注意: 方法只有一个参数,才能使用花括号调用法

                  示例

                  使用花括号调用法Math.abs求绝对值

                  参考代码

                   

                  1.7.4 无括号调用法

                  如果方法没有参数,可以省略方法名后面的括号

                  示例

                  参考代码

                  注意:

                  1. 在Scala中, 如果方法的返回值类型是Unit类型, 这样的方法称之为过程(procedure)
                  2. 过程的等号(=)可以省略不写. 例如:

                   

                  2. 函数

                  scala支持函数式编程,将来编写Spark/Flink程序会大量使用到函数, 目前, 我们先对函数做一个简单入门, 在后续的学习过程中, 我们会逐步重点讲解函数的用法.

                  2.1 定义函数

                  语法

                  注意:

                  • 在Scala中, 函数是一个对象(变量)
                  • 类似于方法,函数也有参数列表和返回值
                  • 函数定义不需要使用def定义
                  • 无需指定返回值类型

                  2.2 示例

                  需求:

                  1. 定义一个计算两个整数和的函数
                  2. 调用该函数

                  参考代码

                  2.3 方法和函数的区别

                  在Java中, 方法和函数之间没有任何区别, 只是叫法不同. 但是在Scala中, 函数和方法就有区别了, 具体如下:

                  结论: 在Scala中, 函数是对象, 而方法是属于对象的, 所以可以理解为: 方法归属于函数.

                  示例

                  演示方法无法赋值给变量

                  2.4 方法转换为函数

                  有时候需要将方法转换为函数. 例如: 作为变量传递,就需要将方法转换为函数

                  格式

                  注意: 使用_即可将方法转换为函数

                  示例

                  1. 定义一个方法用来计算两个整数和
                  2. 将该方法转换为一个函数,并赋值给变量

                  参考代码

                   

                  3. 案例: 打印nn乘法表

                  3.1 需求

                  定义方法实现, 根据用户录入的整数, 打印对应的乘法表。

                  例如: 用户录入5,则打印55乘法表,用户录入9,则打印99乘法表。

                  3.2 目的

                  1. 考察键盘录入和方法, 函数的综合运用.
                  2. 体会方法和函数的不同.

                  3.3 步骤

                  1. 定义方法(或者函数), 接收一个整型参数.
                  2. 通过for循环嵌套实现, 根据传入的整数, 打印对应的乘法表.
                  3. 调用方法(函数), 输出结果.

                  3.4 参考代码

                   

                  Scala第六章节

                  章节目标

                  1. 掌握类和对象的定义
                  2. 掌握访问修饰符和构造器的用法
                  3. 掌握main方法的实现形式
                  4. 掌握伴生对象的使用
                  5. 掌握定义工具类的案例

                  1. 类和对象

                  Scala是一种函数式的面向对象语言, 它也是支持面向对象编程思想的,也有类和对象的概念。我们依然可以基于Scala语言来开发面向对象的应用程序。

                  1.1 相关概念

                  什么是面向对象?

                  面向对象的三大思想特点是什么?

                  面试题: 什么是面向对象? 思路: 概述, 特点, 举例, 总结.

                  什么是类?

                  • 属性(也叫成员变量): 名词, 用来描述事物的外在特征的.
                  • 行为(也叫成员方法): 动词, 表示事物能够做什么.
                  • 例如: 学生有姓名和年龄(这些是属性), 学生要学习, 要吃饭(这些是行为).

                  什么是对象?

                  面向对象的三大特征是什么?

                  1.2 创建类和对象

                  Scala中创建类和对象可以通过class和new关键字来实现. 用class来创建类, 用new来创建对象.

                  1.2.1 示例

                  创建一个Person类,并创建它的对象, 然后将对象打印到控制台上.

                  1.2.2 步骤
                  1. 创建一个scala项目,并创建一个object类

                    注意: object修饰的类是单例对象, 这点先了解即可, 稍后会详细解释.

                  2. 在object类中添加main方法.

                  3. 创建Person类, 并在main方法中创建Person类的对象, 然后输出结果.

                  1.2.3 实现

                  1.3 简写方式

                  用法

                  示例

                  使用简写方式重新创建Person类和对象, 并打印对象.

                  参考代码

                   

                  2. 定义和访问成员变量

                  一个类会有自己的属性,例如:人类,就有自己的姓名和年龄。我们接下来学习如何在类中定义和访问成员变量。

                  2.1 用法

                  2.2 示例

                  需求

                  1. 定义一个Person类,包含一个姓名和年龄字段
                  2. 创建一个名为"张三"、年龄为23岁的对象
                  3. 打印对象的名字和年龄

                  步骤

                  1. 创建一个object类,添加main方法
                  2. 创建Person类,添加姓名字段和年龄字段,并对字段进行初始化,让scala自动进行类型推断
                  3. 在main方法中创建Person类对象,设置成员变量为"张三"、23
                  4. 打印对象的名字和年龄

                  参考代码

                   

                  3. 使用下划线初始化成员变量

                  scala中有一个更简洁的初始化成员变量的方式,可以让代码看起来更加简洁, 更优雅.

                  3.1 用法

                  3.2 示例

                  需求

                  1. 定义一个Person类,包含一个姓名和年龄字段
                  2. 创建一个名为"张三"、年龄为23岁的对象
                  3. 打印对象的名字和年龄

                  步骤

                  1. 创建一个object类,添加main方法
                  2. 创建Person类,添加姓名字段和年龄字段,指定数据类型,使用下划线初始化
                  3. 在main方法中创建Person类对象,设置成员变量为"张三"、23
                  4. 打印对象的名字和年龄

                  参考代码

                   

                  4. 定义和访问成员方法

                  类可以有自己的行为,scala中也可以通过定义成员方法来定义类的行为。

                  4.1 格式

                  在scala的类中,也是使用def来定义成员方法的.

                  注意: 返回值的类型可以不写, 由Scala自动进行类型推断.

                  4.2 示例

                  需求

                  1. 创建一个Customer类

                    1557322180244

                  2. 创建一个该类的对象,并调用printHello方法

                  步骤

                  1. 创建一个object类,添加main方法
                  2. 创建Customer类,添加成员变量、成员方法
                  3. 在main方法中创建Customer类对象,设置成员变量值(张三、男)
                  4. 调用成员方法

                  参考代码

                   

                  5. 访问权限修饰符

                  和Java一样,scala也可以通过访问修饰符,来控制成员变量和成员方法是否可以被外界访问。

                  5.1 定义

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

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

                    注意: Scala中的权限修饰符只有: private, private[this], protected, 默认这四种.

                  5.2 案例

                  需求

                  参考代码

                   

                  6. 类的构造器

                  当创建对象的时候,会自动调用类的构造器。之前使用的都是默认构造器,接下来我们要学习如何自定义构造器。

                  6.1 分类

                  6.2 主构造器

                  语法

                  注意:

                  • 主构造器的参数列表直接定义在类名后面,添加了val/var表示直接通过主构造器定义成员变量
                  • 构造器参数列表可以指定默认值
                  • 创建实例,调用构造器可以指定字段进行初始化
                  • 整个class中除了字段定义和方法定义的代码都是构造代码

                  示例

                  1. 定义一个Person类,通过主构造器参数列表定义姓名和年龄字段,并且设置它们的默认值为张三, 23
                  2. 在主构造器中输出"调用主构造器"
                  3. 创建"李四"对象(姓名为李四,年龄为24),打印对象的姓名和年龄
                  4. 创建"空"对象,不给构造器传入任何的参数,打印对象的姓名和年龄
                  5. 创建"测试"对象,不传入姓名参数,仅指定年龄为30,打印对象的姓名和年龄

                  参考代码

                  6.2 辅助构造器

                  在scala中,除了定义主构造器外,还可以根据需要来定义辅助构造器。例如:允许通过多种方式,来创建对象,这时候就可以定义其他更多的构造器。我们把除了主构造器之外的构造器称为辅助构造器

                  语法

                  注意: 辅助构造器的第一行代码,必须要调用主构造器或者其他辅助构造器

                  示例

                  需求

                  注意:

                  参考代码

                   

                  7. 单例对象

                  scala中是没有static关键字的,要想定义类似于Java中的static变量、static方法,就要使用到scala中的单例对象了, 也就是object.

                  7.1 定义单例对象

                  单例对象表示全局仅有一个对象, 也叫孤立对象. 定义单例对象和定义类很像,就是把class换成object.

                  格式

                  注意:

                  示例

                  需求

                  参考代码

                  7.2 在单例对象中定义方法

                  在单例对象中定义的成员方法类似于Java中的静态方法.

                  示例

                  需求

                  参考代码

                   

                  8. main方法

                  scala和Java一样,如果要运行一个程序,必须有一个main方法。在Java中main方法是静态的,而在scala中没有静态方法。所以在scala中,这个main方法必须放在一个单例对象中。

                  8.1 定义main方法

                  main方法

                  示例

                  需求

                  参考代码

                  8.2 继承App特质

                  创建一个object, 继承自App特质(Trait),然后将需要编写在main方法中的代码,写在object的构造方法体内。

                  示例

                  需求

                  参考代码

                   

                  9. 伴生对象

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

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

                  9.1 定义伴生对象

                  一个class和object具有同样的名字。这个object称为伴生对象,这个class称为伴生类

                  示例

                  需求

                  参考代码

                  9.2 private[this]访问权限

                  如果某个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问.

                  示例

                  示例说明

                  示例代码

                  注意: 上述代码,会编译报错。但移除掉[this]就可以访问了

                  9.3 apply方法

                  在Scala中, 支持创建对象的时候, 免new的动作, 这种写法非常简便,优雅。要想实现免new, 我们就要通过伴生对象的apply方法来实现。

                  9.3.1 格式

                  定义apply方法的格式

                  创建对象

                  例如: val p = Person("张三", 23)

                  9.3.2 示例

                  需求

                  参考代码

                   

                  10. 案例: 定义工具类

                  10.1 概述

                  Scala中工具类的概念和Java中是一样的, 都是

                  综上所述, 在Scala中只有object单例对象满足上述的要求.

                  10.2 示例

                  需求

                  步骤

                  参考代码

                  Scala第七章节

                  章节目标

                  1. 掌握继承和抽象类相关知识点
                  2. 掌握匿名内部类的用法
                  3. 了解类型转换的内容
                  4. 掌握动物类案例

                  1. 继承

                  1.1 概述

                  实际开发中, 我们发现好多类中的内容是相似的(例如: 相似的属性和行为), 每次写很麻烦. 于是我们可以把这些相似的内容提取出来单独的放到一个类中(父类), 然后让那多个类(子类)和这个类(父类)产生一个关系, 从而实现子类可以访问父类的内容, 这个关系就叫: 继承.

                  因为scala语言是支持面向对象编程的,我们也可以使用scala来实现继承,通过继承来减少重复代码。

                  1.2 语法

                  语法

                  叫法

                  1.3 类继承

                  需求

                  已知学生类(Student)和老师类(Teacher), 他们都有姓名和年龄(属性), 都要吃饭(行为), 请用所学, 模拟该需求.

                  1.4 单例对象继承

                  在Scala中, 单例对象也是可以继承类的.

                  需求

                  定义Person类(成员变量: 姓名, 成员方法: sayHello()), 定义单例对象Student继承自Person, 然后测试.

                  1.5 方法重写

                  1.5.1 概述

                  子类中出现和父类一模一样的方法时, 称为方法重写. Scala代码中可以在子类中使用override来重写父类的成员,也可以使用super来引用父类的成员.

                  1.5.2 注意事项
                  1.5.3 示例

                  需求

                  定义Person类, 属性(姓名, 年龄), 有一个sayHello()方法.

                  然后定义Student类继承Person类, 重写Person类中的字段和方法, 并测试.

                  参考代码

                   

                  2. 类型判断

                  有时候,我们设计的程序,要根据变量的类型来执行对应的逻辑, 如下图:

                  1557152463629

                  在scala中,如何来进行类型判断呢?

                  有两种方式:

                  2.1 isInstanceOf, asInstanceOf

                  概述

                  格式

                  示例代码:

                  val trueOrFalse = p.isInstanceOf[Student]

                  val s = p.asInstanceOf[Student]

                  2.2 案例

                  需求

                  参考代码

                  2.3 getClass和classOf

                  isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出: 对象就是指定类的对象。如果要求精确地判断出对象的类型就是指定的数据类型,那么就只能使用 getClass 和 classOf 来实现.

                  用法

                  示例

                  示例说明

                  参考代码

                   

                  3. 抽象类

                  scala语言是支持抽象类的, , 通过abstract关键字来实现.

                  3.1 定义

                  如果类中有抽象字段或者抽象方法, 那么该类就应该是一个抽象类.

                  • 抽象字段: 没有初始化值的变量就是抽象字段.
                  • 抽象方法: 没有方法体的方法就是一个抽象方法.

                  3.2 格式

                  3.3 抽象方法案例

                  需求

                  1552449400240

                  步骤

                  1. 创建一个Shape抽象类,添加一个area抽象方法,用于计算面积
                  2. 创建一个Square正方形类,继承自Shape,它有一个边长的主构造器,并实现计算面积方法
                  3. 创建一个长方形类,继承自Shape,它有一个长、宽的主构造器,实现计算面积方法
                  4. 创建一个圆形类,继承自Shape,它有一个半径的主构造器,并实现计算面积方法
                  5. 编写main方法,分别创建正方形、长方形、圆形对象,并打印它们的面积

                  参考代码

                  3.4 抽象字段

                  在scala的抽象类中,不仅可以定义抽象方法, 也可以定义抽象字段。如果一个成员变量是没有初始化,我们就认为它是抽象的。

                  语法

                  示例

                  示例说明

                  1. 创建一个Person抽象类,它有一个String抽象字段occupation
                  2. 创建一个Student类,继承自Person类,重写occupation字段,初始化为学生
                  3. 创建一个Teacher类,继承自Person类,重写occupation字段,初始化为老师
                  4. 添加main方法,分别创建Student/Teacher的实例,然后分别打印occupation

                   

                  参考代码

                   

                  4. 匿名内部类

                  匿名内部类是继承了类的匿名的子类对象,它可以直接用来创建实例对象。Spark的源代码中大量使用到匿名内部类。学完这个内容, 对我们查看Spark的底层源码非常有帮助.

                  4.1 语法

                  注意: 上述格式中, 如果的类的主构造器参数列表为空, 则小括号可以省略不写.

                  4.2 使用场景

                  4.3 示例

                  需求

                  1. 创建一个Person抽象类,并添加一个sayHello抽象方法
                  2. 定义一个show()方法, 该方法需要传入一个Person类型的对象, 然后调用Person类中的sayHello()方法.
                  3. 添加main方法,通过匿名内部类的方式来创建Person类的子类对象, 调用Person类的sayHello()方法.
                  4. 调用show()方法.

                  参考代码

                   

                  5. 案例: 动物类

                  5.1 需求

                  已知有猫类和狗类, 它们都有姓名和年龄, 都会跑步, 而且仅仅是跑步, 没有什么不同. 它们都有吃饭的功能, 不同的是猫吃鱼, 狗吃肉. 而且猫类独有自己的抓老鼠功能, 狗类独有自己的看家功能, 请用所学模拟该需求.

                  5.2 目的

                  5.3 步骤

                  1. 定义抽象动物类(Animal), 属性: 姓名, 年龄, 行为: 跑步, 吃饭.
                  2. 定义猫类(Cat)继承自动物类, 重写吃饭的方法, 并定义该类独有的抓老鼠的方法.
                  3. 定义狗类(Dog)继承自动物类, 重写吃饭的方法, 并定义该类独有的看家的方法.

                  5.4 参考代码

                  Scala第八章节

                  章节目标

                  1. 能够使用trait独立完成适配器, 模板方法, 职责链设计模式
                  2. 能够独立叙述trait的构造机制
                  3. 能够了解trait继承class的写法
                  4. 能够独立完成程序员案例

                  1. 特质入门

                  1.1 概述

                  有些时候, 我们会遇到一些特定的需求, 即: 在不影响当前继承体系的情况下, 对某些类(或者某些对象)的功能进行加强, 例如: 有猴子类和大象类, 它们都有姓名, 年龄, 以及吃的功能, 但是部分的猴子经过马戏团的训练后, 学会了骑独轮车. 那骑独轮车这个功能就不能定义到父类(动物类)或者猴子类中, 而是应该定义到特质中. 而Scala中的特质, 要用关键字trait修饰.

                  1.2 特点

                  1.3 语法

                  定义特质

                  继承特质

                  注意

                  1.4 示例: 类继承单个特质

                  需求

                  1. 创建一个Logger特质,添加log(msg:String)方法
                  2. 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
                  3. 添加main方法,创建ConsoleLogger对象,调用log方法.

                  参考代码

                  1.5 示例: 类继承多个trait

                  需求

                  1. 创建一个MessageSender特质,添加send(msg:String)方法
                  2. 创建一个MessageReceiver特质,添加receive()方法
                  3. 创建一个MessageWorker类, 继承这两个特质, 重写上述的两个方法
                  4. 在main中测试,分别调用send方法、receive方法

                  参考代码

                  1.6 示例: object继承trait

                  需求

                  1. 创建一个Logger特质,添加log(msg:String)方法
                  2. 创建一个Warning特质, 添加warn(msg:String)方法
                  3. 创建一个单例对象ConsoleLogger,继承Logger和Warning特质, 重写特质中的抽象方法
                  4. 编写main方法,调用单例对象ConsoleLogger的log和warn方法

                  参考代码

                  1.7 示例: 演示trait中的成员

                  需求

                  1. 定义一个特质Hero, 添加具体字段name(姓名), 抽象字段arms(武器), 具体方法eat(), 抽象方法toWar()
                  2. 定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.
                  3. 在main方法中, 创建Generals类的对象, 调用其中的成员.

                  参考代码

                   

                  2. 对象混入trait

                  有些时候, 我们希望在不改变类继承体系的情况下, 对对象的功能进行临时增强或者扩展, 这个时候就可以考虑使用对象混入技术了. 所谓的对象混入指的就是: 在scala中, 类和特质之间无任何的继承关系, 但是通过特定的关键字, 却可以让该类对象具有指定特质中的成员.

                  2.1 语法

                  2.2 示例

                  需求

                  1. 创建Logger特质, 添加log(msg:String)方法
                  2. 创建一个User类, 该类和Logger特质之间无任何关系.
                  3. 在main方法中测试, 通过对象混入技术让User类的对象具有Logger特质的log()方法.

                  参考代码

                   

                  3. 使用trait实现适配器模式

                  3.1 设计模式简介

                  概述

                  设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它并不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

                  分类

                  设计模式一共有23种, 分为如下的3类:

                  1. 创建型

                    指的是: 需要创建对象的. 常用的模式有: 单例模式, 工厂方法模式

                  2. 结构型

                    指的是: 类,特质之间的关系架构. 常用的模式有: 适配器模式, 装饰模式

                  3. 行为型

                    指的是: 类(或者特质)能够做什么. 常用的模式有:模板方法模式, 职责链模式

                  3.2 适配器模式

                  当特质中有多个抽象方法, 而我们只需要用到其中的某一个或者某几个方法时, 不得不将该特质中的所有抽象方法给重写了, 这样做很麻烦. 针对这种情况, 我们可以定义一个抽象类去继承该特质, 重写特质中所有的抽象方法, 方法体为空. 这时候, 我们需要使用哪个方法, 只需要定义类继承抽象类, 重写指定方法即可. 这个抽象类就叫: 适配器类. 这种设计模式(设计思想)就叫: 适配器设计模式.

                  结构

                  需求

                  1. 定义特质PlayLOL, 添加6个抽象方法, 分别为: top(), mid(), adc(), support(), jungle(), schoolchild()

                    解释: top: 上单, mid: 中单, adc: 下路, support: 辅助, jungle: 打野, schoolchild: 小学生

                  2. 定义抽象类Player, 继承PlayLOL特质, 重写特质中所有的抽象方法, 方法体都为空.

                  3. 定义普通类GreenHand, 继承Player, 重写support()和schoolchild()方法.

                  4. 定义main方法, 在其中创建GreenHand类的对象, 并调用其方法进行测试.

                  参考代码

                   

                  4. 使用trait实现模板方法模式

                  在现实生活中, 我们会遇到论文模板, 简历模板, 包括PPT中的一些模板等, 而在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

                  例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。这就要用到模板方法设计模式了.

                  4.1 概述

                  在Scala中, 我们可以先定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤, 这就是: 模板方法设计模式.

                  优点

                  1. 扩展性更强.

                    父类中封装了公共的部分, 而可变的部分交给子类来实现.

                  2. 符合开闭原则。

                    部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能.

                  缺点

                  1. 类的个数增加, 导致系统更加庞大, 设计也更加抽象。

                    因为要对每个不同的实现都需要定义一个子类

                  2. 提高了代码阅读的难度。

                    父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构.

                  4.2 格式

                  注意: 抽象方法的个数要根据具体的需求来定, 并不一定只有一个, 也可以是多个.

                  4.3 示例

                  需求

                  1. 定义一个模板类Template, 添加code()和getRuntime()方法, 用来获取某些代码的执行时间.
                  2. 定义类ForDemo继承Template, 然后重写code()方法, 用来计算打印10000次"Hello,Scala!"的执行时间.
                  3. 定义main方法, 用来测试代码的具体执行时间.

                  参考代码

                   

                  5 使用trait实现职责链模式

                  5.1 概述

                  多个trait中出现了同一个方法, 且该方法最后都调用了super.该方法名(), 当类继承了这多个trait后, 就可以依次调用多个trait中的此同一个方法了, 这就形成了一个调用链。

                  执行顺序为:

                  1. 按照从右往左的顺序依次执行.

                    即首先会先从最右边的trait方法开始执行,然后依次往左执行对应trait中的方法

                  2. 当所有子特质的该方法执行完毕后, 最后会执行父特质中的此方法.

                  这种设计思想就叫: 职责链设计模式.

                  注意: 在Scala中, 一个类继承多个特质的情况叫叠加特质.

                  5.2 格式

                  5.3 示例

                  需求

                  通过Scala代码, 实现一个模拟支付过程的调用链.

                  解释:

                  我们如果要开发一个支付功能,往往需要执行一系列的验证才能完成支付。例如:

                  1. 进行支付签名校验
                  2. 数据合法性校验
                  3. ...

                  如果将来因为第三方接口支付的调整,需要增加更多的校验规则,此时如何不修改之前的校验代码,来实现扩展呢?

                  这就需要用到: 职责链设计模式了.

                  步骤

                  1. 定义一个Handler特质, 添加具体的handle(data:String)方法,表示处理数据(具体的支付逻辑)

                  2. 定义一个DataValidHandler特质,继承Handler特质.

                    • 重写handle()方法,打印"验证数据", 然后调用父特质的handle()方法
                  3. 定义一个SignatureValidHandler特质,继承Handler特质.

                    • 重写handle()方法, 打印"检查签名", 然后调用父特质的handle()方法
                  4. 创建一个Payment类, 继承DataValidHandler特质和SignatureValidHandler特质

                    • 定义pay(data:String)方法, 打印"用户发起支付请求", 然后调用父特质的handle()方法
                  5. 添加main方法, 创建Payment对象实例, 然后调用pay()方法.

                  参考代码

                   

                  6. trait的构造机制

                  6.1 概述

                  如果遇到一个类继承了某个父类且继承了多个父特质的情况,那该类(子类), 该类的父类, 以及该类的父特质之间是如何构造的呢?

                  要想解决这个问题, 就要用到接下来要学习的trait的构造机制了.

                  6.2 构造机制规则

                  6.3 示例

                  需求

                  步骤

                  1. 创建Logger特质,在构造器中打印"执行Logger构造器!"
                  2. 创建MyLogger特质,继承自Logger特质,,在构造器中打印"执行MyLogger构造器!"
                  3. 创建TimeLogger特质,继承自Logger特质,在构造器中打印"执行TimeLogger构造器!"
                  4. 创建Person类,在构造器中打印"执行Person构造器!"
                  5. 创建Student类,继承Person类及MyLogger, TimeLogge特质,在构造器中打印"执行Student构造器!"
                  6. 添加main方法,创建Student类的对象,观察输出。

                  参考代码

                   

                  7. trait继承class

                  7.1 概述

                  在Scala中, trait(特质)也可以继承class(类)。特质会将class中的成员都继承下来。

                  7.2 格式

                  7.3 示例

                  需求

                  1. 定义Message类. 添加printMsg()方法, 打印"学好Scala, 走到哪里都不怕!"
                  2. 创建Logger特质,继承Message类.
                  3. 定义ConsoleLogger类, 继承Logger特质.
                  4. 在main方法中, 创建ConsoleLogger类的对象, 并调用printMsg()方法.

                  参考代码

                   

                  8. 案例: 程序员

                  8.1 需求

                  现实生活中有很多程序员, 例如: Python程序员, Java程序员, 他们都有姓名和年龄, 都要吃饭, 都有自己所掌握的技能(skill). 不同的是, 部分的Java程序员和Python程序员来黑马程序员培训学习后, 掌握了大数据技术, 实现更好的就业. 请用所学, 模拟该场景.

                  8.2 目的

                  8.3 分析

                  1578388916738

                  8.4 参考代码

                  Scala第九章节

                  章节目标

                  1. 理解包的相关内容
                  2. 掌握样例类, 样例对象的使用
                  3. 掌握计算器案例

                  1. 包

                  实际开发中, 我们肯定会遇到同名的类, 例如: 两个Person类. 那在不改变类名的情况下, 如何区分它们呢?

                  这就要使用到包(package)了.

                  1.1 简介

                  包就是文件夹, 用关键字package修饰, 它可以区分重名类, 且功能相似的代码可以放到同一个包中, 便于我们维护和管理代码.

                  注意:

                  1. 编写Scala源代码时, 包名和源码所在的目录结构可以不一致.

                  2. 编译后, 字节码文件和包名路径会保持一致(由编译器自动完成).

                  3. 包名由数字, 大小写英文字母, _(下划线), $(美元符)组成, 多级包之间用.隔开, 一般是公司域名反写.

                    例如: com.itheima.demo01, cn.itcast.demo02

                  1.2 格式

                  1.3 作用域

                  Scala中的包和其它作用域一样, 也是支持嵌套的, 具体的访问规则(作用域)如下:

                  1. 下层可以直接访问上层中的内容.

                    即: 在Scala中, 子包可以直接访问父包中的内容

                  2. 上层访问下层内容时, 可以通过导包(import)或者写全包名的形式实现.

                  3. 如果上下层有相同的类, 使用时将采用就近原则来访问.

                    即: 上下层的类重名时,优先使用下层的类, 如果明确需要访问上层的类,可通过上层路径+类名的形式实现

                  需求

                  1. 创建com.itheima包, 并在其中定义Person类, Teacher类, 及子包scala.
                  2. 在com.itheima.scala包中定义Person类, Student类.
                  3. 在测试类中测试.

                  参考代码

                  1.4 包对象

                  包中可以定义子包, 也可以定义类或者特质, 但是Scala中不允许直接在包中定义变量或者方法, 这是因为JVM的局限性导致的, 要想解决此问题, 就需要用到包对象了.

                  1.4.1 概述

                  在Scala中, 每个包都有一个包对象, 包对象的名字和包名必须一致, 且它们之间是平级关系, 不能嵌套定义.

                  注意:

                  1. 包对象也要定义到父包中, 这样才能实现包对象和包的平级关系.
                  2. 包对象一般用于对包的功能进行补充, 增强等
                  1.4.2 格式
                  1.4.3 示例

                  需求

                  1. 定义父包com.itheima, 并在其中定义子包scala.
                  2. 定义scala包的包对象, 并在其中定义变量和方法.
                  3. 在scala包中定义测试类, 并测试.

                  参考代码

                  1.5 包的可见性

                  在scala中, 我们也是可以通过访问权限修饰符(private, protected, 默认), 来限定包中一些成员的访问权限的.

                  格式

                  需求

                  1. 定义父包com.itheima, 并在其中添加Employee类和子包scala.
                  2. 在Employee类中定义两个变量(name, age), 及sayHello()方法.
                  3. 在子包com.itheima.scala中定义测试类, 创建Employee类对象, 并访问其成员.

                  参考代码

                  1.6 包的引入

                  1.6.1 概述

                  在Scala中, 导入包也是通过关键字import来实现的, 但是Scala中的import功能更加强大, 更加灵活, 它不再局限于编写到scala文件的顶部, 而是可以编写到scala文件中任何你需要用的地方. 且Scala默认引入了java.lang包, scala包及Predef包.

                  1.6.2 注意事项
                  1. Scala中并不是完全引入了scala包和Predef包中的所有内容, 它们中的部分内容在使用时依旧需要先导包.

                    例如: import scala.io.StdIn

                  2. import语句可以写到scala文件中任何需要用到的地方, 好处是: 缩小import包的作用范围, 从而提高效率.

                  3. 在Scala中, 如果要导入某个包中所有的类和特质, 要通过_(下划线)来实现.

                    例如:import scala._ 的意思是, 导入scala包下所有的内容

                  4. 如果仅仅是需要某个包中的某几个类或者特质, 则可以通过选取器(就是一对大括号)来实现.

                    例如:import scala.collection.mutable.{HashSet, TreeSet}表示只引入HashSet和TreeSet两个类.

                  5. 如果引入的多个包中含有相同的类, 则可以通过重命名或者隐藏的方式解决.

                    • 重命名的格式

                    • 隐藏的格式

                  1.6.3 示例

                  需求

                  1. 创建测试类, 并在main方法中测试上述的5点注意事项.
                  2. 需求1: 导入java.util.HashSet类.
                  3. 需求2: 导入java.util包下所有的内容.
                  4. 需求3: 只导入java.util包下的ArrayList类和HashSet类
                  5. 需求4: 通过重命名的方式, 解决多个包中类名重复的问题
                  6. 需求5: 导入时, 隐藏某些不需要用到的类, 即: 导入java.util包下除了HasSet和TreeSet之外所有的类.

                  参考代码

                   

                  2. 样例类

                  在Scala中, 样例类是一种特殊类,一般是用于保存数据的(类似于Java POJO类), 在并发编程以及Spark、Flink这些框架中都会经常使用它。

                  1.1 格式

                  1.2 示例

                  需求

                  参考代码

                  1.3 样例类中的默认方法

                  1.3.1 简介

                  当我们定义一个样例类后,编译器会自动帮助我们生成一些方法, 常用的如下:

                  1.3.2 功能详解
                  1.3.3 示例

                  需求

                  1. 创建Person样例类, 指定姓名, 年龄.
                  2. 在测试类中创建Person类的对象, 并测试上述的5个方法.

                  参考代码

                   

                  3. 样例对象

                  在Scala中, 用case修饰的单例对象就叫: 样例对象, 而且它没有主构造器 , 它主要用在两个地方:

                  1. 当做枚举值使用.

                    枚举: 就是一些固定值, 用来统一项目规范的.

                  2. 作为没有任何参数的消息传递

                    注意: 这点目前先了解即可, 后续讲解Akka并发编程时会详细讲解.

                  3.1 格式

                  3.2 示例

                  需求

                  参考代码

                   

                  4. 案例: 计算器

                  8.1 需求

                  8.2 目的

                  8.3 参考代码

                  Scala第十章节

                  Scala第十一章节

                  Scala第十二章节

                  Scala第十三章节

                  Scala第十四章节

                  Scala第十五章节

                   

                  Scala第十六章节

                  章节目标

                  1. 掌握泛型方法, 类, 特质的用法
                  2. 了解泛型上下界相关内容
                  3. 了解协变, 逆变, 非变的用法
                  4. 掌握列表去重排序案例

                  1. 泛型

                  泛型的意思是泛指某种具体的数据类型, 在Scala中, 泛型用[数据类型]表示. 在实际开发中, 泛型一般是结合数组或者集合来使用的, 除此之外, 泛型的常见用法还有以下三种:

                  1.1 泛型方法

                  泛型方法指的是把泛型定义到方法声明上, 即:该方法的参数类型是由泛型来决定的. 在调用方法时, 明确具体的数据类型.

                  格式

                  需求

                  定义方法getMiddleElement(), 用来获取任意类型数组的中间元素.

                  参考代码

                   

                  1.2 泛型类

                  泛型类指的是把泛型定义到类的声明上, 即:该类中的成员的参数类型是由泛型来决定的. 在创建对象时, 明确具体的数据类型.

                  格式

                  需求

                  1. 定义一个Pair泛型类, 该类包含两个字段,且两个字段的类型不固定.
                  2. 创建不同类型的Pair泛型类对象,并打印.

                  参考代码

                   

                  1.3 泛型特质

                  泛型特质指的是把泛型定义到特质的声明上, 即:该特质中的成员的参数类型是由泛型来决定的. 在定义泛型特质的子类或者子单例对象时, 明确具体的数据类型.

                  格式

                  需求

                  1. 定义泛型特质Logger, 该类有一个变量a和show()方法, 它们都是用Logger特质的泛型.
                  2. 定义单例对象ConsoleLogger, 继承Logger特质.
                  3. 打印单例对象ConsoleLogger中的成员.

                  参考代码

                   

                  2. 上下界

                  我们在使用泛型(方法, 类, 特质)时,如果要限定该泛型必须从哪个类继承、或者必须是哪个类的父类。此时,就需要使用到泛型的上下界

                  2.1 上界

                  使用T <: 类型名表示给类型添加一个上界,表示泛型参数必须要从该类(或本身)继承.

                  格式

                  例如: [T <: Person]的意思是, 泛型T的数据类型必须是Person类型或者Person的子类型

                  需求

                  1. 定义一个Person类
                  2. 定义一个Student类,继承Person类
                  3. 定义一个泛型方法demo(),该方法接收一个Array参数.
                  4. 限定demo方法的Array元素类型只能是Person或者Person的子类
                  5. 测试调用demo()方法,传入不同元素类型的Array

                  参考代码

                   

                  2.2 下界

                  使用T >: 数据类型表示给类型添加一个下界,表示泛型参数必须是从该类型本身或该类型的父类型.

                  格式

                  注意:

                  1. 例如: [T >: Person]的意思是, 泛型T的数据类型必须是Person类型或者Person的父类型
                  2. 如果泛型既有上界、又有下界。下界写在前面,上界写在后面. 即: [T >: 类型1 <: 类型2]

                  需求

                  1. 定义一个Person类
                  2. 定义一个Policeman类,继承Person类
                  3. 定义一个Superman类,继承Policeman类
                  4. 定义一个demo泛型方法,该方法接收一个Array参数,
                  5. 限定demo方法的Array元素类型只能是Person、Policeman
                  6. 测试调用demo,传入不同元素类型的Array

                  参考代码

                   

                  3. 协变、逆变、非变

                  在Spark的源代码中大量使用到了协变、逆变、非变,学习该知识点对我们将来阅读spark源代码很有帮助。

                  如下图:

                  1558064807949

                  3.1 非变

                  语法格式

                  3.2 协变

                  语法格式

                  3.3 逆变

                  语法格式

                  3.4 示例

                  需求

                  1. 定义一个Super类、以及一个Sub类继承自Super类
                  2. 使用协变、逆变、非变分别定义三个泛型类
                  3. 分别创建泛型类对象来演示协变、逆变、非变

                  参考代码

                   

                  4. 案例: 列表去重排序

                  4.1 需求

                  1. 已知当前项目下的data文件夹中有一个1.txt文本文件, 文件内容如下:

                  2. 对上述数据去重排序后, 重新写入到data文件夹下的2.txt文本文件中, 即内容如下:

                  4.2 目的

                  考察泛型, 列表, 流相关的内容.

                  4.3 参考代码

                  Scala第十七章节

                  章节目标

                  1. 了解集合的相关概念
                  2. 掌握Traversable集合的用法
                  3. 掌握随机学生序列案例

                  1. 集合

                  1.1 概述

                  但凡了解过编程的人都知道程序 = 算法 + 数据结构这句话, 它是由著名的瑞士计算机科学家尼古拉斯·沃斯提出来的, 而他也是1984年图灵奖的获得者. 算法指的是计算的一系列有效, 通用的步骤. 算法和数据结构是程序设计中相辅相成的两个方面, 因此数据结构也是编程中很重要的一个方面. 很多编程语言都提供了数据结构的对应编程库,

                  并称之为集合库(Collection Library). Scala中也有集合库, 它的优点如下:

                  1.2 分类

                  Scala同时支持不可变集合和可变集合, 因为不可变集合可以安全的并发访问, 所以它也是默认使用的集合类库. 在Scala中, 对于几乎所有的集合类, 都提供了可变和不可变两个版本, 具体如下:

                  如下图:

                  1581223237588

                  小技巧:

                  1. 可变集合比不可变集合更加丰富.

                    例如: 在Seq集合中, 增加了Buffer集合, 我们常用的有: ArrayBuffer和ListBuffer.

                  2. 当我们接触一个新的继承体系是, 建议采用学顶层, 用底层的方式.

                    • 顶层定义的是整个继承体系所共有的内容.
                    • 而底层才是具体的体现, 实现.

                   

                  2. Traversable

                  2.1 概述

                  Traversable是一个特质(trait), 它是其他集合的父特质, 它的子特质immutable.Traversable和mutable.Traversable分别是不可变集合和可变集合的父特质, 集合中大部分通用的方法都是在这个特质中定义的. 因此了解它的功能对学习其他集合类至关重要.

                  2.2 格式

                  2.3 示例一: 创建Traversable对象

                  1. 创建空的, 用来存储Int类型数据的Traversable对象.
                  2. 创建Traversable集合对象, 存储数字1, 2, 3, 并将结果打印到控制台上.

                  参考代码

                   

                  2.4 案例二: 转置Traversable集合

                  了解过线性代数的同学都知道, 矩阵有一个转置的操作, 在Scala中, 可以通过transpose()方法来实现类似的操作.

                  如下图:

                  1581226285172

                  注意:

                  进行转置操作时, 程序会自动检测每个集合中的元素个数是否一致, 如果一致, 则转置成功. 如果不一致, 则报错.

                  需求

                  1. 定义一个Traversable集合t1, 它有三个元素, 每个元素都是Traversable集合, 并分别存储如下数据:
                  2. 第一个元素存储(1, 4, 7), 第二个元素存储(2, 5, 8), 第三个元素存储(3, 6, 9).
                  3. 通过transpose方法, 对集合t1进行转置操作.
                  4. 打印结果.

                  参考代码

                   

                  2.5 案例三: 拼接集合

                  在实际开发中, 数据是从多渠道获取到的, 所以我们经常需要拼接一些数据, 在Scala中, 我们可以通过++来拼接数据, 但是这种方式会创建大量的临时集合(即: 每++一次, 就会创建一个新的临时集合), 针对这种情况, 我们可以通过concat()方法来实现. 该方法会预先计算出所需的集合的大小, 然后生成一个集合, 减少了中间无用的临时集合, 所以它更加有效.

                  需求

                  1. 已知有三个Traversable集合, 分别存储(11, 22, 33), (44, 55), (66, 77, 88, 99)元素.
                  2. 通过concat()方法拼接上述的三个集合.
                  3. 将拼接后的结果打印到控制台上.

                  参考代码

                   

                  2.6 案例四: 利用偏函数筛选元素

                  在Scala中, 我们还可以通过collect()方法实现偏函数结合集合来使用, 从而来从集合中筛选指定的数据.

                  格式

                  解释:

                  1. [B]表示通过偏函数处理后, 返回值的数据类型.
                  2. pf: PartialFunction[A, B]表示collect()方法需要传入一个偏函数对象.
                  3. Traversable[B]表示返回的具体数据的集合.

                  需求

                  1. 已知有一个Traversable集合, 存储元素为: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10.
                  2. 通过collect方法筛选出集合中所有的偶数.

                  参考代码

                   

                  2.7 案例五: 计算集合元素的阶乘

                  假设一个Traversable[Int]集合中包含(1, 2, 3, 4, 5)五个数字, 如果让我们计算每个元素的阶乘, 并放到一个新的Traversable[Int]集合中, 我们可以通过递归来实现, 但是这种实现方式有弊端, 每次计算都是从头开始计算的, 例如: 获取5的阶乘, 是通过5 * 4 * 3 * 2 * 1计算出来的, 并没有运用之前计算出来的4的阶乘的结果. 此时, 我们就可以通过scan()方法来优化此需求了, 它不仅将中间的计算结果放入新的集合中, 并且还会把中间结果传递给下一次的函数调用.

                  格式

                  解释:

                  1. [B]表示返回值的数据类型.
                  2. (z: B)表示初始化值.
                  3. (op: (B, B) => B)表示一个具体的运算函数.
                  4. scan()方法等价于scanLeft()方法, 还有一个跟它相反的方法scanRight().

                  需求

                  1. 定义Traversable集合t1, 存储1, 2, 3, 4, 5这五个数字.
                  2. 假设初始值为1, 通过scan()方法, 分别获取t1集合中各个元素的阶乘值.
                  3. 打印结果.

                  参考代码

                   

                  2.8 案例六: 获取集合的指定元素

                  集合是用来存储数据的, 既然能存储, 那肯定也可以从集合中获取我们想要的数据, 可以通过如下的方法实现:

                  需求

                  1. 定义一个Traversable集合, 包含1, 2, 3, 4, 5, 6这六个元素.
                  2. 分别通过head, headOption, last, lastOption获取集合中的首尾第一个元素, 并打印.
                  3. 通过find方法获取集合中第一个偶数, 并打印.
                  4. 通过slice()方法获取3, 4, 5这三个元素, 并将它们放到一个新的Traversable集合中, 然后打印结果.

                  参考代码

                   

                  2.9 案例七: 判断元素是否合法

                  如果我们遇到判断集合中所有的元素是否都满足指定的条件, 或者任意元素满足指定的条件这种需求时, 就可以考虑使用forall()方法和exists()方法来实现了.

                  需求

                  1. 定义Traversable集合t1, 包含1到6这六个数字.
                  2. 通过forall()方法实现, 判断t1中的元素是否都是偶数.
                  3. 通过exists()方法实现, 判断t1中是否有偶数.

                  参考代码

                   

                  2.10 案例八: 聚合函数

                  如果我们想统计集合中满足条件的元素个数, 或者计算集合元素和, 乘积, 求最大值, 最小值等操作 , 就可以用到如下的这些方法了:

                  需求

                  1. 定义Traversable集合t1, 包含1到6这六个数字.
                  2. 通过count()方法统计t1集合中所有奇数的个数, 并打印结果.
                  3. 通过sum()方法获取t1集合中所有的元素和, 并打印结果.
                  4. 通过product()方法获取t1集合中所有的元素乘积, 并打印结果.
                  5. 通过max()方法获取t1集合中所有元素的最大值, 并打印结果.
                  6. 通过min()方法获取t1集合中所有元素的最小值, 并打印结果.

                  参考代码

                   

                  2.11 案例九: 集合类型转换

                  有时候, 我们需要将Traversable集合转换成其他的集合来进行操作, 这时候就要用toXxx()方法了.

                  注意: 上述的Xxx表示目标集合的名称, 例如: toList, toSet, toArray, toSeq等等...

                  需求

                  1. 定义Traversable集合t1, 包含1到5这五个数字.
                  2. 将t1集合分别转成数组(Array), 列表(List), 集(Set)这三种形式, 并打印结果.

                  参考代码

                   

                  2.12 案例十: 填充元素

                  如果我们需要往集合中快速添加相同元素, 例如: 生成5个都是"abc"的Traversable对象, 就需要用到fill()和iterate()方法了, 那如果是想生成指定间隔的队列元素, 就可以通过range()方法来实现了, 具体如下:

                  需求

                  1. 通过fill()方法, 生成一个Traversable集合, 该集合包含5个元素, 值都是"传智播客".

                  2. 通过fill()方法, 生成一个Traversable集合, 该集合包含3个随机数.

                  3. 通过fill()方法, 生成一个Traversable集合, 格式如下:

                  4. 通过iterate()方法, 生成一个Traversable集合, 该集合包含5个元素, 分别为:1, 10, 100, 1000, 10000.

                  5. 通过range()方法, 获取从数字1开始, 截止数字21之间, 间隔为5的所有数据.

                  参考代码

                   

                  3. 案例: 随机学生序列

                  3.1 需求

                  1. 定义一个Traversable集合, 包含5个学生(属性为: 姓名, 年龄)的信息, 且学生的姓名和年龄信息是随机生成的.

                    • 假设姓名信息为("张三", "李四", "王五", "赵六", "田七"), 年龄的取值范围是: [20, 30), 前闭后开写法.
                  2. 按照学生的年龄信息降序排列后, 将结果打印到控制台上.

                  3.2 目的

                  考察样例类, 随机数, 集合相关的内容.

                  3.3 步骤

                  1. 创建Student样例类, 属性为: 姓名, 年龄, 用来记录学生的信息.
                  2. 定义列表, 记录学生的姓名信息, 值为: "张三", "李四", "王五", "赵六", "田七".
                  3. 创建随机数对象r, 用来实现获取一些随机值的操作.
                  4. 创建Traversable集合, 包含5个随机的学生信息.
                  5. 将Traversable集合转换成List列表.
                  6. 通过列表的sortWith()方法, 按照学生的年龄降序排列.
                  7. 打印结果.

                  3.4 参考代码

                  Scala第十八章节

                  章节目标

                  1. 掌握Iterable集合相关内容.
                  2. 掌握Seq集合相关内容.
                  3. 掌握Set集合相关内容.
                  4. 掌握Map集合相关内容.
                  5. 掌握统计字符个数案例.

                  1. Iterable

                  1.1 概述

                  Iterable代表一个可以迭代的集合, 它继承了Traversable特质, 同时也是其他集合的父特质. 最重要的是, 它定义了获取迭代器(iterator)的方法: def iterator: Iterator[A], 这是一个抽象方法, 它的具体实现类需要实现这个方法, 从而实现迭代的返回集合中的元素.

                  1.2 分类

                  Traversable提供了两种遍历数据的方式:

                  1.3 案例一: 遍历集合

                  需求

                  1. 定义一个列表, 存储1, 2, 3, 4, 5这五个数字.
                  2. 通过iterator()方法遍历上述的列表.
                  3. 通过foreach()方法遍历上述的列表.

                  参考代码

                   

                  1.4 案例二: 分组遍历

                  如果遇到将Iterable对象中的元素分成固定大小的组, 然后遍历这种需求, 就可以通过grouped()方法来实现了.

                  格式

                  需求

                  1. 定义一个Iterable集合, 存储1~13之间的所有整数.
                  2. 通过grouped()方法, 对Iterable集合按照5个元素为一组的形式进行分组, 遍历并打印结果.

                  参考代码

                   

                  1.5 案例三: 按照索引生成元组

                  Iterable集合中存储的每个元素都是有索引的, 如果我们想按照元素 -> 索引这种格式, 生成一个新的集合, 此时, 就需要用到zipWithIndex()方法了.

                  需求

                  1. 定义一个Iterable集合, 存储"A", "B", "C", "D", "E"这五个字符串.
                  2. 通过zipWithIndex()方法, 按照 字符串->索引这种格式, 生成新的集合.
                  3. 重新按照 索引->字符串这种格式, 生成新的集合.
                  4. 打印结果.

                  参考代码

                   

                  1.6 案例四: 判断集合是否相同

                  有时候, 我们不仅想判断两个集合中的元素是否全部相同, 而且还要求这两个集合元素的迭代顺序保持一致, 此时, 就可以通过sameElements()方法来实现该需求了.

                  即sameElements()方法的功能是: 判断集合所包含的元素及元素的迭代顺序是否一致.

                  需求

                  1. 定义Iterable集合list1, 包含"A", "B", "C"这三个元素.
                  2. 通过sameElements()方法, 判断list1和Iterable("A", "B", "C")集合是否相同.
                  3. 通过sameElements()方法, 判断list1和Iterable("A", "C", "B")集合是否相同.
                  4. 定义Iterable集合list2, 包含"A", "B", "C", "D"这四个元素.
                  5. 通过sameElements()方法, 判断list1和list2是否相同.

                  参考代码

                   

                  2. Seq

                  2.1 概述

                  Seq特质代表按照一定顺序排列的元素序列, 序列是一种特别的可迭代集合, 它的元素特点是有序(元素存取顺序一致), 可重复, 有索引.

                  2.2 分类

                  继承关系如下图:

                  1581314503487

                   

                  IndexedSeq和LinearSeq是Seq的子特质, 代表着序列的两大子门派.

                  1. IndexedSeq特质代表索引序列, 相对于Seq来说, 它并没有增加额外的方法, 对于随机访问元素的方法来讲, 它更加有效, 常用的子集合有: NumericRange, Range, Vector, String等.

                    • Range集合

                      Range代表一个有序的整数队列, 每个元素之间的间隔相同, 例如奇数队列:1, 3, 5, 7, 9, 但是斐波那契数列1, 1, 2, 3, 5, 8就不属于Range类. 简单来说, Range类似于数学中的等差数列.

                    • NumericRange集合

                      NumericRange集合是一个更通用的等差队列, 可以生成Int, BigInteger, Short, Byte等类型的序列.

                    • Vector集合

                      Vector是一个通用的不可变的数据结构, 相对来讲, 它获取数据的时间会稍长一些, 但是随机更新数据要比数组和链表快很多.

                  2. LinearSeq特质代表线性序列, 它通过链表的方式实现, 因此它的head, tail, isEmpty执行起来相对更高效. 常用的子集合有: List, Stack, Queue等.

                    • Stack: 表示数据结构, 元素特点是先进后出.

                      由于历史的原因, Scala当前的库中还包含一个immutable.Stack, 但当前已经被标记为弃用, 因为它的设计不怎么优雅, 而且性能也不太好, 因为栈会涉及到大量元素的进出, 所以不可变栈的应用场景还是比较小的, 最常用的还是可变栈, 例如: mutable.Stack, mutable.ArrayStack.

                      • mutable.Stack: 是通过List, 也就是链表的方式实现的, 增删快, 查询慢.
                      • mutable.ArrayStack: 是通过Array, 也就是数组的方式实现的, 查询快, 增删慢.
                    • Queue: 表示队列数据结构, 元素特点是先进先出.

                  2.3 案例一: 创建Seq集合

                  需求

                  创建Seq集合, 存储元素1, 2, 3, 4, 5, 并打印结果.

                  参考代码

                   

                  2.4 案例二: 获取长度及元素

                  因为Seq集合的每个元素都是有索引的, 所以我们可以通过索引直接获取其对应的元素, 而且可以通过length()或者size()方法获取集合的长度.

                  需求

                  1. 创建Seq集合, 存储元素1, 2, 3, 4, 5.
                  2. 打印集合的长度.
                  3. 获取索引为2的元素.

                  参考代码

                   

                  2.5 案例三: 获取指定元素的索引值

                  格式

                  获取指定元素的索引值, 我们可以通过indexOf(), lastIndexOf(), indexWhere(), lastIndexWhere(),indexOfSlice()方法来实现, 具体功能如下:

                  注意: 上述方式都是查找到指定数据后则返回对应的索引, 如果找不到此数据, 则返回-1.

                  需求

                  1. 定义Seq集合, 存储数据: 1, 2, 4, 6, 4, 3, 2.
                  2. 获取元素2在集合中第一次出现的索引, 并打印.
                  3. 获取元素2在集合中最后一次出现的索引, 并打印.
                  4. 获取集合中, 小于5的第一个偶数的索引, 并打印.
                  5. 从索引2位置开始查找集合中, 小于5的第一个偶数的索引, 并打印.
                  6. 获取集合中, 小于5的最后一个偶数的索引, 并打印.
                  7. 获取子序列Seq(1, 2)在t1集合中, 第一次出现的索引, 并打印.
                  8. 从索引3开始查找子序列Seq(1, 2)在t1集合中, 第一次出现的索引, 并打印.

                  参考代码

                   

                  2.6 案例四: 判断是否包含指定数据

                  如果我们想判断序列是否以指定的数据开头或者结尾, 以及判断序列是否包含指定的数据, 就可以通过startsWith(), endsWith(), contains(), containsSlice()来实现了, 具体如下:

                  需求

                  1. 定义Seq集合s1, 存储1到10这十个数据.
                  2. 判断s1集合是否以子序列(1, 2)开头, 并打印结果.
                  3. 判断s1集合是否以子序列(1, 3)开头, 并打印结果.
                  4. 判断s1集合是否以子序列(9, 10)结尾, 并打印结果.
                  5. 判断s1集合是否以子序列(8, 9)结尾, 并打印结果.
                  6. 判断s1集合是否包含元素3, 并打印结果.
                  7. 判断s1集合是否包含子序列Seq(1, 2), 并打印结果.
                  8. 判断s1集合是否包含子序列Seq(1, 3), 并打印结果.

                  参考代码

                   

                  2.7 案例五: 修改指定的元素

                  如果我们想修改某个元素值, 或者用指定的子序列来替代集合中的某一段元素, 就可以通过updated(), patch()方法来实现了, 具体如下:

                  需求

                  1. 定义Seq集合s1, 存储1到5这五个数据.
                  2. 修改索引2位置的元素值为: 10, 并打印结果.
                  3. 从索引1开始, 用子序列Seq(10, 20)替换3个元素, 并打印结果.

                  参考代码

                   

                  3. Stack

                  3.1 概述

                  表示数据结构, 元素特点是先进后出. 由于历史的原因, Scala当前的库中还包含一个immutable.Stack, 但当前已经被标记为弃用, 因为它的设计不怎么优雅, 而且性能也不太好, 因为栈会涉及到大量元素的进出, 所以不可变栈的应用场景还是比较小的, 最常用的还是可变栈, 例如: mutable.Stack, mutable.ArrayStack.

                  3.2 图解

                  把元素1, 2, 3添加到栈中, 图解如下:

                  1584792168888

                  3.3 常用方法

                  注意:

                  1. immutable.Stack集合中有一个独有方法pushAll(), 把多个元素压入栈中.

                  2. mutable.ArrayStack集合独有的方法为:

                    • dup(duplicate缩写): 可以用来复制栈顶元素.
                    • preserving: 该方法会执行一个表达式, 在表达式执行完毕后恢复栈, 即: 栈的内容和调用前一致.

                  3.4 示例一: 演示Stack可变栈

                  需求

                  1. 定义可变栈Stack, 存储1到5这5个数字.
                  2. 通过top()方法获取栈顶元素, 并打印.
                  3. 通过push()方法把元素6添加到栈顶位置, 并打印.
                  4. 通过pushAll()往栈顶位置添加Seq(11, 22, 33)序列, 并打印.
                  5. 通过pop()方法移除栈顶元素, 并打印.
                  6. 通过clear()方法移除集合内所有的元素.

                  参考代码

                   

                  3.5 案例二: 演示ArrayStack可变栈

                  需求

                  1. 定义可变栈ArrayStack, 存储1到5这5个数字.
                  2. 通过dup()方法复制栈顶元素, 并打印结果.
                  3. 通过preserving()方法实现先清空集合元素, 然后恢复集合中清除的数据, 并打印.

                  参考代码

                   

                  4. Queue

                  4.1 概述

                  表示队列, 元素特点是先进先出, 我们常用的队列是可变队列: mutable.Queue, 它内部是以MutableList数据结构实现的.

                  4.2 图解

                  把元素1, 2, 3添加到队列中, 图解如下:

                  1581338930411

                  4.3 常用方法

                  4.4 案例演示

                  需求

                  1. 定义可变队列Queue, 存储1到5这五个数据.
                  2. 往队列中添加元素6, 并打印.
                  3. 往队列中添加元素7, 8, 9, 并打印.
                  4. 移除队列的第一个元素, 并打印该元素.
                  5. 移除队列的第一个奇数, 并打印该元素.
                  6. 移除队列中所有的偶数, 并打印所有被移除的数据.
                  7. 打印可变队列Queue, 查看最终结果.

                  参考代码

                   

                  5. Set

                  5.1 概述

                  Set集合中的元素不包含重复的元素, 常用子类有: SortedSet(子类是TreeSet), HashSet, , ListSet.

                  5.2 分类

                  继承关系如下图:

                  1581311590211

                  5.3 示例

                  需求

                  1. 创建SortedSet集合, 存储元素1, 4, 3, 2, 5, 然后打印该集合.
                  2. 创建HashSet集合, 存储元素1, 4, 3, 2, 5, 然后打印该集合.
                  3. 创建LinkedHashSet集合, , 存储元素1, 4, 3, 2, 5, 然后打印该集合.

                  参考代码

                   

                  6. Map

                  6.1 概述

                  Map表示映射, 它是包含键值对(key-value)的集合, Map类型的基本操作类似于Set集合的操作, 由于它包含的元素Entry是键值对, 所以Map提供了一些单独针对键或者值操作的方法.

                  6.2 分类

                  继承关系如下图:

                  1581340394726

                  6.3 示例

                  需求

                  1. 定义Map集合, 存储数据为: "A" -> 1, "B" -> 2, "C" -> 3.

                  2. 遍历Map集合.

                  3. 通过filterKeys()方法, 获取出键为"B"的这组键值对对象, 并打印结果.

                    filterKeys: 针对键进行筛选, 根据键获取满足条件的键值对元素.

                  参考代码

                   

                  7. 案例: 统计字符个数

                  7.1 需求

                  1. 提示用户录入字符串, 并接收.
                  2. 统计上述字符串中每个字符出现的次数, 并将结果打印到控制台上.

                  7.2 目的

                  综合考察集合, 键盘录入相关知识点.

                  7.3 步骤

                  1. 提示用户录入字符串, 并接收.
                  2. 定义Map集合, 用来存储字符及其出现的次数. 键:字符, 值: 字符出现的次数.
                  3. 将字符串转成字符数组.
                  4. 遍历字符数组, 获取到每一个字符.
                  5. 如果字符是第一次出现, 就将其次数记录为1, 如果字符是重复出现, 就将其次数+1, 然后重新存储.
                  6. 遍历集合, 查看结果.

                  7.4 参考代码

                  Scala第十九章节

                  章节目标

                  1. 了解Actor的相关概述
                  2. 掌握Actor发送和接收消息
                  3. 掌握WordCount案例

                  1. Actor介绍

                  Scala中的Actor并发编程模型可以用来开发比Java线程效率更高的并发程序。我们学习Scala Actor的目的主要是为后续学习Akka做准备。

                  1.1 Java并发编程的问题

                  在Java并发编程中,每个对象都有一个逻辑监视器(monitor),可以用来控制对象的多线程访问。我们添加sychronized关键字来标记,需要进行同步加锁访问。这样,通过加锁的机制来确保同一时间只有一个线程访问共享数据。但这种方式存在资源争夺、以及死锁问题,程序越大问题越麻烦。

                  1552787178794

                  线程死锁

                  1552788042478

                  1.2 Actor并发编程模型

                  Actor并发编程模型,是Scala提供给程序员的一种与Java并发编程完全不一样的并发编程模型,是一种基于事件模型的并发机制。Actor并发编程模型是一种不共享数据,依赖消息传递的一种并发编程模式,有效避免资源争夺、死锁等情况。

                  1552787528554

                  1.3 Java并发编程对比Actor并发编程

                  Java内置线程模型Scala Actor模型
                  "共享数据-锁"模型 (share data and lock)share nothing
                  每个object有一个monitor,监视线程对共享数据的访问不共享数据,Actor之间通过Message通讯
                  加锁代码使用synchronized标识 
                  死锁问题 
                  每个线程内部是顺序执行的每个Actor内部是顺序执行的

                  注意:

                  1. scala在2.11.x版本中加入了Akka并发编程框架,老版本已经废弃。
                  2. Actor的编程模型和Akka很像,我们这里学习Actor的目的是为学习Akka做准备。

                   

                  2. 创建Actor

                  我们可以通过类(class)或者单例对象(object), 继承Actor特质的方式, 来创建Actor对象.

                  2.1 步骤

                  1. 定义class或object继承Actor特质
                  2. 重写act方法
                  3. 调用Actor的start方法执行Actor

                  注意: 每个Actor是并行执行的, 互不干扰.

                  2.2 案例一: 通过class实现

                  需求

                  1. 创建两个Actor,一个Actor打印1-10,另一个Actor打印11-20
                  2. 使用class继承Actor实现.(如果需要在程序中创建多个相同的Actor)

                  参考代码

                   

                  2.3 案例二: 通过object实现

                  需求

                  1. 创建两个Actor,一个Actor打印1-10,另一个Actor打印11-20
                  2. 使用object继承Actor实现.(如果在程序中只创建一个Actor)

                  参考代码

                  2.4 Actor程序运行流程

                  1. 调用start()方法启动Actor
                  2. 自动执行act()方法
                  3. 向Actor发送消息
                  4. act方法执行完成后,程序会调用exit()方法结束程序执行.

                   

                  3. 发送消息/接收消息

                  我们之前介绍Actor的时候,说过Actor是基于事件(消息)的并发编程模型,那么Actor是如何发送消息和接收消息的呢?

                  3.1 使用方式

                  3.1.1 发送消息

                  我们可以使用三种方式来发送消息:

                  发送异步消息,没有返回值
                  !?发送同步消息,等待返回值
                  !!发送异步消息,返回值是Future[Any]

                  例如:要给actor1发送一个异步字符串消息,使用以下代码:

                   

                  3.1.2 接收消息

                  Actor中使用receive方法来接收消息,需要给receive方法传入一个偏函数

                  注意: receive方法只接收一次消息,接收完后继续执行act方法

                   

                  3.2 案例一: 发送及接收一句话

                  需求

                  1. 创建两个Actor(ActorSender、ActorReceiver)
                  2. ActorSender发送一个异步字符串消息给ActorReceiver
                  3. ActorReceiver接收到该消息后,打印出来

                  1552791021244

                  参考代码

                   

                  3.3 案例二: 持续发送和接收消息

                  如果我们想实现ActorSender一直发送消息, ActorReceiver能够一直接收消息,该怎么实现呢?

                  答: 我们只需要使用一个while(true)循环,不停地调用receive来接收消息就可以啦。

                  需求

                  1. 创建两个Actor(ActorSender、ActorReceiver)
                  2. ActorSender持续发送一个异步字符串消息给ActorReceiver
                  3. ActorReceiver持续接收消息,并打印出来

                  参考代码

                   

                  3.4 案例三: 优化持续接收消息

                  上述代码,是用while循环来不断接收消息的, 这样做可能会遇到如下问题:

                  针对上述情况, 我们可以使用loop(), 结合react()来复用线程, 这种方式比while循环 + receive()更高效.

                  需求

                  1. 创建两个Actor(ActorSender、ActorReceiver)
                  2. ActorSender持续发送一个异步字符串消息给ActorReceiver
                  3. ActorReceiver持续接收消息,并打印出来

                  注意: 使用loop + react重写上述案例.

                  参考代码

                   

                  3.5 案例四: 发送和接收自定义消息

                  我们前面发送的消息都是字符串类型,Actor中也支持发送自定义消息,例如:使用样例类封装消息,然后进行发送处理。

                  3.5.1 示例一: 发送同步有返回消息

                  需求

                  1. 创建一个MsgActor,并向它发送一个同步消息,该消息包含两个字段(id、message)
                  2. MsgActor回复一个消息,该消息包含两个字段(message、name)
                  3. 打印回复消息

                  注意:

                  • 使用!?来发送同步消息
                  • 在Actor的act方法中,可以使用sender获取发送者的Actor引用

                  参考代码

                   

                  3.5.2 示例二: 发送异步无返回消息

                  需求

                  创建一个MsgActor,并向它发送一个异步无返回消息,该消息包含两个字段(id, message)

                  注意: 使用!发送异步无返回消息

                  参考代码

                   

                  3.5.3 示例三: 发送异步有返回消息

                  需求

                  1. 创建一个MsgActor,并向它发送一个异步有返回消息,该消息包含两个字段(id、message)
                  2. MsgActor回复一个消息,该消息包含两个字段(message、name)
                  3. 打印回复消息

                  注意:

                  • 使用!!发送异步有返回消息
                  • 发送后,返回类型为Future[Any]的对象
                  • Future表示异步返回数据的封装,虽获取到Future的返回值,但不一定有值,可能在将来某一时刻才会返回消息
                  • Future的isSet()可检查是否已经收到返回消息,apply()方法可获取返回数据

                  图解

                  1585309506141

                  参考代码

                   

                  4. 案例: WordCount

                  4.1 需求

                  接下来,我们要使用Actor并发编程模型实现多文件的单词统计

                  案例介绍

                  给定几个文本文件(文本文件都是以空格分隔的),使用Actor并发编程来统计单词的数量.

                  思路分析

                  1552798868918

                  实现思路

                  1. MainActor获取要进行单词统计的文件
                  2. 根据文件数量创建对应的WordCountActor
                  3. 将文件名封装为消息发送给WordCountActor
                  4. WordCountActor接收消息,并统计单个文件的单词计数
                  5. 将单词计数结果发送给MainActor
                  6. MainActor等待所有的WordCountActor都已经成功返回消息,然后进行结果合并

                   

                  4.2 步骤一: 获取文件列表

                  实现思路

                  1. 在当前项目下的data文件夹下有: 1.txt, 2.txt两个文本文件, 具体存储内容如下:

                    1.txt文本文件存储内容如下:

                    2.txt文本文件存储内容如下:

                  2. 获取上述两个文本文件的路径, 并将结果打印到控制台上.

                  参考代码

                   

                  4.3 步骤二: 创建WordCountActor

                  实现思路

                  1. 根据文件数量创建对应个数的WordCountActor对象.
                  2. 为了方便后续发送消息给Actor,将每个Actor与文件名关联在一起

                  实现步骤

                  1. 创建WordCountActor
                  2. 将文件列表转换为WordCountActor
                  3. 为了后续方便发送消息给Actor,将Actor列表和文件列表拉链到一起
                  4. 打印测试

                  参考代码

                  4.4 步骤三: 启动Actor/发送/接收任务消息

                  实现思路

                  启动所有WordCountActor对象,并发送单词统计任务消息给每个WordCountActor对象.

                  注意: 此处应发送异步有返回消息

                  实现步骤

                  1. 创建一个WordCountTask样例类消息,封装要进行单词计数的文件名
                  2. 启动所有WordCountActor,并发送异步有返回消息
                  3. 获取到所有的WordCountActor中返回的消息(封装到一个Future列表中)
                  4. 在WordCountActor中接收并打印消息

                  参考代码

                   

                  4.5 步骤四: 统计文件单词计数

                  实现思路

                  读取文件文本,并统计出来单词的数量。例如:

                  实现步骤

                  1. 读取文件内容,并转换为列表
                  2. 按照空格切割文本,并转换为一个一个的单词
                  3. 为了方便进行计数,将单词转换为元组
                  4. 按照单词进行分组,然后再进行聚合统计
                  5. 打印聚合统计结果

                  参考代码

                  4.6 步骤五: 返回结果给MainActor

                  实现思路

                  实现步骤

                  1. 定义一个样例类封装单词计数结果
                  2. 将单词计数结果发送给MainActor
                  3. MainActor中检测所有WordCountActor是否均已返回,如果均已返回,则获取并转换结果
                  4. 打印结果

                  参考代码

                   

                  4.7 步骤六: 结果合并

                  实现思路

                  对接收到的所有单词计数进行合并。

                  参考代码

                  Scala第二十章节

                  章节目标

                  1. 理解Akka并发编程框架简介
                  2. 掌握Akka入门案例
                  3. 掌握Akka定时任务代码实现
                  4. 掌握两个进程间通信的案例
                  5. 掌握简易版spark通信框架案例

                  1. Akka并发编程框架简介

                  1.1 Akka概述

                  Akka是一个用于构建高并发、分布式和可扩展的基于事件驱动的应用工具包。Akka是使用scala开发的库,同时可以使用scala和Java语言来开发基于Akka的应用程序。

                  1.2 Akka特性

                  1.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接受的方法中

                  1552871108166

                   

                  2. 创建Actor

                  Akka中,也是基于Actor来进行编程的。类似于之前学习过的Actor。但是Akka的Actor的编写、创建方法和之前有一些不一样。

                  2.1 API介绍

                  2.2 Actor Path

                  每一个Actor都有一个Path,这个路径可以被外部引用。路径的格式如下:

                  Actor类型路径示例
                  本地Actorakka://actorSystem名称/user/Actor名称akka://SimpleAkkaDemo/user/senderActor
                  远程Actorakka.tcp://my-sys@ip地址:port/user/Actor名称akka.tcp://192.168.10.17:5678/user/service-b

                   

                  2.3 入门案例

                  2.3.1 需求

                  基于Akka创建两个Actor,Actor之间可以互相发送消息。

                  1552879431645

                  2.3.2 实现步骤
                  1. 创建Maven模块
                  2. 创建并加载Actor
                  3. 发送/接收消息
                  2.3.3 创建Maven模块

                  使用Akka需要导入Akka库,这里我们使用Maven来管理项目, 具体步骤如下:

                  1. 创建Maven模块.

                  2. 打开pom.xml文件,导入akka Maven依赖和插件.

                   

                  2.3.4 创建并加载Actor

                  到这, 我们已经把Maven项目创建起来了, 后续我们都会采用Maven来管理我们的项目. 接下来, 我们来实现:

                  创建并加载Actor, 这里, 我们要创建两个Actor:

                  具体步骤

                  1. 在src/main/scala文件夹下创建包: com.itheima.akka.demo

                  2. 在该包下创建两个Actor(注意: 用object修饰的单例对象).

                    • SenderActor: 表示发送消息的Actor对象.
                    • ReceiverActor: 表示接收消息的Actor对象.
                  3. 在该包下创建单例对象Entrance, 并封装main方法, 表示整个程序的入口.

                  4. 把程序启动起来, 如果不报错, 说明代码是没有问题的.

                  参考代码

                   

                   

                  2.3.5 发送/接收消息

                  思路分析

                  1. 使用样例类封装消息

                    • SubmitTaskMessage——提交任务消息
                    • SuccessSubmitTaskMessage——任务提交成功消息
                  2. 使用!发送异步无返回消息.

                  参考代码

                  输出结果

                   

                  3. Akka定时任务

                  需求: 如果我们想要使用Akka框架定时的执行一些任务,该如何处理呢?

                  答: 在Akka中,提供了一个scheduler对象来实现定时调度功能。使用ActorSystem.scheduler.schedule()方法,就可以启动一个定时任务。

                  3.1 schedule()方法的格式

                  注意: 不管使用上述的哪种方式实现定时器, 都需要导入隐式转换和隐式参数, 具体如下:

                   

                  3.2 案例

                  需求

                  1. 定义一个ReceiverActor, 用来循环接收消息, 并打印接收到的内容.

                  2. 创建一个ActorSystem, 用来管理所有用户自定义的Actor.

                  3. 关联ActorSystem和ReceiverActor.

                  4. 导入隐式转换和隐式参数.

                  5. 通过定时器, 定时(间隔1秒)给ReceiverActor发送一句话.

                    • 方式一: 采用发送消息的形式实现.
                    • 方式二: 采用自定义方式实现.

                  参考代码

                   

                  4. 实现两个进程之间的通信

                  4.1 案例介绍

                  基于Akka实现在两个进程间发送、接收消息。

                  1. WorkerActor启动后去连接MasterActor,并发送消息给MasterActor.
                  2. MasterActor接收到消息后,再回复消息给WorkerActor。

                  1552886264753

                  4.2 Worker实现

                  步骤

                  1. 创建一个Maven模块,导入依赖和配置文件.

                    • 创建Maven模块.

                      GroupId: com.itheima

                      ArtifactID: akka-worker

                    • 把资料下的pom.xml文件中的内容复制到Maven项目akka-worker的pom.xml文件中

                    • 把资料下的application.conf复制到 src/main/resources文件夹下.

                    • 打开 application.conf配置文件, 修改端口号为: 9999

                  2. 创建启动WorkerActor.

                    • 在src/main/scala文件夹下创建包: com.itheima.akka
                    • 在该包下创建 WorkerActor(单例对象的形式创建).
                    • 在该包下创建Entrance单例对象, 里边定义main方法
                  3. 发送"setup"消息给WorkerActor,WorkerActor接收打印消息.

                  4. 启动测试.

                  参考代码

                  4.3 Master实现

                  步骤

                  1. 创建一个Maven模块,导入依赖和配置文件.

                    • 创建Maven模块.

                      GroupId: com.itheima

                      ArtifactID: akka-master

                    • 把资料下的pom.xml文件中的内容复制到Maven项目akka-master的pom.xml文件中

                    • 把资料下的application.conf复制到 src/main/resources文件夹下.

                    • 打开 application.conf配置文件, 修改端口号为: 8888

                  2. 创建启动MasterActor.

                    • 在src/main/scala文件夹下创建包: com.itheima.akka
                    • 在该包下创建 MasterActor(单例对象的形式创建).
                    • 在该包下创建Entrance单例对象, 里边定义main方法
                  3. WorkerActor发送"connect"消息给MasterActor

                  4. MasterActor回复"success"消息给WorkerActor

                  5. WorkerActor接收并打印接收到的消息

                  6. 启动Master、Worker测试

                  参考代码

                   

                  5. 案例: 简易版spark通信框架

                  5.1 案例介绍

                  模拟Spark的Master与Worker通信.

                  1552890302701

                  5.2 实现思路

                  1. 构建Master、Worker阶段

                    • 构建Master ActorSystem、Actor
                    • 构建Worker ActorSystem、Actor
                  2. Worker注册阶段

                    • Worker进程向Master注册(将自己的ID、CPU核数、内存大小(M)发送给Master)
                  3. Worker定时发送心跳阶段

                    • Worker定期向Master发送心跳消息
                  4. Master定时心跳检测阶段

                    • Master定期检查Worker心跳,将一些超时的Worker移除,并对Worker按照内存进行倒序排序
                  5. 多个Worker测试阶段

                    • 启动多个Worker,查看是否能够注册成功,并停止某个Worker查看是否能够正确移除

                   

                  5.3 工程搭建

                  需求

                  本项目使用Maven搭建工程.

                  步骤

                  1. 分别搭建以下几个项目, Group ID统一都为: com.itheima, 具体工程名如下:
                  工程名说明
                  spark-demo-common存放公共的消息、实体类
                  spark-demo-masterAkka Master节点
                  spark-demo-workerAkka Worker节点
                  1. 导入依赖(资料包中的pom.xml).

                    注意: master, worker要添加common依赖, 具体如下:

                  2. 分别在三个项目下的src/main, src/test下, 创建scala目录.

                  3. 导入配置文件(资料包中的application.conf)

                   

                  5.4 构建Master和Worker

                  需求

                  分别构建Master和Worker,并启动测试

                  步骤

                  1. 创建并加载Master Actor
                  2. 创建并加载Worker Actor
                  3. 测试是否能够启动成功

                  参考代码

                  5.5 Worker注册阶段实现

                  需求

                  在Worker启动时,发送注册消息给Master.

                  思路分析

                  1. Worker向Master发送注册消息(workerid、cpu核数、内存大小)

                    • 随机生成CPU核(1、2、3、4、6、8)
                    • 随机生成内存大小(512、1024、2048、4096)(单位M)
                  2. Master保存Worker信息,并给Worker回复注册成功消息

                  3. 启动测试

                  具体步骤

                  1. 在spark-demo-common项目的src/main/scala文件夹下创建包: com.itheima.spark.commons

                    把资料下的MessagePackage.scala和Entities.scala这两个文件拷贝到commons包下.

                  2. 在WorkerActor单例对象中定义一些成员变量, 分别表示:

                    • masterActorRef: 表示MasterActor的引用.
                    • workerid: 表示当前WorkerActor对象的id.
                    • cpu: 表示当前WorkerActor对象的CPU核数.
                    • mem: 表示当前WorkerActor对象的内存大小.
                    • cup_list: 表示当前WorkerActor对象的CPU核心数的取值范围.
                    • mem_list: 表示当前WorkerActor对象的内存大小的取值范围.
                  3. 在WorkerActor的preStart()方法中, 封装注册信息, 并发送给MasterActor.

                  4. 在MasterActor中接收WorkerActor提交的注册信息, 并保存到双列集合中..

                  5. MasterActor给WorkerActor发送回执信息(注册成功信息.).

                  6. 在WorkerActor中接收MasterActor回复的 注册成功信息.

                  参考代码

                  5.6 Worker定时发送心跳阶段

                  需求

                  Worker接收到Master返回的注册成功信息后,定时给Master发送心跳消息。而Master收到Worker发送的心跳消息后,需要更新对应Worker的最后心跳时间。

                  思路分析

                  1. 编写工具类读取心跳发送时间间隔
                  2. 创建心跳消息
                  3. Worker接收到注册成功后,定时发送心跳消息
                  4. Master收到心跳消息,更新Worker最后心跳时间
                  5. 启动测试

                  具体步骤

                  1. 在worker的src/main/resources文件夹下的 application.conf文件中添加一个配置.

                    worker.heartbeat.interval = 5 //配置worker发送心跳的周期(单位是 s)

                  2. 在worker项目的com.itheima.spark.work包下创建一个新的单例对象: ConfigUtils, 用来读取配置文件信息.

                  3. 在WorkerActor的receive()方法中, 定时给MasterActor发送心跳信息.

                  4. Master接收到心跳消息, 更新Worker最后心跳时间. .

                  参考代码

                  5.7 Master定时心跳检测阶段

                  需求

                  如果某个worker超过一段时间没有发送心跳,Master需要将该worker从当前的Worker集合中移除。可以通过Akka的定时任务,来实现心跳超时检查。

                  思路分析

                  1. 编写工具类,读取检查心跳间隔时间间隔、超时时间
                  2. 定时检查心跳,过滤出来大于超时时间的Worker
                  3. 移除超时的Worker
                  4. 对现有Worker按照内存进行降序排序,打印可用Worker

                  具体步骤

                  1. 修改Master的application.conf配置文件, 添加两个配置

                  2. 在Master项目的com.itheima.spark.master包下创建: ConfigUtils工具类(单例对象), 用来读取配置文件信息.

                  3. 在MasterActor中开始检查心跳(即: 修改MasterActor#preStart中的代码.).

                  4. 开启Master, 然后开启Worker, 进行测试.

                  参考代码

                  5.8 多个Worker测试阶段

                  需求

                  修改配置文件,启动多个worker进行测试。

                  大白话: 启动一个Worker, 就修改一次Worker项目下的application.conf文件中记录的端口号, 然后重新开启Worker即可.

                  步骤

                  1. 测试启动新的Worker是否能够注册成功
                  2. 停止Worker,测试是否能够从现有列表删除