Skip to content

bigdata:包含scala语言,Hadoop、Hive、HBase、Spark、Flink...相关API在Java与Scala语言下的对比,在本地链接虚拟机服务进行测试

Notifications You must be signed in to change notification settings

ReturnTears/allst-bigdata

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

2018/04/16

每天都要有Get的感觉

Scala的学习之路, 加油 !!!

Part1

1、学习Scala的原因:

Spark是新一代内存级大数据计算框架,是大数据的重要内容
Spark就是使用Scala编写的。因此为了更好的学习Spark,需要掌握Scala这门语言

Scala是Scalable Language的简写, 十一么多范式(范式/编程方式[面向对象/函数式编程]的编程语言)

Spark的兴起带动了Scala语言的发展

Pizza和Scala极大地推动了Java编程语言的发展
jdk5.0的泛型,for循环增强,自动类型转换等都是从Pizza引入的新特性。
jdk8.0的类型推断, Lambda表达式就是从Scala引入的特性。 

Part2

2、java/scala/jvm的关系

java代码 > javac编译器 > 得到.class字节码文件 > 将字节码文件放到JVM(JVM for windows / linux / unix)上运行 > 跨平台
Scala能使用Java的部分语法
Scala有自己特有的语法
Scala增加了函数式编程 / 偏函数 / 函数的柯里化 / 高阶函数 / 将函数作为参数传递
Scala对Java的类进行了包装
Scala代码 > Scalac编译器 > 得到.class字节码文件 > 将字节码文件放到JVM中运 > 跨平台

Part3

Scala

Part4

Scala的特点:

Scala是一门以Java虚拟机(JVM)为运行环境并将 面向对象 和 函数式编程 的最佳特性结合在一起的静态类型编程语言
1)Scala是一门多范式(multi-paradigm)的编程语言, Scala支持面向对象和函数式编程
2)Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上, 并可以调用现有的Java类库,实现两种语言的无缝对接
3)Scala单作为一门语言来看,非常的简洁高效 (舍弃了三元运算 ++ -- 等)
4)Scala的设计者式参考了Java的设计思想,可以说Scala是源于Java

Part5

Windows下搭建Scala开发环境

1-Scala运行需要Java运行时库, 安装Scala需要首先安装JVM虚拟机并配置好环境变量, 推荐安装JDK1.8
2-http://www.scala-lang.org 不同系统选择不同版本

Part6

Linux下搭建Scala开发环境

scala

Part7

Scala的开发工具

IEDA(推荐)
Eclipse

默认情况下, Scala不支持Scala的开发, 需要在IDEA中安装Scala插件

Part8

Scala快速开发入门

Part9

IDEA开发Scala入门

IDEA创建maven项目, 项目中添加上Scala的SDK包,一般在本地安装了Scala, 将安装环境配置到IDEA中就可以了

Part10

Java模拟Scala运行流程机制


Part11

Scala程序开发注意事项(重点)

1 - Scala源文件以".scala"为扩展名
2 - Scala程序的执行入口时main()函数
3 - Scala语言严格区分大小写
4 - Scala犯方法由一条条语句构成, 每个语句后不需要分号(Scala语言会在每行后面自动加分号), 体现了Scala的简洁性
5 - 如果在同一行有多条语句, 除了最后一条语句不需要分号, 其他行语句需要分号

Scala语言转义字符(escape char)

1 - \t  : 一个制表符, 实现对齐的功能      println("name\tage")
2 - \n  : 换行符                      println("name\nage")
3 - \\  : 一个\                      println("C:\\xx")
4 - \"  : 一个"                      println("name\"age\"")
5 - \r  : 一个回车                    println("name\r age")

Scala语言输出的三种方式

1 - 字符串通过 + 号连接 (似Java)
2 - printf用法 (似C)  字符串通过%传值  (%s-String %d-十进制)
3 - 字符串通过$引用 (似PHP)

Part12

Scala源码查看关联

在使用Scala过程中, 为了搞清楚Scala底层机制, 需要查看源码:
1 - 查看源码, 选择要查看的方法或者类, 输入ctrl + b   (将鼠标指针放到要查看的方法或类上, 输入ctrl + b)
2 - 关联源码, 将Attach Source指定到Scala的源代码解压路径下即可

Part13

注释(comment)

用于注释说明解释程序的文字就是注释, 注释提高了代码的可阅读性
注释是一个程序员必须要具有的良好编码习惯,将自己的思想通过注释先整理出来,再用代码去体现
Scala中的注释类型:
1 - 单行注释
2 - 多行注释
3 - 文档注释
    scaladoc -d 文档存放路径 类名称  (最好是直接切换到当前类的路径下)
    scaladoc -d E:\test\doc HelloScala.scala

part14

Scala规范的代码风格

正确的缩进和空白
1 - 使用一次tab操作, 实现缩进,默认整体向右移动, 使用shift+tab整体左移
2 - 使用ctrl + alt + l 格式化代码
3 - 运算符的两边习惯性加一个空格
4 - 一行代码最长不要超过80个字符,超过则使用换行展示,尽量保持格式优雅

scala官方编程指南


Part15 / 16 /17

Scala变量

变量是程序的基本组成单位,变量相当于内存中一个数据存储控件的表示, 你可以把变量看做是一个房间的门牌号,
通过门牌号我们可以找到房间, 而通过变量名可以访问到变量

声明/定义(指声明的同时给变量赋值)变量的关键字:
val: 不可变类型, 没有线程安全问题(推荐使用), 效率较高
var: 可变类型
再Scala中小数默认为Double类型,整数默认为Int类型
isInstanceOf判断数据类型

val | var 变量名 [:变量类型] = 变量值
注意:
1 - 声明变量时,类型可以省略(编译器自动推导,即类型推导)
2 - 类型确定后,就不能修改,说明Scala是强数据类型语言
3 - 再声明/定义一个变量时,可以使用var或者val来修饰, var修饰的变量可改变, val修饰的变量不能改变
4 - val修饰的变量再编译后,等同于加上final
5 - var修饰的对象引用可以改变,val修饰的则不可以改变, 但对象的状态(值)却是可以改变的。
6 - 声明变量时, 需要初始值

程序中 + 号的使用  (同Java)
1 - 当左右两边都是数值型时, 则做加法运算
2 - 当左右两边有一方为字符串, 则做拼接运算

Scala数据类型

1 - Scala与Java有着相同的数据类型,再Scala中数据类型都是对象, 也就是说Scala没有Java中的原生类型
2 - Scala数据类型分为两大类AnyVal(值类型) 和 AnyRef(引用类型), 注意: 不管是AnyVal还是AnyRef都是对象
3 - 相对于Java的类型系统, Scala要更复杂, 也正是这些复杂多变的类型系统才让面向对象编程和函数式编程完美的融合在一起

Image

对上图的解释如下:
1 - 在Scala中有一个根类型, Any是所有类型的父类
2 - Scala中一切皆为对象,分为两大类型AnyVal(值类型),AnyRef(引用类型), 它们都是Any的子类型
3 - Null类型是Scala的特别类型,他只是一个值null, 它是bottom class, 是所有AnyRef类型的子类型
4 - Nothing类型也是bottom class, 它是所有类型的子类型, 在开发中可以将Nothing类型的值返回给任意变量或者函数, 在抛出异常时使用较多
5 - 在Scala中仍然遵守低精度向高精度的值自动转换(implicit conversion)隐式转换

Part18 / 19

Scala数据类型列表

数据类型         |           描述
----            |           ----
Byte            |           8位有符号补码整数。整数区间为-128 ~ 127           -2的7次方 ~ 2的7次方 - 1
Short           |           16位有符号补码整数。整数区间为-32768 ~ 32767
Int             |           32位有符号补码整数。整数区间为-2147483648 ~ 2147483647
Long            |           64位有符号补码整数。整数区间为-9223372036864775808 ~ 9223372036854775807
Float           |           32位,IEEE754标准的单精度浮点数
Double          |           64位,IEEE754标准的双精度浮点数
Char            |           16位无符号Unicode,区间值为U+0000到U+FFFF
String          |           无符序列
Boolean         |           true或false
Unit            |           表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型,Unit是一个实例值,写成()
Null            |           null
Nothing         |           Nothing类型在Scala的类层级的最低端,它是任何其他类型的子类型
Any             |           Any是所有其他类的超类
AnyRef          |           AnyRef类是Scala里所有引用类reference class的基类

补充: 最高位为符号为 [0 1] ·0表示整数 ·1表示负数

整数的使用细节:

1 - Scala个整数类型有固定的表示范围和字段长度, 不受具体的OS的影响, 以保证Scala程序的可移植性性
2 - scala的整型 **常量/字面量** 默认为Int型, 声明Long型 常量/字面量 须后面加"l"或者“L”
3 - Scala程序中变量常声明为Int型, 除非不足以表示大数, 才使用Long

浮点数使用细节:

1 - 与整型类似,Scala浮点数类型也有固定的表示范围和字段长度,不受具体OS的影响
2 - Scala的浮点数类型**常量**为Double型, 声明Float型常量,须后加'f'或'F'
3 - 浮点型常量有两种表示方式:
    十进制数形式:5.12     512.0f  .512 (必须有小数点)
    科学计数法形式:5.12e2 = 5.12 × 10^2  5.12E-2 = 5.12 ÷ 10^2
4 - 通常情况下,应该使用Double型,因此他比Float型更精确(Float大致精确到小数点后7位)

Part20

字符类型(Char)

字符类型可以表示单格字符,字符类型是Char,16位无符号Unicode(Unicode编码包含了assci码)字符(2个字节),区间值为U+0000到U+FFFF

字符类型使用细节:
1 - 字符常量是使用单引号('')括起来的单格字符; var c1 = 'a'
2 - Scala也允许使用转义字符'\'来将其后的字符转变为特殊字符型常量
3 - 可以直接给Char赋一个整数,然后输出时,回按照对应的Unicode字符输出['\u0061' 97]
4 - Char类型是可以进行运算的,详单与一个整数,因为它都对应有Unicode码

注意:

当把一个计算的结果赋值一个变量,则编译器会进行类型转换及判断(即会查看范围+类型)
当把一个字面量赋值一个变量,则编译器会进行数值范围判断

Part21

布尔类型:Boolean

基本介绍:
1 - 布尔类型也叫Boolean类型, Boolean类型数据只允许取值true和false
2 - Boolean类型占1个字节
3 - Boolean类型适用于逻辑运算, 一般用于程序流程控制
> if条件控制语句
> while循环控制语句
> do-while循环控制语句
> for循环控制语句

Unit类型 Null类型 Nothing类型

使用细节和注意事项:
1 - Null类型只有一个实例对象,null,类似于Java的null引用.null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
2 - Unit类型用来标识过程,也就是没有明确返回值的函数.由此可见,unit类似于Java的void.Unit只有一个实例,(),这个实例也没有实质的意义
3 - Nothing,可以作为没有正常返回值的方法的返回值类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容

Zuoye Page

1 - 在Scala REPL(Read evaluateion print loop)中,计算3的平方根,然会再对该值求平方, 这个结果和3相差多少?
    scala> var a = 3
    a: Int = 3
    
    scala> import scala.math
    import scala.math
    
    scala> val b = math.sqrt(a)
    b: Double = 1.7320508075688772
    
    scala> val c = b * b
    c: Double = 2.9999999999999996
    
    scala> val d = c - a
    d: Double = -4.440892098500626E-16
    
2 - Scala语言的sdk是什么?
    开发工具包

3 - Scala环境变量配置及其作用
    注意Windows和LInux下的环境配置区别
    作用是方便开发和使用

4 - Scala程序的编写,编译,运行各个步骤是什么? 能否一步执行?
    
    
5 - Scala程序编写的规则
    同Java, 可以不带分号

6 - 简述: 在配置环境\编译\运行各个步骤中常见错误
    

7 - 如何检测一个变量是val还是var?  
    思路:分别定义一个val和var变量, 重新赋值成功的是var变量, 不能重新赋值的是val变量
    isInstanceOf


8 - Scala允许你用数字去乘一个字符串,去REPL中试一下"crazy" * 3? 这个步骤做什么?再Scaladoc中如何找到这个操作?
    scala> val e = "crezy"
    e: String = crezy
    
    scala> e * 3
    res0: String = crezycrezycrezy

9 - 10 max 2的含义是什么? max方法定义下那个类下?
    scala> 10 max 2
    res1: Int = 10
    或者
    scala> 10.max(2)
    返回两个数的最大值
    max方法定义在RichInt, Int
    
10 - length计算2的1024次方(使用BigInt计算)
    scala> val a = BigInt(2)
    a: scala.math.BigInt = 2
    
    scala> a.pow(1024)
    res0: scala.math.BigInt = 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216

11 - 在Scala中如何获取字符串"Hello"的首字符和尾字符
    Java,JavaScript可以使用substring
    scala> val b = "Hello"
    b: String = Hello
    
    scala> b.take(1)
    res1: String = H
    
    scala> "Hello"(0)
    res4: Char = H
    
    scala> b.reverse.take(1)
    res2: String = o
    
    scala> b.takeRight(1)
    res3: String = o

Part24

值类型转换

自动类型转换细节说明:
1 - 有多种类型的数据混合运算时,系统首先会自动将所有数据转换成容量最大的那种数据类型,然后在进行计算
2 - 当我们把精度(容量)大的数据类型赋值给精度(容量)小的数据类型时就会报错,反之,就会进行自动类型转换
3 - (byte,short)和char之间不会相互转换,(byte和short可以)
4 - byte,short,char它们三者可以计算,在计算时首先转换为int类型
5 - 自动提升原则, 表达式结果的类型自动提升为操作数中最大的类型

高级隐式转换和隐式函数

todo

强制类型转换

自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型,使用时需要加上强制类型转换函数,
但可能造成精度降低或溢出, 格外注意
强制类型转换细节说明:
1 - 当进行数据的从大到小,就需要使用强制类型转换
2 - 强制符号只针对于最近的操作数有效,往往会使用小括号提升优先级
    val num1:Int = 10 * 3.5.toInt + 6 * 1.5.toInt   // 36
    val num2:Int = (10 * 3.5 + 6 * 1.5).toInt       // 44
3 - Char类型可以保持int的常量值,但不能保存Int的变量值,需要强转
4 - Byte和Short类型在进行运算时,当作int类型处理

Part26

值类型和String类型的转换

在开发中我们经常需要将基本数据类型转成String类型.或者将String类型转换成基本数据类型
基本数据类型转String类型:
语法:将基本数据类型的值 + ""即可, String str = true + ""; 或者 4.5 + ""; 或者 1000 + ""
String类型转基本数据类型:
语法: 通过基本类的String.toXXX方法即可
注意事项:
1 - 在将String类型转成基本数据类型时, 要确保String类型能转成有效的数据. 可以把"123"转换成一个整数,但是不能把"hello"转成整数
2 - 如何把"12.5"转成Int ,   在Scala中不是将小数点后的数字进行截取,而是会抛出异常

Part27

标识符的命名规范

标识符概念
1 - Scala对各种变量\方法\函数等命名时使用的字符序列化称为标识符
2 - 凡是自己可以起名字的地方都叫标识符

标识符的命名规则(Remenber)

Scala中的标识符声明, 基本和Java是一致,细节上有所不同.
1 - 首字符为字母, 后续字符任意字母和数字,美元符号,可后接下划线_
2 - 不能以数字开头
3 - 首字符为操作符(+ - * /), 后续字符也需要跟操作符, 至少一个(反编译)
4 - 操作符不能再标识符中间和最后
5 - 用反引号`...`包括的任意字符串,即使是关键字(39个)也可以[true]

在Scala中Int不是保留字(关键字),而是预定义标识符,可以是作为标识符,但是不推荐
不能直接单独使用下划线_作为标识符
val `true` = "Hello Scala"
println(`true`)
注意事项:
1 - 包名:尽量采取有意义的包名,简写,有意义
2 - 变量名\函数名\方法名采用驼峰命名法

Scala关键字(39个)

package import class object trait extends with type forSome
private protected abstract sealed final implicit lazy override
try catch finally throw
if else match case do while for return yield
def val var
this super
new true false null

Part28

运算符

运算符是一种特殊的符号,用以表示数据的运算\赋值和比较等
1 - 算术运算符
    算术运算符(arithmetic)是对数值类型的变量进行运算的,在Scala程序中使用非常多
    + - * / % (+还可以表示'正号','字符串相加',-还可以表示'负号'), 
    在Scala中么有++ --, 而是使用了 +=1 和 -=1
    % 的运算规则: a % b = a - a/b * b
    10 % 3      // 1
    -10 % 3     // -1
    -10 % -3    // -1
    10 % -3     //  1
2 - 赋值运算符
    赋值运算符就是将某个运算后的值赋给指定的变量
    = += -= *= /= %=
    <<= 左移后赋值   a <<= b 等价于 a = a << b
    >>= 右移后赋值
    &= 按位与后赋值
    ^= 按位异或后赋值
    |= 按位或后赋值
    赋值运算的特点:
    1 - 运算顺序从右往左
    2 - 赋值运算符的左边只能是变量, 右边可以是变量,表达式,常量值,字面量
    3 - 复合赋值运算符等价于下面的效果 a += b ==> a = a + b
3 - 比较运算符(关系运算符)
    关系运算符的结果都是Boolean类型,true, false
    关系表达式经常用在if结构的条件或循环结构的条件中
    关系运算符的使用和Java一样
    == != < > <= >=
    
    细节说明:
    1 - 关系运算符的结构都是Boolean类型,true,false
    2 - 关系运算符组成的表达式,我们称为关系表达式, a > b
    3 - 比较运算符"=="不能写成"="
    4 - 使用陷阱: 如果两个浮点数进行比较,应当保证数据类型一致
4 - 逻辑运算符
    用于连接多个条件(一般来讲是关系表达式),最终的结果也是一个Boolean值
    && || !
5 - 位运算符
    & 按位与
    | 按位或
    ^ 按位异或
    ~ 按位取反
    << 左移
    >> 右移
    >>> 无符号右移
Scala不支持三目运算, 在Scala中使用if-else的方式实现,
Scala在这里的设计理念是:一种事情尽量只有一种方法完成,这样可以让代码风格更加统一

运算符的优先级

1 - 运算符有不同的优先级,所谓优先级就是表达式运算中的运算顺序
2 - 只有单目运算符\赋值运算符是从右往左运算的
3 - 运算符优先级和Java一样
L = 从左到右
R = 从右到左
-------运算符--------------关联性------
-      ()[]                 L
-       !~                  R
-      */%                  L
-      + -                  L
-      >> >>> <<            L
-      > >= < <=            L
-      == !=                L
-      &                    L
-      ^                    L
-      |                    L
-      &&                   L
-      ||                   L
-      = += -= /= *= %= >>= <<= &= ^= |=        R
-      ,(逗号运算符)          L              (例: println("a","b"))
从上到下优先级由高到低               

Part35 程序流程控制

    同Java,分为三大类:
    顺序控制: 从上到下逐行执行, 中间没有任何判断和跳转
    分支控制: 
        单分支: if
        双分支: if else
        多分支: if ... else if ... else / switch case
        分支控制的注意事项: 如果大括号{}内的逻辑代码只有一行, 大括号可以省略, 同Java
        Scala中任意表达式都有返回值的, 也就意味着if else表达式其实是由返回结果的, 具体返回结果的值取决于满足条件的代码体的最后一行内容
    循环控制
    
    理论上, 嵌套分支不要超过三层
    
    switch-case 分支结构, 涉及模式匹配 
    
    for循环控制 / for推导式
    for (i <- start to end) {}
    i表示循环的变量, i将会从start到end循环, 前后闭合
    
    循环守卫, 即循环保护式(也称为条件判断式, 守卫), 保护式为true则进入循环体内部, 为false则跳过, 类似continue
    
    until 前闭合后开区间的范围,
    for (i <- start until end) {}
    
    while(condition){循环体}
    
    do {循环体} while(condition)
    
    多层循环, 将一个循环放在另一个循环体内
    
    while循环的中断: Scala内置控制结构特地去掉了break和continue, 是为了更好的适应函数化编程, 推荐使用函数式编程
    break() 函数
    breakable() 函数是一个高阶函数, 可以接收函数的函数就是高阶函数
    也可以使用if-else或者循环守卫实现continue的效果
    
    

函数式编程

函数式编程的基础:
常见概念说明:
1,在Scala中,方法和函数几乎可以等同,只是函数的使用方式更加的灵活多样[方法转函数]
2,函数式编程时从编程方式(范式)的角度来谈的, 可以这样理解: 函数式编程把函数当作一等公民, 充分利用函数,支持的函数的多种使用方式
  在Scala中, 函数是一等公民,想变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量, 函数的创建不用依赖于类或者对象, 而在Java当中, 函数的闯进啊则要依赖于类,抽象类或者接口
3,面向对象编程是以对象为基础的编程方式
4,在Scala中函数式编程和面向对象编程融合在一起

函数定义/声明
  def 函数名([参数名: 参数类型], ...)[:返回值类型] = {
    语句
    return 语句
  }
  1,函数声明关键字为def
  2,[参数名:参数类型],... 表示函数的输入,可以没有, 如果有,多个参数使用逗号隔开
  3,函数中的语句,表示为了实现某一功能代码块
  4,函数可以有返回值,也可以没有
  5,返回值形式1: :返回值类型 = 
  6,返回值形式2: = 表示返回值类型不确定, 使用类型推导完成
  7,返回值形式3:  空表示没有返回值, return不生效
  8,如果没有return, 默认以执行到最后一行的结果作为返回值
  
  函数的注意事项:
    1),函数的形参列表可以是多个, 如果函数没有形参, 调用时可以不带()
    2),形参列表和返回值列表的数据类型可以是值类型和引用类型
    3),Scala中的函数可以根据函数体最后一行代码自行推断返回值类型,
    4),因为Scala可以自行推断, 所以在省略return关键字的场合, 返回值类型也可以省略
    5),如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成":返回值类型=",
       当然如果你什么都不写, 说明不需要返回值, 即使有return, 那么返回值也为()
    6),如果函数明确无返回值(声明Unit), 那么函数体中即使使用return关键字,也不会有返回值
    7),如果明确函数无返回值或不确定返回值类型, 那么返回值类型可以省略(或者声明为Any)
    8),Scala语法中任何的语法结构都可以嵌套其他语法结构, 即: 函数中可以再声明/定义函数, 
       类中可以再声明类, 方法中可以再声明/定义方法
    9),Scala函数的形参, 在声明参数时, 直接赋值初始值(默认值), 这时调用函数时,如果没有指定实参, 则会使用默认值.
       如果指定了实参,则实参会覆盖默认值.
    10),如果函数存在多个参数, 每个参数都可以设定默认值, 那么这个时候传递的参数到底是覆盖默认值, 还是赋值给没有默认值的参数,
       就不确定了(默认按照声明顺序[从左到右]),在这种情况下,可以采用带实名参数
    11), Scala函数的形参默认是val的, 因此不能再此函数中进行修改
    12),递归函数未执行之前是无法推断出结果类型的, 在使用时必须有明确的返回值类型
    13),Scala函数支持可变参数, 可变参数需要写在形参的最后, args是集合,通过for循环可以访问到各个值
函数运行机制
  函数的调用机制:类似Java
递归[推荐使用递归来解决问题]
  每调用一次函数,在内存中就会开辟一个栈空间
  
  函数递归需要遵守的重要原则:
  1,执行一个函数时, 就创建一个新的受到保护的独立空间(新函数栈)
  2,函数的局部变量是独立的, 不会相互影响
  3,递归必须向退出递归的条件逼近, 否则就是无限递归
  4,当一个函数执行完毕, 或者遇到return,就会返回, 遵守谁调用就将结果返回给谁
  
过程(过程就是一个概念)
  将函数的返回类型为Unit的函数称之为过程(procedure), 如果明确函数没有返回值, 那么等号可以省略
  
  注意: 如果函数声明时没有返回值类型, 但是有=号, 可以进行类型推断最后一行代码.
       这时这个函数实际是有返回值的, 该函数并不是过程.
  
惰性函数
  惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性,惰性集合再需要时提供其元素,无需预先计算他们,
  这带来了一些好处, 首先,您可以将耗时得计算推迟到绝对需要得时候, 其次,您可以创造无限个集合, 只要他们继续收到请求,就会继续提供元素
  函数得惰性使用让您能够得到更高效得代码, Java并没有为惰性提供原生支持,Scala提供了.
  
  当函数返回值被声明为lazy时, 函数的执行将被推迟, 直到我们首次对此取值, 该函数才会执行.
  这种函数我们称之为惰性函数, 在Java的某些框架代码中称之为懒加载(延迟加载)
  
  注意:
  lazy 不能修饰var类型变量
  不但是在调用函数时, 加了lazy,会导致函数的执行被退出, 我们在声明一个变量时, 如果给声明了lazy, 那么变量值得分配也会推迟
  
异常
  1,Scala提供try和catch块来处理异常. try块用于包含可能出错得代码. 
    catch块用于处理try块中发生得异常. 可以根据需要在程序中有任意数量得try...catch块
  2,语法处理上和Java类似, 但是又不尽相同
  3,在Scala中只有一个catch, 可以有多个case, 每个case可以匹配一种异常case ex: xxx
    => 关键符号,表示后面是要对异常的处理代码块
    finally 最终要执行的
  
  小结:
    1),将可疑代码封装在try块中, 使用catch处理程序来捕获异常,发生异常时,catch处理厂程序将处理它, 程序将不会异常终止
    2),Scala的异常工作机制和Java一样,但是Scala没有"checked(编译器)"异常,所有异常都在运行时去捕获处理
    3),用throw关键字,抛出一个异常对象.所有异常都是Throwable的子类型, throw表达式是有类型的, 就是Nothing
       因为Nothing是所有类型的子类型, 所以throw表达式可以用在需要类型的地方
    4),在Scala中, 借助模式匹配的思想来做异常的匹配, 因此,在catch的代码里, 是一系列case子句来匹配异常
    5),越具体的异常越靠前, 越普遍的异常越靠后
    6),finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤, 一般用于清理工作
    7),Scala中提供了throws关键字来声明异常
    
    
面向对象编程:
  Java是面向对象的编程语言, Java中还存在着非面向对象的内容, 基本类型, null, 静态方法
  Scala语言来自于Java, 所以天生就是面向对象的语言, 而且Scala是纯粹的面向对象语言, 即在Scala中一切皆对象
  在面向对象的学习过程中可以对比着Java语言学习
  
  类和对象的区别和联系
  1),类是抽象的, 概念的,代表一类事物,
  2),对象是具体的, 实际的,代表一个具体事物
  3),类是对象的模板,对象是类的一个个体, 对应一个实例
  4),scala中类和对象的区别和联系和Java是一样的
  
  [修饰符]class 类名 {
    类体
  }
  定义类的注意事项:
  1),Scala语法中, 类并不声明为public, 所有这些类都具有共有可见性(即默认就是public)
  2),一个Scala源文件可以包含多个类
  3),属性是类的一个组成部分,一般是值数据类型, 也可是引用类型
  定义属性的注意事项:
  1),属性的定义语法同变量, [访问修饰符]var 属性名称[:类型] = 属性值
  2),属性的定义类型可以为任意类型, 包含值类型或引用类型
  3),Scala中声明一个属性, 必须显示的初始化, 然后根据初始化数据的类型自动推断, 属性类型可以省略
  4),如果赋值为null, 则一定要加类型, 因为不加类型, 那么该属性的类型就是Null类型
  5),如果在定义属性是, 暂时不赋值, 也可以使用符号_(下划线), 让系统分配默认值
       类型                   _对应的值
       Byte,Short,Int,Long      0
       Float, Double            0.0
       String和引用类型           null
       Boolean                  false
  6),不同对象的属性是相互独立的, 互不影响,一个对象对属性的更改,不影响另一个
    
  属性的高级部分
  说明: 属性的高级部分和构造器(构造方法/函数)相关, 
   
   创建对象基本语法:
   val | var 对象名[: 类型] = new 类型()
   说明:
      1),如果我们不喜欢改变对象的引用(即内存地址), 应该声明为val性质的, 否则声明为var,
        Scala设计者推荐使用val, 因为一般来说, 在程序中, 我们呢只改变对象属性的值, 而不是改变对象的引用
      2),Scala在声明对象变量时,可以根据创建对象的类型自动推断, 所有类型声明可以省略, 但当类型和后面new对象类型有继承关系即多态时,
        就必须写类型
   
   方法
   Scala中的方法其实就是函数, 声明对照请参考函数式编程中的函数声明
   基本语法:
   def 方法名(参数列表)[: 返回值类型] = {
        方法体
   }
   方法的调用机制: 程序调用方法过程 + 说明
   1), 当我们Scala开始执行时, 先在栈区开辟一个main栈, mainzh栈是最后销毁
   2),当Scala程序在执行到一个方法时, 总会开一个新的栈
   3),每个栈是独立的空间, 变量(基本数据类型)是独立的, 互不影响
   4),当方法执行完毕后, 该方法开辟的栈就会被JVM回收
   
   构造器
   Scala构造器的介绍
   和Java一样,Scala构造对象也需要调用构造方法, 并且可以有任意多个构造方法(也支持重载)
   Scala类的构造器包括: 主构造器和辅助构造器
   
   Scala构造器的基本语法
   class 类名(形参列表) {     // 主构造器只有一个
        // 类体
        def this(形式参数1) {    // 辅助构造器
            
        }
        def this(形式参数2) {    // 辅助构造器可以有多个
                    
        }
   }
   辅助构造器函数的名称this, 可以有多个, 编译器通过不同参数来区分
   构造器的注意事项和细节:
   1),Scala构造器作用是完成对新对象的初始化, 构造器没有返回值
   2),主构造器的声明直接放置于类名之后
   3),主构造器会执行定义中的所有语句, 这里可以体会到Scala的函数式编程和面向对象编程融合在一起, 即:构造器也是方法(函数), 传递参数和使用方法和函数没有区别
   4),如果主构造器无参数, 小括号可以省略
   5),辅助构造器名称是this, 多个辅助构造器通过不同参数列表进行区别
   6),如果想让主构造器为私有的, 可以在()之前加上private, 这样用户只能通过辅助构造器来构造对象了
   7), 辅助构造器的声明不能和主构造器的声明一致, 会发生错误
   
   属性的高级部分
   属性的高级部分和构造器(构造器,构造方法)相关, 
   1),Scala类的主构造器的形参未用任何修饰符修饰, 那么这个参数是局部变量
   2),如果参数使用val关键字声明, 那么Scala会将参数作为类的私有的只读属性
   3),如果参数使用var关键字声明, 那么Scala会将参数作为类的成员属性使用, 并会提供属性对应的xx()[类似getter]/xx_$eq()[类似setter]方法, 即这时的成员属性是私有的, 但是可读写
   
   Bean属性
   JavaBean规范定义了Java的属性是像getXx()和setXx()方法, 将Scala字段加@BeanProperty时, 这样会自动生成规范的setXx/getXx方法,可以使用对象的getXx方法和setXx方法来调用属性
   注意: 给属性添加了@BeanProperty注解,会生成getXx和setXx方法, 并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以并存
   
   包
   Scala包的基本介绍:
   和Java一样, Scala中管理项目可以使用包, 但Scala中的包的功能更加强大,使用也相对复杂
   基本语法
   package 包名
   
   Scala包的三大作用(和Java一样)
   1),区分相同名字的类
   2),当类很多时, 可以很好的管理类
   3),控制访问范围
   4),可以对类的功能进行扩展
   
   Scala中包名和源码所在的系统文件目录可以不一致,但是编译后的字节码文件路径和包名会保持一致
   
   Scala包的命名方式
   只能包含数字,字母,下划线,小圆点. ,但不能以数字开头,也不能使用关键字
   命名规范:一般是小写字母+小圆点, 例如: com.公司名称.项目名称.业务模块名称
   Scala会自动引入的常用包
   java.lang.*
   scala包
   Predef包
   
   Scala包的注意事项和使用细节
   1),Scala进行package打包时, 可以有如下形式:
   查看PackageMth
   2),包也可以像嵌套类那样使用(包种有包), 这个在使用第三种方式时的好处是:程序员可以在同一个文件中将类(class/object),trait创建在不同的包中,这样很灵活
   3),作用域原则: 可以直接向上访问, 即Scala种子包种直接访问父包种中的内容,大括号体现作用域, 在子包和父包类重名时, 采用就近原则,如果希望指定使用某个类,则带上包名即可
   4),父包要访问子包的内容时,需要import对应的类
   5),可以在同一个.scala文件中,声明多个并列的package(建议嵌套的package不要超过3层)
   6),包名可以相对路径也可以绝对路径,比如访问BeanProperty的绝对路径是:
      _root_.scala.beans.BeanProperty,在一般情况下, 我们使用相对路径来引入包, 只有当包名称冲突时,才使用绝对路径
   
   包对象
   基本介绍: 包可以包含类, 对象和特质trait, 但不能包含函数/方法或变量的定义, 这是Java虚拟机的局限, 为了弥补这一点,Scala提供了包对象的概念来解决这个问题
   查看PackageMth中的包对象部分
   包对象的注意事项:
   包对象和子包是一个平级的关系,
   包对象的名称和子包的名称一致, 一般用来对包的功能补充
   
   包的可见性
   Java提供了四种访问控制修饰符号控制方法和变量的访问权限(范围):
   1),公开级别: 用public修饰, 对外公开
   2),受保护级别,用protected修饰,对子类和同一个包中的类公开
   3),默认级别, 没有修饰符号,向同一个包中的累公开
   4),私有级别,用private修饰,只有类本身可以访问,不对外公开
   Scala包的可见性
   在Scala中同样可以通过类似Java的修饰符达到同样的效果
   
   Scala的设计者将static关键字取消了, 他认为static不是真正的面向对象, 他就设计了伴生类和伴生对象的概念来替代static
   伴生类 写静态的内容, 伴生对象 写静态内容, 在伴生对象中可以访问伴生类中的属性及私有属性
   
   Scala中包的可见性和访问修饰符的使用
   1,当属性访问权限为默认时, 从底层看属性是private的,但是因为提供了xxx_$eq()类似[setter] / xxx()类似[getter]方法, 因此从使用效果来看是任何地方可以访问的
   2,当方法访问权限为默认时,默认为public访问权限
   3,private为私有权限,只在类的内部和伴生对象中可用
   4,protected为受保护权限,Scala中受保护权限比Java中更严格,只能子类访问,同包无法访问
   5,在Scala中没有public关键字,即不能用public显式的修饰属性和方法
   6,包访问权限(表示属性有了限制,同时包也有了限制), 这点和Java不一样, 体现了Scala包使用的灵活性
   
   包的引入
   Scala包的引入基本介绍
   Scala引入包也是使用import关键字,基本原理和机制同Java, 但是Scala中的import更加强大,也更灵活
   因为Scala语言原子Java,所以Java.lang包中的类会自动引入到当前环境中, 而Scala中的Scala包和Predef包的类也会自动引入到当前环境中, 随即其下的类可以直接使用
   如果想要把其他包中的类引入到当前环境中, 需要使用import语句
   Scala引入包的细节和注意事项
   1,在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一致延伸到包含该语句的末尾, 这种语法的好处是:在需要时才引入包, 缩小import包的作用范围,提高效率
   2,Java中如果想要导入包中所有的类, 可以使用通配符*, Scala中采用下划线_
   3,如果只想引入某个包下的部分类,可以使用选择器{类名称},选择引入包的指定类,
   4,如果引入的多个包中含有相同的类, 那么可以将不需要的类进行重命名来区分{类名称 => 新名称}
   5,如果某个冲突的类根本不会用到, 那么这个类可以直接隐藏掉{类名称 => _, _} , 第一个_表示忽略掉的类名称, 第二个下划线_表示引入所有
   
   面向对象编程的三大特性
   封装:把抽象出来的数据和对数据的操作封装在一起, 数据被保护在内部, 程序的其他部分只有通过被授权的操作(成员方法), 才能对数据进行操作
       封装的好处:
       1,隐藏实现细节, 2,加入数据验证,保证安全合理, 3,同时可以加入逻辑
       如何体现封装:
       1,对类中的属性进行封装, 2,通过成员方法,包实现封装
       封装的步骤:
       1,对属性进行私有化
       2,提供一个公共的set方法, 用于对数学判断并赋值
         def setXX(参数名称; 类型):Unit = {
            // set中可以加入逻辑
            属性 = 参数名
         }
       3,提供一个公共的get方法, 用于获取属性的值
         def getXX()[:返回类型] = {
            return 属性
         }
   Scala封装的注意事项:
   1),Scala中为了简化代码的开发, 当声明属性时, 本身就自动提供了对应setter/getter方法,如果属性声明为private的, 那么自动生成的setter/getter方法也是private的, 如果属性省略访问权限修饰符, 那么自动生成的setter/getter方法是public的
   2),如果我们只对一个属性进行简单的set和get, 只要声明一下该属性(属性使用默认修饰符), 访问时直接对象.变量即可, 这样做是为了保持访问一致性
   3),对象.属性直接访问属性,其实底层仍然是访问的方法
   4),当前有很多新的框架在进行反射式,也支持属性的直接反射
   
   继承: 
   Java继承的回顾:
   class 子类名 extends 父类名称 { 类体 }
   子类继承了父类的属性和方法
   继承可以解决代码复用, 让我们的编程更加靠近人类的思维, 当多个类存在相同的属性和方法时,我们可以从这些类中抽象出父类, 在父类中定义这些相同的属性和方法, 子类继承即可
   Scala和Java一样,Scala也支持类的单继承
   
   Scala继承的基本语法
   class 子类名称 extends 父类名称 { 类体 }
   继承的优点:
   1),代码的复用性提高了
   2),代码的扩展性和维护性提高了
   子类继承了父类的所有属性,只有私有的属性不能访问,需要通过公共的方法去访问
   属性和方法加了protected,则该属性和方法只能在子类中使用
   属性和方法加了private,则该属性和方法只能在本类或伴生对象中使用
   
   重写方法
   Scala明确规定, 重写一个非抽象方法需要使用override修饰符, 调用超类的方法使用super关键字
   Scala中类型检查和转换
   基本介绍
   要测试某个对象是否属于某个给定的类, 可以用isInstanceOf方法,用asInstanceOf方法将引用转换为子类的引用. classOf获取对象的类名
   1),classOf[String]就如同Java的String.class
   2),obj.isInstanceOf[T]等同于Java的obj instanceof T判断obj是不是T类型
   3),obj.asInstanceOf[T]等同于Java的(T)obj将obj强转为T类型
   最佳实践
   类型检查和转换的最大价值在于: 可以判断传入对象的类型, 然后转成对应的子类对象, 进行相关操作, 体现了多态的特点
   
   Scala中超类的构造
   回顾Java中超类的构造:在Java中,创建子类对象时, 子类的构造器总是去调用一个父类构造器(显示或者隐式调用)
   1),类有一个主构造器和任意数量的辅助构造器, 而每个辅助构造器都必须先调用主构造器(也可以是间接调用)
   2),只有主构造器可以调用父类的构造器,辅助构造器不能直接调用父类的构造器,在Scala的构造器中, 你不能调用super(params)
   
   覆写字段
   基本介绍:
   在Scala中, 子类改写父类的字段, 我们称为覆写/重写字段, 覆写字段需使用override修饰
   Java回顾:
   在Java中只有方法的重写, 没有属性/字段的覆写, 
   隐藏的概念: 父类和子类定义同一名称的字段时, 不会报错, 但是对于同一个对象,用父类的应用去取值(字段),会获取父类字段的值, 用子类的对象去取值(字段),会获取子类子类字段的值
            但是,在实际开发中应该尽量避免这种父类子类名称相同的情况
   JVM的动态绑定机制:
   如果调用的是方法, 则JVM将该方法和对象的内存地址绑定
   如果调用的是属性, 则没有动态绑定机制, 谁调用就返回对应的值
   
   向上造型:父类引用子类对象
   
   Scala字段的override
   细节:
   1),def只能重写另一个def(即: 方法只能重写另一个方法)
   2),val只能重写另一个val属性或重写不带参数的def
   3),val只能重写另一个抽象属性(声明未初始化的变量就是抽象属性), 当一个类含有抽象属性时, 则该类需要标记为抽象类, 抽象属性编译后不会生成get/set方法
   
   抽象类
   在Scala中, 通过abstract关键字标记不能被实例化的累,方法不用标记abstract, 只省略掉方法体即可,
   抽象类可以拥有抽象字段, 也可以拥有非抽象字段
   抽象类的价值:
   抽象类的价值更多是在于设计, 是设计者设计好后,让子类继承并实现抽象类(实现抽象类的抽象方法)
   抽象类注意事项:
   1),抽象类不能被实例化
   2),抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法
   3),一旦类包含了抽象方法或者抽象属性, 则这个类必须声明为abstract
   4),抽象方法不能有主体, 不允许使用abstract修饰
   5),如果一个类继承了抽象类, 则它必须实现抽象类的所有抽象方法和抽象属性, 除非他自己也声明为abstract类
   6),抽象方法和抽象属性不能使用private, final来修饰, 因为这些关键字都是和重写/实现相违背的
   7),抽象类中可以有实现的方法
   8),子类重写抽象方法不需要override, 写上也不会错
   
   匿名子类
   和Java一样, 可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类
   
   
   
多态: 
   面向对象编程方法- 抽象
   在定义一个类的时候就是把一个事物的公共属性和行为提取出来,形成一个物理模型(模板), 这种研究问题的方法叫做抽象
   
静态属性和静态方法
    Scala中静态地概念:伴生对象
    Scala语言是完全面向对象(万物皆对象)地语言,所以并没有静态地操作(即在Scala中没有静态地概念)
    但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊地对象来模拟类对象,我们称之为类地伴生对象.
    这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
    
    伴生对象总结
    1),Scala中伴生对象采用object关键字声明, 伴生对象中声明的全是"静态"内容, 可以通过伴生对象名称直接调用
    2),伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致
    3),伴生对象中的属性和方法可以直接通过伴生对象直接调用访问
    4),Scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类, 实现属性和方法的调用
    5),从语法角度讲,所谓的伴生对象其实就是类的静态方法和成员的集合
    6),从底层原理看,伴生对象实现静态特性是依赖于public static final MODULE$实现的
    7),伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不再同一个文件中会运行错误),但是如果没有伴生类,也就没有所谓的伴生对象了,所有放在哪里就无所谓了
    8),如果class A独立存在, 那么A 就是一个类, 如果object A独立存在, 那么A就是一个"静态"性质的对象(即类对象), 在object A中声明的属性和方法可以通过A.属性和A.方法来实现调用 
   
   伴生对象-apply方法
   在伴生对象中定义apply方法, 可以实现: 类名(参数)方式来创建对象实例
   val list = list(1,2,3)

Scala接口
    从面向对象来看, 接口并不属于面向对象的范畴,Scala是纯面向对象的语言, 在Scala中, 没有接口
    Scala语言中, 采用特质trait(特征)来代替接口的概念, 也就是说, 多个类具有相同的特质(特征)时,
    就可以将这个特质独立出来, 采用关键字trait声明
    trait等价于Java中interface+abstract class
    
    trait的声明
    trait 特质名称 {
        trait 体
    }
    
    trait命名一般首字母大写
    Cloneable, Serializable
    object T1 extends Serializable {
    
    }
    Serializable就是Scala的一个特质.
    在Scala中,Java中的接口可以当作特质使用
    
    scala中trait的使用
    一个类具有某种特质(特征), 也就意味着这个类满足了这个特质的所有要素,所以在使用时,也采用了extends关键字
    如果有多个特质或存在父类, 那么需要采用with关键字连接
    没有父类
    class 类名 extends 特质1 with 特质2 with 特质3...
    有父类
    class 类名 extends 父类 with 特质1 with 特质2 with 特质3
   
   可以把特质看作是对继承的一种补充
   Scala的继承是单继承,也就是一个类只能有一个父类, Scala引入了特质一是可以替代Java的接口, 二是对单继承的补充
   Scala提供的特质(trait)可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质
   特质中没有实现的方法就是抽象方法, 类通过extends继承特质, 通过with可以继承多个特质
   所有的Java接口都可以当作Scala特质使用
   带有特质的对象, 动态混入
   1),除了可以在类声明时继承特质以外, 还可以在构建对象时混入特质,扩展目标类的功能
   2),此种方式也可以应用于对抽象类功能进行扩展
   3),动态混入是Scala特有的方式(Java没有动态混入), 可在不修改类声明/定义的情况下,扩展类的功能,非常灵活,耦合性低
   4),动态混入可以在不影响原有的继承关系的基础上, 给指定的类扩展功能
   
   Scala创建对象的集中方式:
   new 对象
   apply创建
   匿名子类
   动态混入
   四种方式的底层都一样
   
   叠加特质(TraitOverlay.class)
   对象创建的顺序以及方法的执行顺序
   
   特质的声明顺序从左到右
   Scala在执行叠加对象的方法是, 会首先从后面(从右到左)的特质开始执行
   
   如果想要调用具体特质的方法, 可以指定super[特质].xxx()其中的泛型必须是该特质的直接超类类型
   
   在特质中重写抽象方法TraitMthOver.class
   
   特质的富接口
   富接口: 指该特质中既有抽象方法又有非抽象方法
   
   特质中的具体字段
   特质中可以定义具体字段, 如果初始化了就是具体字段, 未初始化就是抽象字段
   混入该特质的类就具有了该字段, 字段不是继承,而是直接加入类成为自己的字段
   特质中的抽象字段
   特质中未被初始化的字段在具体的子类中必须被重写
   
   特质的构造顺序
   特质也是具有构造器的, 构造器中的内容由"字段的初始化"和一些其他语句构成
   第一种特质构造顺序(声明类的同时混入特质):
   调用当前类的超类构造器
   第一个特质的父特质构造器
   第一特质构造器
   第二特质构造器的父特质构造器, 如果以及执行过就不再执行
   第二特质构造器
   ...
   
   第二种特质构造顺序(在构建对象时, 动态混入特质):
   调用当前类的超类构造器
   当前类构造器
   第一个特质构造器的父特质构造器
   第一个特质的构造器
   第二个特质构造器的父特质构造器, 如果已经执行过就不再执行
   第二个特质的构造器
   ...
   
   这两种方式对构造顺序的影响:
   第一种实际是构建类对象, 在混入特质时, 该对象还没有创建
   第二种实际是构造匿名子类, 可以理解成在混入特质时, 对象以及创建了
   
   扩展类特质
   1,所有混入该特质的类会自动成为那个特质所继承的超类的子类
   2,如果混入该特质的类, 已经继承了另一个类(A类), 则要求A类是特质超类的子类, 否则会出现多继承现象,发送错误
   
   特质自身类型
   自身类型: 主要是为了解决特质的循环依赖问题, 同时可以确保特质在不扩展某个类的情况下, 依然可以做到限制混入该特质的类的类型
   
补充特质:
   当一个类继承了一个trait, 那么该类的实例就可以传递给这个trait
   trait myTrait {}
   
   class yourTrait extends myTrait {}
   
   object Hersy {
      def test(m: myTrait): Unit = { println("ok) }
   }
   val a = new yourTrait
   Hersy.test(a)
   
   
Scala中的嵌套类
    在Scala中, 你几乎可以在任何语法结构中内嵌任何语法结构, 如果类中可以再定义一个类,
    这样的类就是嵌套类, 其他的语法结构也是一样, 嵌套类类似于Java中的内部类
    回顾Java:
    Java中有五大成员: 属性,方法,内部类,构造器,代码块
    在Java中, 一个类的内部又完整的嵌套了另一个完整的类结构, 被嵌套的类称为内部类(inner class),
    嵌套其他类的称为外部类, 内部类最大的特点就是可以直接访问私有属性, 并且可以体系那类与类之间的包含关系
    Java内部类的分类:
    从定义在外部类的成员位置上来看:
    1,成员内部类(没有static修饰)
    2,静态内部类(使用static修饰)
    定义在外部类局部位置上(方法内)看:
    1,分为局部内部类(有类名)
    2,匿名内部类(没有类名)

    内部类中访问外部类的属性:
    1,在Scala中内部类如果想要访问外部类的属性, 可以通过外部类对象访问, 即访问方式: 外部类名称.this.属性名
    2,内部类如果想要访问外部类的属性, 也可以通过外部类别名访问(推荐),即外部类名别名.属性名
   
    Scala类型投影
    类型投影: 在方法的声明上, 如果使用外部类#内部类的方式, 表示忽略内部类的对象关系, 等同于Java中内部类的语法操作. 
    这种方式我们称之为类型投影(即: 忽略对象的创建方式, 只考虑类型)
    
    
    Scala的隐式转换与隐式参数
    隐式函数的基本介绍: 指其功能是隐式的, 隐式转换函数是以implicit关键字声明的带有单个参数的函数, 这种函数将会自动应用, 将值从一种类型转换为另一种类型 
    使用隐式转换函数可以优雅的解决数据类型转换
    隐式转换的注意事项和细节:
    1), 隐式转换函数的函数名可以是任意的, 隐式转换与函数名称无关, 只与函数签名有关(函数参数类型和返回值类型)
    2), 隐式函数可以有多个(即: 隐式函数列表), 但是需要保证在当前环境下, 只有一个隐式函数能被识别
    
    隐式转换丰富类库功能
    基本介绍:
    如果现需要为一个类增加一个方法, 可以通过隐式转换来实现.(动态增加功能), 比如想为MySQL类增加一个delete方法
    分析解决方案:
    在当前程序中, 如果想要给MySQL类增加功能是非常简单的, 但是在实际项目中, 如果想要增加新的功能就会需要改变源代码, 这是很难受的.
    而且违背了软件开发中的OCP(开闭原则)开发原则, 在这种情况下, 可以通过隐式转换函数给类动态添加功能
    指定某些数据类型的相互转换
    
    隐式值
    隐式值也叫隐式变量, 将某个形参变量标记为implicit, 所有编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数
    默认值只能作用在一个函数的形参, 隐式值可以作用多个函数的形参
    隐式值得优先级高于默认值: 同时有implicit修饰得隐式值和默认参数, 隐式值得优先级高
    挡在程序中有传值, 隐式值, 默认值时: 有点急高低:传值 > 隐式值 > 默认值
    当一个隐式参数匹配不到隐式值时任然会使用默认值
    没有隐式值, 没有默认值, 没有传值时,就会报错
    
    隐式类
    基本介绍:
    在Scala2.10后提供了隐式类, 可以使用implicit声明类, 隐式类非常强大, 同样可以扩展类得功能,比前面使用隐式转换丰富类库功能更加得方便, 在集合中隐式类会发挥重要得作用.
    隐式类使用有如下几个特点:
    1),其所带的构造参数有且只能有一个
    2),隐式类必须被定义在"类"或"伴生对象"或"包对象"里, 即隐式类不能是顶级的(top-level objects)
    3),隐式类不能是case class (case class在后续介绍)
    4),作用域内不能有与之相同名称的标识符
    
    隐式转换时机
    1,当方法中的参数的类型与目标类型不一致时, 或者时赋值时
    2,当对象调用所在类中不存在的方法或成员时, 编译器会自动将对象进行隐式转换(根据类型)
    
    隐式解析的机制
    即编译器是如何查找到缺失信息的, 解析具有以下两种规则
    1),首先会在当前代码作用域下查找隐式实体(隐式方法, 隐式类, 隐式对象)
    2),如果第一条规则查找隐式实体失败, 会继续在隐式参数的类型作用域查找.
       类型的作用域是指与该类型相关联的全部伴生模块, 一个隐式实体的类型T它的查找范围如下:
       a,如果T被定义为T with A with B with C, 那么A,B,C都是T的部分, 在T的隐式解析过程中, 它们的伴生对象都会被搜索
       b,如果T是参数化类型, 那么类型参数和与参数相关联的部分都算作T的部分, 比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象
       c,如果T是一个单例类型p.T, 即T是属于某个P对象内, 那么这个p对象也会被搜索
       d,如果T是个类型注入S#T, 那么S和T都会被搜索
    
    隐式转换的前提
    在进行隐式转换时, 需要遵守两个基本的前提:
    1,不能存在二义性
    2, 隐式操作不能嵌套
    
    
     
    

函数式编程高级

值函数(函数字面量)

高阶函数
基本介绍:
能够接受函数作为参数的函数,叫做高阶函数(higher-order function), 可使应用程序更加健壮
高阶函数可以返回匿名函数, 该匿名函数可以使用变量来接收

参数(类型)推断
参数推断省去类型信息(在某些情况下[需要有应用场景], 参数类型是可以推断出来的, 如:list=List(1,2,3) list.map() map种函数参数类型是可以推断的), 同时也可以进行相应的简写
参数类型推断写法说明:
1),参数类型是可以推断时, 可以省略参数类型
2),当传入的函数只有单个参数时, 可以省去括号
3),如果变量只在=>右边只出现一次,可以用_来代替


闭包(Closure)
基本介绍:
闭包是一种语法现象, 而不是某一个语言单有的(PHP,JavaScript,Scala都有闭包)
闭包就是一个函数和与其相关的的引用环境组合的一个整体(实体)
闭包的好处(结合案例ClosureFunc1):
1), 返回的匿名函数和makeSuffix(suffix: String)的suffix变量组合成一个闭包, 因为返回的函数引用到suffix这个变量
2), 如果使用传统的方法,也可以轻松实现这个功能, 但是传统方法需要每次都传入后缀名, 比如.png, 而闭包因为可以保留上次引用的某个值,
    所以我们传入一次就可以反复使用


应用函数

柯里化函数(curry)
基本介绍:
1),函数编程中, 接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化
2),柯里化就是证明了函数只需要一个参数而已,其实我们刚才的学习过程中,已经涉及到了柯里化操作
3),不用设立柯里化存在的意义这样的命题, 柯里化就是以函数为主体这种思想发展的必然产生的结果(即:柯里化是面向函数思想的必然产生结果)

抽象控制
基本介绍:
控制抽象是这样的函数, 必须满足如下条件
1),参数是函数
2),函数参数没有输入值也没有返回值


偏函数(partial function)
基本介绍
1),在对符合某个条件, 而不是所有情况进行逻辑操作时, 使用偏函数是一个不错的选择
2),将包含在大括号内的一组case语句封装为函数, 我们称之为偏函数, 它只是会作用于指定类型的参数或指定范围值的参数实施计算, 超出范围的值会忽略(也未必会忽略,取决于你自己如何处理)
3),偏函数在Scala中是一个特质PartialFunction
偏函数小结:
1),使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)
2),PartialFunction是一个特质
3),构建函数时, 参数形式[Any, Int]是泛型, 第一个表示参数类型,第二个表示返回值类型
4),当使用偏函数时,会遍历集合的所有元素,编译器执行流程会先执行isDefinedAt(),如果为true就会执行apply,构建一个新的Int对象返回
5),执行isDefinedAt()为false就过滤掉这个元素,即不构建新的Int对象
6),map函数不支持偏函数,因为map底层的机制就是循环遍历,无法过滤处理原来集合的元素
7),collect函数支持偏函数

偏函数的简化形式
声明偏函数需要重写特质中的方法,有时候会略显麻烦, 而Scala其实提供了简单的方法
1),简化形式one
def f1: PartialFunction[Any, Int]={
    case i: Int => i + 1
}
val list1 = List(1,2,3,"Scala").collect(f1)

2),简化形式two
val list2 = List(1,2,3,"Scala").collect{case i: Int => i + 1}


作为参数的函数
基本介绍:
函数作为一个变量传入到另一个函数种, 那么该作为参数的函数的类型是:function1,即:()参数类型=>返回类型

匿名函数
基本介绍:没有名字的函数就是匿名函数, 可以通过函数表达式来设置匿名函数
例如:
val triple = (x: Double) = 3 * x
println(triple(3))
说明:
1), (x: Double) = 3 * x 这一整代码块就是匿名函数
2), (x: Double)是形参列表, => 是规定语法表示后面是函数体, 3 * x就是函数体, 如果有多行, 使用{}
3), triple是指向匿名函数的变量

匿名函数不需要def关键字
不需要写返回值类型, 使用类型推导





Scala数据结构 - 集合上

集合

数据结构的应用
Scala集合基本介绍:
1,Scala同时支持不可变集合和可变集合, 不可变集合可以安全的并发访问
2,两个主要的包:
    不可变集合:scala.collection.immutable
    可变集合:scala.collection.mutable
3,Scala默认采用不可变集合, 对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本
4,Scala的集合有三大类, 序列Seq, 集合Set, 映射Map, 所有的集合都扩展自Iterable特质, 
  在Scala中集合有可变和不可变两种类型

不可变集合: Scala不可变集合,就是这个集合本身不能动态变化,(类似Java的数组, 是不可以动态增长的)
可变集合:   可变集合就是这个集合本身可以动态变化的, 比如: ArrayList是可以动态增长的

学数据结构的顺序: 数据结构的创建, 查询, 修改, 删除

集合结构图 Image Image

数组

定长数组(声明泛型)
定义方式:
1,数组等同于Java中的数组, 中括号的类型就是数组的类型
    val arr1 = new Array[Int](10)
    arr1(1) = 7
2,使用apply方法创建数组
    // 这里使用的object Array的apply方法
    // 直接初始化数组, 这时候编译器根据你给出的数据类型推断除泛型为Any
    val arr = Array(1, 2, "Hi")
    // 遍历方式1
    for (i <- arr) {
         println(i)
    }
    // 遍历方式2
    for (i <- 0 to arr.length - 1) {
        println(arr(i))
    }
    // 遍历方式3
    for (i <- 0 until arr.length) {
        println(arr(i))
    }

变长数组(声明泛型)
定义方式:
    val arr = ArrayBuffer[Int]()
    // 追加append支持可变参数
    arr.append(7, 8)
    // 通过下标访问
    arr(0)
    // 修改
    arr(0) = 9
  分析小结:
    1),ArrayBuffer是变长数组, 类似Java的ArrayList
    2),val arr = ArrayBuffer[Int]()也是使用的apply方法构建对象
    3),def append(elems: A*) {appendAll(elems)}接收的是可变参数
    4),每append一次, arr在底层会重新分配空间, 进行扩容, arr的内存地址会发生变化,也就成为新的ArrayBuffer

定长数组与变长数组的转换
arr.toBuffer    // 定长数组转可变数组, 返回的结果才是一个可变数组, arr本身没有变化
arr.toArray     // 可变数组转定长数组, 返回的结果才是一个定长数组, arr本身没有变化

多维数组
定义和使用:
    val arr = Array.ofDim[Any](3, 4)
    二维数组中有三个一维数组, 每个一维数组有四个元素
    
    赋值
    arr(1)(1) = 11.11
        
Scala数组与Java的List互转
Scala数组转Java的List

元组


Tuple
基本介绍:
    元组也是可以理解为一个容器, 可以存放各种相同或不同类型的数据
    将多个无关的数据封装为一个整体, 成为元组
    **注意**: 元组最大只能由22个元素
    最大的优点就是灵活, 对数据类型没有过多的要求
    val tuplea = (1, 2, 3, "Hi", true)  // 对应类型Tuple5
    为了高效的操作元组, 编译器根据元素的个数不同对应不同的元组类型, 分别对应Tuple1~Tuple22
    
    元组数据的访问
    访问元组中的数据, 可以采用顺序号(_ 顺序号), 也可以通过索引号(productElement)访问
        
    元组数据的遍历
    Tuple是一个整体,遍历需要调其迭代器
    
    
     

Scala中的List

基本介绍:
    Scala中的List和Java List不一样, 在Java中List是一个接口, 真正存放数据是ArrayList,
    而Scala的list可以直接存放数据,就是一个object, 默认情况下Scala的list是不可变的, List属于序列Seq
    val list = scala.collection.immutable.List
    object List extends SeqFactory[List]
    // 创建时直接分配元素
    val list = List(1,2,3)
    val list1 = Nil //空列表

小结:
    1),List默认为不可变的集合
    2),List在Scala包对象声明的, 因此不需要引入其他包也可以使用
    3),val List = scala.collection.immutable.List
    4),List中可以存放任何数据类型, List[Any]
    5),Nil对象声明空列表

    List列表的元素追加
    向列表增加元素, 会返回新的列表/集合对象. 注意: Scala中List元素的追加形式非常独特和Java不一样
    var list = List(1,2,3,"abc")
    :+ 运算符表示在列表的最后增加数据, 巧记: :+ 冒号的左侧为列表加号的右侧为追加的数据
    +: 运算符表示在列表的前面追加数据, 有冒号:的一侧是列表,+的一侧是待追加的元素
    val list2 = list :+ 4
    :: 双冒号操作符也表示追加元素,运算规则是:从右向左依次执行
    val list = 1 :: 2 :: 3 :: list2 :: Nil
    ::: 三冒号操作符是将集合中的每一个元素加入到空集合中去, 三冒号操作符的两边都要为集合
    val list = 1 :: 2 :: 3 :: list2 ::: Nil
    
    
    List列表的元素获取,list是序列, 可以按照序列的方式获取
    list(x)
    

Scala中的ListBuffer

ListBuffer是可变的List集合

Scala的队列Queue

队列是一个有序列表, 在底层可以用数组或链表来实现
遵循先入先出原则
val q0 = new mutable.Queue[Int]
q0 += 10

q0 ++= List(20, 30, 40)

q0 += List(50, 60)  // 需要定义的泛型为Any才行

按照队列的顺序删除元素(队列先进先出)


Scala中的Map

Java回顾:
HashMap是一个散列表(数组+链表), 他存储的内容是键值对key-value映射, Java中的HashMap是无序的,key是无序的

Scala中的Map和Java类似, 也是一个散列表, 它的存储内容也是键值对, key-value映射, Scala中的不可变的Map是有序的, 可变的Map是无序的
scala.collection.mutable.Map 无序可变Map
scala.collection.immutable.Map 有序不可变的Map
key-value类型支持Any, 在Map的底层每对key-value是Tuple2

Map取值
1),通过key取值, 在取值前使用contains方法检查是否存在key
2),map.get(key).get 如果key存在, map.get(key)就会返回Some(值), 然后get取出值, 不存在就返回None
3),getOrElse(key, 默认值)

Map修改,添加和删除
map(key) = value 存在key就更新, 没有就添加
map += (k1 -> v1) 添加单个元素, 存在k1更新, 不存在添加
map -= (k1, k2) 删除多个元素k1, k2

Map的遍历
查看MapDemo.scala


Scala中的Set

Set简称集, 集是不重复元素的结合, 集不保留顺序, 默认是以哈希集实现
Scala中set默认是不可变的集合, 想要使用可变集合, 需要引入scala.collection.mutable.Set包

创建
val set0 = Set(k0, k1, k2, ... ) 不可变
val set = mutable.Set(k0, k1, k2, ... ) 可变

可变集合添加, 如果添加的对象存在,则不会重复添加,也不会报错
set.add(kn)
set += kn
set .+= (kn)

可变集合删除, 如果删除的对象不存在, 则不生效, 也不报错

set -= k1 操作符的形式删除
set.remove("k1") 方法的形式删除

set集合的遍历操作
val set = mutable.Set(1, 2, 3, 4, "5", "abc")
for (x <- set) {
    println(x)
}

Scala数据结构 - 集合下

集合元素的map操作

集合映射操作问题:
在Scala中可用通过map映射操作来解决集合映射问题, 将集合中的每一个元素通过指定功能(函数)映射(转换)成新的结果集合, 
就是所谓的将函数作为参数传递给另一个函数, 这就是函数式编程的特点  

以HashSet为例说明:
def map[B](f:(A) => B): HashSet[B] // map函数的签名
1),这是map映射函数集合类型
2),[B]是泛型
3),map是一个高阶函数(高阶函数:可用接受函数参数的函数)
4),HashSet[B]就是返回的新的集合

flatmap 映射: flat即压扁, 扁平, 扁平化映射
flatmap: 将集合中的每个元素的子元素映射到某个函数并返回新的集合

filter 过滤: 将复合要求筛选的数据放到新的集合

reduceLeft : 向左化简, 将二元函数引用于函数中
reduceLeft接受的函数需要的形式为: f((a, b) => a): a
reduceLeft的运行规则是, 从左边开始执行将得到的结果返回给第一个参数然后继续和下一个元素运行, 依次递归直到运行完成
reduceRight同理
应用场景: 在大集合中比较最大数和最小数效率最高
reduce底层调用的是reduceLeft

fold 折叠: fold函数将上一步返回的值作为函数的第一个参数继续传递参数运算, 直到list中所有的元素被遍历
foldLeft和foldRight得缩写方法分别为: 常量 /: 集合 和 集合 :\ 常量

scan 扫描
扫描即对某个集合的所有元素左fold操作, 但是会把产生的所有中间结果放置于一个集合中保存

zip 拉链
在开发中,当我们需要将两个集合进行对偶元组合并, 可用使用拉链
注意事项:
1),拉链的本质就是两个集合的合并操作, 合并后每个元素是一个对偶元组
2),如果两个集合元素个数不对应, 会造成数据丢失
3),集合不限于List, 也可以是其他集合, 比如Array
4),如果想要取出合并后的各个对偶元组的数据, 可用遍历

iterator 迭代器
通过iterator方法从集合中获取一个迭代器, 通过while循环和for循环对集合进行遍历
iterator的构建实际上是AbstractIterator的一个匿名子类, 该子类提供了hasNext, next方法

Stream 流
Stream是一个集合, 这个集合可用用于存放无穷多个元素, 但是这个无穷个元素并不会一次性产生出来,
而是需要用到很大的区间, 会动态生成, 末尾元素遵循lazy规则

def numsForm(n: Int): Stream[BigInt] = n#::numsForm(n + 1)
val stream1 = numForm(1)
注意:
Stream集合存放的数据类型是BigInt, 
numsForm是自定义的一个函数, 函数名是程序员指定的
创建的集合的第一个元素是n, 后续元素生成的规则是n+1
后续元素生成的规则是可以程序员指定的, 比如numsForm(n * 4)
使用tail就会动态的向stream集合按规则生成新的元素

view 视图
Stream的懒加载特性也可以对其他集合应用view方法来得到类似的效果
view方法会产生出一个总是被懒加载执行的集合
view不会缓存数据, 每次都要重新计算
应用场景:
开发中如果想对集合进行map, filter, reduce, fold...操作, 但是不喜欢立即执行, 再需要结果时才执行则可以使用view来进行优化

现场安全的集合
所有线程安全的集合都是以Synchronized开头的集合
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizedSet
SynchronizedStack

并行集合
1), Scala为了充分使用多核CPU, 提供了并行集合, 用于多核环境的并行计算
2), 主要用到的算法有:
    Divide and conquer: 分治算法, Scala通过splitters(分解器), combiners(组合器)等抽象层来实现,
    主要原理是将计算工作分解很多任务, 分发给一些处理器取完成, 并将它们处理结果合并返回
    Workstealin算法, 主要用于任务调度负载均衡, 通俗点完成自己的所有任务之后, 发现其他人还没完成的任务就会帮忙一起完成
3),


Scala操作符的扩展

操作符扩展
1,如果想在变量名, 类名等定义中使用语法关键字(保留字), 可以配合反引号``
 val `val` = 1
2,中置操作符: A操作符B 等同于 A.操作符(B)
 a + b 等价于 a.+ (b)
3,后置操作符, A 操作符 等同于 A.操作符, 如果定义的时候不带()则调用时不能加括号

4, 前置操作符, + - ! ~ 等操作符A等同于A.unary_操作符


Scala模式匹配 (重点)

Scala中模式匹配蕾仕于Java中的switch语法, 但是更加强大
模式匹配语法中, 采用match关键字声明, 每个分支采用case关键字进行声明, 当需要匹配时, 会从第一个case分支开始, 如果匹配成功,
 那么执行对应的逻辑代码, 如果匹配不成功, 继续执行下一个分支进行判断.
如果所有case都不匹配, 那么会执行case_分支, 类型Java中default语句, 不需要break语句

match细节和注意事项:
1,如果所有case都不匹配, 那么会执行case _分支, 类似Java中的default语句
2,如果所有case都不匹配, 又没有写case _ 分支, 那么会抛出异常MatchError
3,每个case中, 不用break语句, 自动中断case
4,可以在match中使用其他类型, 而不仅仅时字符
5,=>等价于Java switch的:
6,=>后面的代码块到下一个case之间是作为一个整体执行,可以使用{}括起来,也可以不用

// Java (不加break会造成穿透现象)
int i = 1
switch(i) {
    case 0:
        xxx
        break;
    case 1:
        xxx
        break;
    default:
        break;
}

// scala

如果想要表达匹配某个范围的数据, 就需要在模式匹配中增加条件守卫即if语句

模式中的变量:
如果在case关键字后跟变量名, 那么match前表达式的值会赋给那个变量
match是一个表达式, 因此可以有返回值, 返回值就是匹配到的代码块的最后一句代码执行的值

类型匹配
可以匹配对象的任意类型, 这样做避免了使用inInstanceOf和asInstanceOf方法
类型匹配注意事项:
1), Map[String, Int] 和Map[Int, String]是两种不同的类型, 其他类推
2), 在进行类型匹配时, 编译器会预先检测是否有可能的匹配, 如果没有则报错
3), 匹配后返回的值就等于匹配的变量, 然后判断类型
    val result = obj match {case i:Int => i} 表示将i=obj
4), 如果case _ 出现在match中间, 则表示隐藏变量名, 即不使用, 而不是表示默认匹配

匹配数组
基本介绍:
1,Array(0)匹配只有一个元素且为0的数组
2,Array(x, y)匹配数组有两个元素, 并将两个元素赋值为x和y, 当然可以依次类推Array(x, y, z)匹配数组有三个元素的等等,
3,Array(0, _*)匹配数组以0开始

列表匹配
基本介绍:

匹配元组

对象匹配
什么才算是匹配呢?
1),case中对象的unapply方法(对象抽取器)返回some集合则为匹配成功
2),返回none集合则为匹配失败
注意事项:
1), 构建对象时apply会被调用, 例如: val n1 = Square(6)
2), 当将Square(n)写在case后时[case Square(n) => xxx], 会默认调用unapply方法
3), num会被传递给def unapply(arg: Double)的arg形参
4), 如果返回的时Some集合, 则unhappy提取器返回的结果会返回给n这个形参
5), case中对象的unhappy方法提取器返回Some集合则为匹配成功
6), 返回none集合则为匹配失败

案例2注意事项:
1), 当case后面的对象提取器方法的参数为多个, 则会默认调用def unapplySeq()
2), 如果unhappySeq返回Some, 获取其中的值, 判断得到的sequence中的元素的个数, 如果匹配上, 则执行匹配后面的逻辑
3), 其他规则不变

变量声明中的模式
基本介绍:
match中每一个case都可以单独提取出来,

for表达式的模式
for循环中也可以进行模式匹配

样例(模板)类
基本介绍
1), 样例类仍然时类
2), 样例类用case关键字进行声明
3), 样例类是为模式匹配而优化的类
4), 构造器中每一个参数都成为val类型属性, 除非被显示声明为var(不建议)
5), 在样例类对应的伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象
6), 提供unapply方法让模式匹配可以工作
7), 将自动生成toString,equals,hashcode和copy方法,(类似模板类直接生成提供出啦)
8), 除上述, 样例类和其他类型完全一样, 可以添加字段, 方法以及扩展它们
样例类的copy方法和带名参数
copy创建一个与现有对象值相同的新对象, 并可以通过带名参数来修改某些属性

匹配嵌套结构
原理类似于正则表达式

密封类
如果想让case类的所有子类都必须在申请该类的相同的源文件中定义, 可以将样例类的通用超类声明为sealed, 这个超类称之为密封类
密封就是不能在其他文件中定义子类


Scala核心编程

使用递归的方式去思考, 去编程

严格意义上讲, 编程的范式分为:
命令式编程(Imperative Programming):
函数式编程(Functional Programming):
逻辑式编程(Logic Programming)
面向对象只是上述几种范式的一个交叉产物, 更多的还是继承了名命式编程的基因

在Scala循环中不建议使用while和do...while, 而建议使用递归

递归是告诉计算机做什么, 而不是告诉计算机怎么做(迭代)

使用递归的注意事项:
斐波拉契数列的案例告诉我们, 递归在进行重复计算时递归调用的次数是会成指数倍增长的, 这时候需要考虑优化和迭代
优化地址: https://blog.csdn.net/dadai_/article/details/50209511


Scala项目

客户信息管理系统

项目开发流程:
1),可行性分析(又没有市场? 市场部+销售部, 可行性报告)
2),需求分析(需求分析师,懂技术+懂业务, 挖掘客户真正的需求, 需求分析报告,白皮书)
3),设计阶段(项目经理+架构师, 使用什么技术+架构+选人, 设计图:类图+时序图+部署图+用例图)+数据库+界面(原型开发)
4),实现阶段(软件工程师+看懂文档+实现各个模块+设计功能模块+模块代码)
5),测试阶段(测试工程师+白盒+黑盒+灰盒, 找bug)
6),实施阶段(实时工程师+将项目部署到系统,并匹配好参数,正确运行)
7),维护阶段(不一定有专人)



AKKA

AKKA介绍
AKKA是Java虚拟机JVM平台上构建并发,分布式和容错应用的工具包和运行时,是由Scala编写的,同时支持Scala和Java的开发接口
AKKA主要应用于:编写稳定的并发程序, 程序员不再过多的考虑线程,锁和资源竞争细节

AKKA中Actor模型
Actor模型用于解决什么问题呢?
1),处理并发问题关键是要保证共享数据的一致性和正确性,因为程序是多线程时,多个线程对同一数据进行修改,若不加同步条件,势必会造成数据污染, 但是当我们对关键代码加入同步条件synchronized后, 实际上大并发就会阻塞这段代码, 对程序效率很大影响
2),若是用单个线程处理, 不会有数一致性的问题, 但是系统的性能又不能保证
3),Actor模型的出现解决了这个问题, 简化并发编程,提升程序性能,

Actor模型是一种处理并发问题的解决方案

Actor模型及其说明
4),Actor与Actor之间只能用消息进行通信, 当一个Actor给另一个Actor发消息, 消息是有顺序的,只需要将消息投递到相应的邮箱即可.
5),怎么处理消息是由接收消息的Actor决定的, 发送消息Actor可以等待回复,也可以异步处理
6),ActorSystem的职责是负责创建并管理其创建的Actor, ActorSystem是单例的,一个JVM进程中有一个即可, 而Actor是可以有多个的
7),Actor模型是对并发模型进行了更高的抽象
8),Actor模型是异步,非阻塞,高性能的事件驱动编程模型
9),Actor模型是轻量级事件处理(1GB内存可容纳百万级别个Actor), 隐藏处理大并发性能高

Actor模型工作机制说明
1),ActorSystem创建Actor
2),ActorRef: 可以理解成是Actor的代理或者引用, 消息是通过ActorRef来发送,而不是通过Actor发送消息, 通过那个ActorRef发消息就是把消息发给那个Actor
3),消息发送到Dispatcher Message(消息分发器), 它得到消息后, 会将消息进行分发到对应的MailBox,(注:Dispatcher Message可以理解成一个线程池, MailBox可以理解成消息队列,可以缓冲多个消息, 遵守FIFO)
4),Actor可以通过receive方法来获取消息, 然后进行处理

Actor间传递消息机制
1),每一个消息就是一个Message对象, Message继承了Runnable, 因为Message就是线程类
2),从Actor模型工作机制上看很麻烦, 但是程序员编程时只需要编写Actor就可以, 其他的交给Actor模型完成即可
3),A Actor要给B Actor发送消息, 那么A Actor要先拿到(或持有)B Actor的代理对象ActorRef才能发送消息

https://www.bilibili.com/video/av39126512?p=237

快捷键 shortcut key

格式化文档:
1 - ctrl + alt + l  格式化代码
2 - ctrl + b 进入光标所在类或者查看包下的所有子类

REPL常用命令

:q  -  退出Scala REPL

javap xxx.class文件, 查看反编译后的代码

学习的套路

学一个技术或知识点的流程:
1,需求(遇到问题): 
2,解决思路(用传统的方法可以解决但是不够完美, 真解决不了的时候,用新技术)
3,学习新技术的基本语法和基本运行原理
4,编写快速入门案例(哪怕很简单也好)
5,反复琢磨该技术的使用细节,使用陷阱, 如何使用规范和高效(这里就体现了编程水平)



Java Comparison Scala

Coding Scheme

Java: psvm
Scala:main

Scala

Scala Introduction

Scala语言最重要的四个特征:兼容性、简洁、高层抽象、高级的静态类型化
Scala是兼容的
Scala是简洁的
Scala是高级的
Scala是静态类型的:Scala是一种具有非常高级的静态类型系统的语言

Scala入门
1、Scala解释器:它是编写Scala表达式和程序的交互式"Shell",只要在解释器里输入表达式,它就能计算并打印结果值,Scala的交互式shell叫做scala
2、变量定义:var和val。val类似Java里的final变量,var类似Java里的非final变量
3、函数定义:def 函数名(参数列表): 返回值类型 = {函数体} , 参数列表=> 变量: 类型
4、编写Scala脚本:Scala脚本的命令行参数保存在名为args的Scala数组中,例如:将如下代码 println("Hello," + args(0) + "!") 保存在hello.scala文件中,然后运行脚本文件:scala hello.scala yang
               这条命令里,命令行参数"yang"被传递给脚本,并通过args(0)获得。打印如下:hello, yang!
5、用while做循环,用if做判断
   while (i < args.length) {} 这种编码风格被称为指令式(imperative)编程。这种风格常见于Java,C++,C这些语言,或者逐条执行指令,或者循环枚举,并经常改变不同函数之间的共享状态。
6、用foreach和for做枚举
   深入了解Scala后,会发现更倾向于一种更为函数式的编程风格
   函数式语言的主要特征之一就是:函数是头等结构,这也正是Scala的主要特征。例如:
   args.foreach(arg => println(arg)) 或者想要表达得更明确:args.foreach((arg: String) => println(arg))
   如果函数字面量只有一行语句并只带一个参数,可以写成如下:
   args.foreach(println)
   Scala只有一个指令式for得函数型与之近似:
   for (arg <- args)
    println(arg)

Scala入门再探
7、使用类型参数化数组(Array):参数化的意思是指在创建实例的同时完成对它的"设置"。使用值参数化实例可以通过把值传递给构造器的圆括号来实现。
   Scala的数组是通过索引值加圆括号进行访问的,而不是像Java那样把索引值放在方括号里面。
   
8、使用列表(List)
   方法没有副作用是函数式风格编程的重要理念。计算并返回值是方法唯一的目的。好处:1)、方法之间降低耦合性,更加可靠和易于重用 。
   2)、方法的参数和返回值都经过类型检查器的检查,因此可以比较容易地根据类型错误推断其中隐含的逻辑错误。而这种函数式编程思想在面向对象编程中的应用也就意味着对象的不可变性。
   数组Array是可变的(元素值是可变的)
   列表List是不可变的同类型对象序列(元素是不可变的)
   列表List定义":::"方法实现叠加功能
   列表List定义"::"操作符,发音"cons",它可以把新元素组合到现有列表的最前端,然后返回作为执行结果的新列表
   "::"是右操作数的方法
   Nil是空列表的简写,放在最后,因为::是定义在List上的方法
   List的一些方法:
   List() / Nil
   :::
   count(condition)
   drop(index)
   dropRight(index)
   exists(condition)
   filter(condition)
   forall(condition)
   foreach(condition)
   head
   init
   isEmpty
   last
   length
   map(condition)
   mkString(condition)
   remove(condition)
   reverse
   sort(condition)
   tail
   
9、使用元组Tuple
   元组也是很有用的容器对象,与列表一样,元组也是不可变的,与列表不同的是,元组可以包含不同类型的元素。
   元组适用场景很多,如:方法里返回多个对象
   元组实例化之后,可以用点号、下划线和基于1的索引访问其中的元素,不能像列表那样访问元组pair(0),因为列表的apply方法始终返回同样的类型,但元组里的类型不尽相同。
   ._N的索引是基于1的,不是基于0的

10、使用集Set和映射Map
    Scala致力于充分利用函数式和指令式风格两方面的好处,因此它的集合collection库区分为可变类型和不可变类型。
    Trait特质这个概念接近于Java的接口(Interface)
    可变和不可变的集都提供了+方法,但结果不一样,可变集把元素加入自身,不可变集则创建并返回包含了添加元素的新集。
    Set是无序的
    Scala的任何对象都能调用->方法,并返回包含键值对的二元组,这种机制被称为隐式转换

11、学习识别函数式风格
    识别函数是否有副作用的地方就在于其结果类型是否为Unit
    真正函数式风格是完全没有副作用或var的mkString方法,能在任何可枚举的集合类型(数组、列表、集和映射)上调用
    
12、从文件里读取文本行
    不使用脚本时可以先注释点脚本内容

Scala 类和对象

1、类、字段和方法
   类是对象的蓝图,一旦你定义了类,就可以使用关键字new根据类的蓝图创建对象
   字段的另一种说法是实例变量
   保持对象健壮性的重要方法之一就是保证对象的状态--即实例变量的值--在对象整个生命周期中持续有效。
        第一步就是通过把字段变为私有的以阻止外界直接对它的访问,私有字段只能被同一个类里的方法访问。
   在Scala中把成员公开的方法是不显示地指定任何访问修饰符。

2、分号推断
   Scala程序里, 语句末尾的分号通常是可选的,如果一行包含多条语句,分号是必须的。
   分号推断的规则:
   分割语句的具体规则既出人意料的简单又非常高效,那就是除非以下情况的一种成立,否则行尾被认为是一个分号:
   1)、疑问行由一个不能合法作为语句结尾的字结束,如句点或中缀操作符。
   2)、下一行开始于不能作为语句开始的词 
   3)、行结束于括号(...)或方括号[...]内部,因为这些符号不可能容纳多个语句。
   
3、singleton对象
   Scala比Java更为面向对象的特点之一是Scala不能定义静态成员, 而是代之以定义单例对象(Singleton Object)
   当单例对象与某个类共享同一个名称时,它就被称为这个类的伴生对象(companion object),类和它的伴生对象必须定义在一个源文件里。
   类被称为是这个单例对象的伴生类(companion class)。类和它的伴生对象可以互相访问其私有成员。
   类和单例对象间的差别是,单例对象不带参数,而类可以。因为单例对象不是new关键字实例化的,所以没机会传递给它实例参数。单例对象在第一次被访问的时候才被实例化。
   不与伴生类共享名称的单例对象被称为独立对象(standalone object)。
   虚拟类的名字是对象名加上一个美元符号,因此单例对象CheckClassAccumulator的虚拟类是CheckClassAccumulator$

4、Scala程序
   scalac 编译scala文件,fsc(scala快速编译器,fast scala compiler)是Scala编译器后台服务daemon, 停掉fsc后台进程: fsc -shutdown 
   无论是scalac还是fsc命令,最终都将完成Java类文件的创建,然后可以使用scala命令去运行它

5、App特质
   Scala提供了特征scala.App,可以减少一些输入工作。
   在单例对象后面加上extends App , 可以替代main方法去编译执行程序,缺点是:
   1)、如果想访问命令行参数的话就不能使用它,因为args数组不可访问
   2)、因为某些JVM线程模型里的局限,如:某些多线程的程序需要自行编写main方法
   3)、某些JVM的实现没有优化被App特质执行的对象的初始化代码。因此只有当程序相对简单并且是单线程的情况下才可以继承App特质

基本类型与操作

1、基本类型
   查看本文档Line:189 >> Scala数据类型列表
   Scala的基本类型与Java的对应类型范围完全一样。这样可以让Scala编译器直接把Scala的值类型实例对应转译为Java原始类型
    
2、字面量
   基本数据类型都可以写成字面量(literal)。字面量就是直接写在代码里的常量值。

3、操作符
   查看本文档前文内容



函数式对象

相关知识:类参数和构造函数、方法和操作符、私有成员、子类方法重载、先决条件检查、同类方法重载和自指向
require方法带一个布尔型参数。如果传入的值为正,require将正常返回。反之,require将抛出IllegalArgumenntExeception阻止对象被构造。

自指向:关键字this指向当前执行方法被调用的对象实例,或者如果使用在构造器里的话,就是正在被构建的对象实例。
辅助构造器:一个类里需要多个构造器,Scala里主构造器之外的构造器被称为辅助构造器
Scala的辅助构造器定义开始于def this(...)。函数主题几乎完全是主构造器的调用,并传递了参数n作为分子和1作为分母
Scala里的每个辅助构造器的第一个动作都是调用同类的别的构造器。换句话说:每个Scala类的每个辅助构造器都是以this(...)开头的,
被调用的构造器既可以是主构造器,也可以是源文件中早于调用构造器定义的其他辅助构造器。规则的根本结果就是每个Scala的构造器调用终将结束于对主构造器的调用。
因此主构造器是类的唯一入口点。

Scala标识符:以字母或下划线开始,之后可以跟字母、数字或下划线。'$'字符也被当作是字母,但被保留作为Scala编译器产生的标识符之用。
因此我们的程序里面不应该包含'$'字符。Scala遵循Java的驼峰式标识符。
字段,方法参数,本地变量,还有函数的驼峰式名称,应该以小写字母开始。
类和特质的驼峰式名称应该以大写字母开始。
字面量标识符是用反引号`....`包括的任意字符串。

隐式转换


About

bigdata:包含scala语言,Hadoop、Hive、HBase、Spark、Flink...相关API在Java与Scala语言下的对比,在本地链接虚拟机服务进行测试

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages