1. Scala 简介
- Scala 是一门多范式(multi-paradigm)的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性
- Scala 运行在 Java 虚拟机上,并兼容现有的 Java 程序 (Scala 是类 Jvm 言)
- Scala 源代码被编译成 Java 字节码,所以它可以运行于 JVM 之上,并可以调用现有的 Java 类库
- Spark 和 flink 等大数据框架都是使用 Scala 开发的
2. Scala 的版本
- 2.10.x:(兼容 spark2.x↓)
- 2.11.x:(兼容 spark2.x↑)
- 因为 spark2.x 后,spark 的是使用 scala2.11.8 编译的, 所以在此我们一般企业开发都会选择一致的版本2.11.8
3. Scala 的安装
3.1 windows 的安装
一路下一步, 安装完成之后, 自动会把路径加入环境变量直接使用即可
3.2 linux 的安装
解压, 配置环境变量即可使用
4. Scala 的 REPL
REPL:Read Eval Print Loop交互式环境(交互式解释器)
jdk9.x 也支持 REPL(jshell)
5. Scala 的第一个程序
5.1 编写 Scala 源代码文件
Ops1.scala
object Test1{def main(args:Array[String]):Unit={println("hello bigdata")
}
}
5.2 编译 Scala 源代码
scalac Ops1.scala
5.3 执行 class 文件
scala Test1
6. Scala 与 Java 的区别
-
都是基于 JVM 虚拟机运行的
- Scala 编译之后的文件也是.class,都要转换为字节码,然后运行在 JVM 虚拟机之上
-
Scala 和 Java 相互调用
- 在 Scala 中可以直接调用 Java 的代码,同时在 Java 中也可以直接调用 Scala 的代码
-
Java 8 VS Scala
- Java 8(lambda)没有出来之前,Java 只是面向对象的一门语言,但是 Java 8 出来以后,Java 就是一个面向对象和面向函数的混合语言了。
- scala 与 java 都支持 面向对象 和函 数式编程(jdk1.8 才开始支持)
- Scala 设计的初衷是 面向函数 FP
- Java 的设计初衷是 面向对象 OOP
7. Scala 解释器和 IDEA
-
Scala 的解释器
- REPL
-
IDEA
- eclipse
- IntelliJ IDEA(推荐使用)
8. Repl 的解释
res0: Int = 12
在我们输入解释器的每个语句后,它会输出一行信息,类似 res0: Int = 12。输出的第一部分是 REPL 给表达式起的变量名。
9. Scala 中的变量的定义
var 变量名称: 变量类型 = 变量值
==在 scala 中除了定义变量还可以定义不可变的量 ” 常量 ”==
val 变量名称: 变量类型 = 变量值
在 Scala 编程中推荐使用 val, 要 val 不能解决问题才使用 var
10. ==Scala 中的类型推断 ==
var a1:Int = 10
var a2 = “hello”(在 Scala 中就会根据值的类型推断出变量的类型相当与 var a2:String = “hello”)
var a3 = 20
a3=”hello” ==错误的, 因为 a3 虽然没有显示的声明类型, 但是根据值已经推断出 a3 是 Int 类型==
11. Scala 中的编程思想
能少敲一下键盘绝不多敲一下
12. Scala 中的数据类型
-
基本数据类型(AnyVal)
- Boolean
- Byte
- Short
- Char
- Int
- Float
- Long
- Double
-
引用数据类型(AnyRef)
- String
……
- String
AnyVal 和 AnyRef 都是 Any 的子类
在 Scala 中没有 void, 使用 Unit 来代替
13. Scala 中的运算符重载
在 Scala 中是 没有运算符 的, 所有的运算符都是 方法
==ps: ==
- 在 Scala 中没有 ++ 和 –
- 在 Scala 中没有三元运算符
14. Scala 中的的流程控制
- 在 Scala 中的代码快都是有返回值的
- 在 Scala 中代码快的返回值是代码快的最后一条语句执行的结果
- 在 Scala 中不能使用 return 关键字
- Scala 中没有 switch 语句 (有更加强大的 模式匹配)
14.1 Scala 中的 if 语句
var ret = if(a>10) ">10" else 0
14.2 Scala 中的 for 循环
// 单循环
for(i <- 1 .to(10) ) {println( i)
}
// 普通的双循环
for(i <- 1 .to(10) ) {for(j <- 1 .to(10) ) {println("i=="+i+",j=="+j)
}
}
// 改良(循环嵌套使用分号分隔)
for(i <- 1 .to(10);j <- 1 .to(10)) println("i=="+i+",j=="+j)
// 带条件的双循环
for(i <- 1 .to(10);j <- 1 .to(10)) {if(i!=j) println("i=="+i+",j=="+j)
}
//if 守卫
for(i <- 1 .to(3);j <- 1 .to(3) if(i!=j)) println("i=="+i+",j=="+j)
//yield 把循环的结果放到一个 Vector 中, yield 后面不能出现其他语句
var ret = for (i <- 1.to(3); j <- 1.to(3) if (i != j)) yield j
//for 循环的跳出循环(在 scala 中没有 break 和 continue)
import scala.util.control.Breaks._
breakable {for (i <- 1.to(10)) {println(i)
if (i == 6) {break();
}
}
}
14.3 while do…while
与 Java 中一致
15. 代码快的返回值
- 代码快的值是代码快最后一个语句的计算结果
- 如果代码快的最后一个语句为赋值语句或打印语句则代码快的值为Unit, 表现形式为 ()
16. Scala 中的输出
//scala 风格的输出
print("hello");
println("hello,bigdata")
// C 语言风格的输出
printf("Hello, %s! You are %d years old.\n", "Fred", 42)
17. Idea 开发 Scala(pom)
-
在 Idea 中安装开发 Scala 的插件
scala-intellij-bin-2017.2.7.zip
- POM 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.uplooking.bigdata</groupId>
<artifactId>scala</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<scala.version>2.11.8</scala.version>
</properties>
<dependencies>
<!-- 导入 scala 的依赖 -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<!-- 编译 scala 的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
18. Scala 中的函数和方法
18.1 函数和方法的区别
- 方法操作的是对象, 函数操作的不是对象
- 其实在实际开发过程中并不会对函数和方法做严格的区分
18.2 方法的定义
def 方法名称(参数名称: 参数类型,....): 方法的返回值类型 = {方法体}
// 定义方法
def say1(str: String): Unit = {println(str)
}
- 定义方法时形参必须要有类型
- 方法的返回值类型可以省略, 一般如果不是递归调用则不会显示的声明返回值类型, 返回值类型支持类型推断
18.3 过程
def 函数名称(参数名称: 参数类型,....) {函数体}
- 在 过程 中, 我们关注的是函数的副作用
18.4 单行函数
// 单行函数就是写在一行的函数
def say01(str: String) = "hello" + str;
18.5 默认参数
def say01(name: String, age: Int = 12) = "name=" + name + ";age=" + age;
- 默认值参数一般放在参数的最后几个
18.6 可变参数
def say02(age: Int*) = age
- 传多个参数返回 WrappedArray
19. Scala 中的 Lazy
- 当 val 被声明为 lazy 时,它的初始化将被推迟,直到我们首次对它取值。例如,
lazy val lines= scala.io.Source.fromFile(“D:/test/scala/wordcount.txt”).mkString
- 如果程序从不访问 lines,那么文件也不会被打开。但故意拼错文件名。在初始化语句被执行的时候并不会报错。不过,一旦你访问 words,就将会得到一个错误提示:文件未找到。
lazy val lines= scala.io.Source.fromFile(“D:/test/scala/wordcount.txt”).mkString
- 懒值对于开销较大的初始化语句而言十分有用
20. Scala 中的定长数组(Array)
20.1 数组的定义
// 定义数组有两种方式
// 直接初始化的定义
val ages = Array(12,14,15,10)
// 创建一个长度为 10 的数组
var ages = new Array(10);
20.2 数组的访问
println(ages(1))
ages(3) = 12
20.3 数组的遍历
for(age <- ages) println(age)
20.4 数组的长度
ages.length
21. Scala 中的变长数组(ArrayBuffer)
21.1 ArrayBuffer 的创建
var agess = ArrayBuffer(12,13,14,15)
21.2 ArrayBuffer 的操作
mkString(","); 把 Scala 中 ArrayBuffer 的数组读取出来按指定的分隔符以字符串的形式打印
+=(element*);Scala 中的追加一个元素
++=(arrayList)
22. 定长数组与边长数组的转换
val ages =Array(10,12,15)
val buf = ages.toBuffer// 定长数组转为边长数组
val ary = buf.toArray// 变长数组转为定长数组
23. Scala 中的匿名函数
// 在匿名函数中不能显示的声明匿名函数返回值的类型
val func1 = (name: String, age: Int) => name
// 调用匿名函数
func1("admin",12)
- 如果单独定义匿名函数, 则函数中的 形参的类型 和小括号 都不可以省略
24. Scala 中的方法调用
- 定义方法时如果没有参数但是 有括号, 则调用时可以写括号也可以不写括号
- 定义方法时如果没有参数而且 无括号, 则调用时一定不能有括号
25. Scala 中的集合
25.1 集合的继承结构
|-Traversable
|-Iterable
|-Set(是一组没有重复元素的集合)
|-HashSet
|-TreeSet
|-Map(Map,是一组 k - v 对)
|-HashMap
|-Seq(是一组有序的元素)
|-Array
|-String
|-Vector
==|-List(列表)==
|-Queue(FIFO)
|-Stack(FIEO)
25.2 List 的操作
// 创建 List
val ages1 = List(12, 14, 16);
val ages2 = List(10, 11, 19);
// 把两个集合合并在一起, 从冒号的一边开始计算
val ages3 = ages1 ::: (ages2);
25.3 Set 的操作
// 创建 Set
val set1 = Set(12, 14, 12, 10)
for (elem <- set1) println(elem)
25.4 Map 的操作
// 定义 map
val map = Map("name" -> "admin", "age" -> 12)
// 遍历 map
val map = Map("name" -> "admin", "age" -> 12)
for (elem <- map) {println(elem._2)
}
25.5 Scala 中的元组 Tuple
元组: 一组数据的集合, 最大有 22 个
val t =Tuple2(12,14)
var t = (12,14,15)// 有几个元素就是 Tuple 几
println(t._1)// 获取元组中的数组 t._1 t._2 t._3....
26. Scala 中的闭包
- 函数的嵌套
- 外部函数的返回值是内部函数
- 内部函数使用了外部函数的参数
// 定义具有闭包特性的两个函数(方法)
def A(i: Int) = {def B(j: Int) = {i + j}
B _
}
// 调用闭包函数
val b = A(10)
val ret = b(20)
println(ret)
// 柯里化
val ret = A(10)(20)
println(ret)
27. Scala 中的柯里化
-
把传递多个参数的函数编程传递一个一个参数的函数
// 柯里化定义函数 def fun1(a: Int)(b: Int) = a + b // 柯里化调用函数 val ret = fun1(12)(10) println(ret)
-
柯里化的过程
//var func = (a:Int,b:Int,c:Int)=>a+b+c val func = (a: Int) => (b: Int) => (c: Int) => a + b + c println(func(10)(20)(30))
28. 高阶函数
函数 func1 的参数是函数 func2, 把 func1 就称为 高阶函数
// 定义高阶函数
val func1 = (func2: (Int, Int) => Int) => func2(10, 20)
// 定义匿名函数
val func2 = (a: Int, b: Int) => a + b
// 调用高阶函数
val ret = func1(func2)
println(ret)
// 使用 def 的方式来定义高阶函数
def func1(func2: (Int, Int) => Int) = {func2(10, 20)
}
// 定义匿名函数
val func2 = (a: Int, b: Int) => a + b
// 调用高阶函数
val ret = func1(func2)
println(ret)
- 直接定义匿名函数, 匿名参数的类型和括号不可以省略
- ==直接传递匿名函数给高阶函数类型可以省略(一般都会省略), 如果匿名函数只有一个参数括号也可以省略==
29. 高阶函数常用的调用方式
-
不省略匿名函数的类型(不经常使用)
val func1 = (func2: (Int, Int) => Int) => func2(10, 20) func1((a: Int, b: Int) => a + b)
-
省略匿名函数的类型(比较常用)
val func1 = (func2: (Int, Int) => Int) => func2(10, 20) func1((a, b) => a + b)
-
使用通配符(比较常用)
val func1 = (func2: (Int, Int) => Int) => func2(10, 20) func1(_ + _)
30. 集合的常用高阶函数
-
foreach
val list1 = List(12, 14, 15, 10) //foreach 内部循环遍历集合中的每个元素, 每遍历一个元素, 调用一次我们自己传递的匿名函数(计算法则) val ret = list1.foreach(i =>println(i))
-
map
//map 内部循环遍历集合中的每个元素, 每遍历一个元素, 调用一次我们自己传递的匿名函数(计算法则), 最后把每一次的计算法则的返回值放入一个新的 List 返回 val ret2 = list1.map(_ + 10) println(ret2)
-
count**
//count 内部循环遍历集合中的每个元素, 每遍历一个元素, 调用一次我们自己传递的匿名函数(计算法则), 最后如果每一次的计算法则的返回值为 true 则 count 会加 1, 最后返回 count var ret = list1.count(i => {if (i > 13) {true} else {false} }) println(ret)
- union 并集
- intersect 交集
- diff 差集
- groupBy 分组
- sorted 排序
- sortBy 根据元素进行排序
- zip 拉链操作
- **fold 折叠操作
==31. Scala 中的面向对象编程==
32. Scala 中的类
32.1 Scala 中类的定义
class Person {
var name: String = _;
val age: Int = 0;
}
- 定义在类中的属性全部为私有属性
- 使用 val 修饰的属性不能再赋值
- 类中的属性必须初始化
32.2 Scala 对象的创建
val p = new Person();
32.3 属性的访问
-
在 Class 定义的内部声明的属性都被系统置为private, 外部不能直接访问, 但是我们一般是一下这样来访问属性的
println(p.name); // 其实访问的不是属性, 只是在调用 getter 方法 def name=this.name p.name="admin" // 其实这个本质上就是调用 setter 方法, def name_=(name:String)={this.name=name}
32.4 Scala 中的构造器
-
主构造器
- 在定义类时类名后面跟 () 就是主构造器
- 主构造器的属性用 var 修饰, 则属性私有化 对外提供 getter 和 setter 方法
- 主构造器中的属性用 val 修饰, 则属性私有化, 对外提供 getter 方法
- 主构造器中的属性不用 var 和 val 修饰, 则属性私有化, 内部生成私有的 getter 和 setter 方法
- scala 中可以定义多个辅助构造器, 但是辅助构造器的第一行必须调用主构造器或者其他辅助构造器
33. Scala 中的嵌套类
-
在 Scala 中,你几乎可以在任何语法结构中内嵌任何语法结构
class A { class B {def say() = {println("halou B.....") } }
// 创建内部类的对象 object Ops1 {def main(args: Array[String]): Unit = { // 创建外部类对象 val a: A = new A() val b = new a.B() b.say()} }
34. Scala 中的 object(单例对象)
- 在 Scala 中 没有 static的概念的
-
Scala 中的 Object 中定义的成员都是 静态的
// 创建对象 object Obj2 {def aa(): Int = {90} }
// 调用对象的方法 object Ops2 {def main(args: Array[String]): Unit = {println(Obj2.aa()) } }
35. Scala 中的伴生对象
- 在同一个.scala 文件中出现一个类名和 Object 名称相同的类和对象, 那么把这个对象就称为类的伴生对象, 把类称为对象的伴生类
- 伴生对象中可以访问私有的伴生类中的成员
-
一般使用伴生对象来创建伴生类的对象
object Ops3 {def main(args: Array[String]): Unit = {val p = Person("admin",12) println(p.age) } } // 定义一个 Person 类 class Person { private var name: String = _; var age: Int = _; def say() = {println("my name is" + this.name + "my age is" + this.age) } } // 定义一个 Person 对象 object Person {def apply(name: String, age: Int): Person = {val person = new Person() person.name = name; person.age = age; person; } }
36. Scala 中的继承
36.1 继承父类
class Person() { var name: String = _; var age: Int = _; def say() = {println("my name is" + this.name + ",my age is" + this.age) } } class Student extends Person
object Ops4 {def main(args: Array[String]): Unit = {val student = new Student() student.name = "admin" println(student.name) student.say()} }
/**
*/
class Student extends Person {
override def say() = {println("hello,my name is" + this.name + ",my age is" + this.age)
}
// 对象也可以继承类, 访问时直接使用对象方法
object Student extends Person {
override def say() = {println("hello,my name is" + this.name + ",my age is" + this.age)
}
}
// 访问
Student.say();
# 36. 抽象类
- 抽象类在定义之前使用 abstract 修饰
- 抽象方法所在的类一定要声明为抽象类
- 抽象类中可以没有抽象方法
object Ops4 {
def main(args: Array[String]): Unit = {
val stu = new Student();
stu.see();
stu.say();
}
}
abstract class Person() {
var name: String = _;
var age: Int = _;
def say() = {
println("my name is" + this.name + ",my age is" + this.age)
}
def see();
}
class Student extends Person {
override def see() = {
println("see.....")
}
}
# 37. Scala 中的特质(接口)
- Scala 中特质使用 trait 修饰
- Scala 中的特质中可以有抽象方法, 但是同时可以有普通方法
- 扩展特质使用 extends 或者 with 关键字
object Ops5 {
def main(args: Array[String]): Unit = {val p9 = new HuaWei()
p9.gaming();}
trait IGame {def gaming();
def aa() = {println("aaa.....")
}
}
trait IMp3 {def mp3();
}
class P
class HuaWei extends P with IMp3 with IGame {def gaming() = println("gaming......")
def mp3() = {println("mp3.....")
}
}
# 38. extends 和 with 区别
- extends 可以扩展类, 抽象类, 特质
- with 只能扩展特质
# 39. 扩展 App 的特性
object Ops6 extends App{
println("hello")
}
# 40. Scala 中实现枚举
Scala 并没有枚举类型。不过,标准类库提供了一个 Enumeration 助手类,可以用于产出枚举。定义一个扩展 Enumeration 类的对象并以 Value 方法调用初始化枚举中的所有可选值
object Ops6 extends App {
println(Color.Red)
}
object Color extends Enumeration {
val Red, Yellow, Green =Value
}
# 41. Scala 中的样例类
- Scala 中的一种特殊的类
- 样例类使用 **case class** 来修饰
- 样例类的主构造体必须有(必须有括号)
- 一个类被声明为样例类则自动创建其伴生对象, 并且生成两个 apply 方法(一个无参数, 另外一个的参数和样例类中构造器的参数一致)
- 样例类主构造体中的参数私有化, 但是对外提供 getter 方法
- 除了生命样例类还可以生命样例对象 **case object**, 样例对象不能主构造体
# 42. Scala 模式匹配
- 因为 Scala 中没有 Switch..case 的语法, 但是模式匹配就相当于 switch..case 的语法, 而且比其更加强大
## 42.1 匹配值
val op = “+”
op match {
case "+" => println("加")
case "-" => println("减")
case "*" => println("乘")
case "/" => println("除")
case _ => println("......")// 变量的匹配 变量名称就为 _
}
## 42.2 类型匹配
val s: Any = "hello"
s match {case str: Int => println("Int")
case str: String => println("String 类型")
case _ => println(".....")
}
## 42.3 集合的匹配
val ages = List(12, 13, 14, 15)
ages match {
case List(12, _*) => println("List(12, _*)")
case _ => println("_________")
}
## 42.4 样例类匹配
object Ops5 {
def main(args: Array[String]): Unit = {val user = User("admin", 12)
user match {case User(name, age) => println("..." + name)
}
}
}
case class User(name: String, age: Int)
## 42.3 样例对象匹配
object Ops5 {
def main(args: Array[String]): Unit = {
val user = User(“admin”, 12)
val p = Person
p match {
case Person => println(“…”)
}
}
}
case class User(name: String, age: Int)
case object Person
# 43. Scala 中的 Option
- Option 类型用来表示可能存在也可能不存在的值
- Some(值) 存在值
- None 不存在值
- getOrElse(默认值)
- isEmpty 判断是否为 None
# 44. Scala 中 implicit
## 44.1 隐式参数
- 如果形参使用 implicit 修饰, 要么直接传递隐式参数值, 其实这种直接传递隐式参数值的方式就使得我们的隐式参数没有任何作用了(** 不可取 **)
- 不传递隐式参数的值, 则不能写多余的括号(** 一般不传递 **)
- 隐私参数必须是柯里化方法的最后几个参数
- 隐式参数不传递, 则在运行时就要到当前的上下文中去找与之匹配的隐式值
- 在当前上下文中一个类型只能对应一个隐式值
implicit val c1: Int = 30
implicit val s1 = “hello”
def sum(a: Int)(implicit b: Int, c: String) = a + b + c
val ret: String = sum(10)
println(ret)
## 44.2 隐式类型转换
### 44.2.1 隐式转换发生的时机
- ** 传递的实参的类型和形参的类型不一致时, 会到当前的上下文中去查找有无可用的隐式转换函数(根据函数的签名查找)**
// 定义隐式转换函数
implicit def string2Int(str: String) = str.toInt
def sum(a: Int, b: Int) = a + b
val ret: Int = sum(12, “13”)
println(ret)
- ** 把一个类型的值赋值给另外一个类型的变量时 **
implicit def string2Int(str: String) = str.toInt
val a: Int = “23”// 到当前上下文查找可用的隐式转换函数, 根据函数的签名查找
println(a)
object Ops8 {
def main(args: Array[String]): Unit = {implicit def string2Int(str: String) = str.toInt
implicit def person2Int(p: Person) = p.age
val p = new Person("admin", 23)
val a: Int = p
println(a)
}
}
class Person(name: String, val age: Int)
- ** 调用对象不存在的方法时 **
package com.uplooking.scala
object Ops8 {
def main(args: Array[String]): Unit = {
implicit def phone2MyCustomPhone(phone: Phone) = new MyCustomPhone
val phone = new Phone
phone.say();
}
}
class MyCustomPhone() {
def say() = {
println(“Person…say()”)
}
}
class Phone() {
def call() = {
println(“Phone…call()”)
}
}
## 44.3 隐式类
/隐式类必须定义在 object 中/
implicit class MyCustomPhone(phone: Phone) {
def say() = {println("MyCustomPhone...say()")
}
}
- 隐式类其实就是隐式转换函数的一种语法糖
- 隐式类的功能 = 普通类 + 隐式转换函数
# 45. 类型参数(泛型)
- 类型参数
相当于 Java 中的泛型
- 作用
限定类型
# 46. 泛型类
- 我们把带有一个或者多个类型参数的类,叫作泛型类
object Ops1 {
def main(args: Array[String]): Unit = {val p = new Person[Int, Int]
p.say(12, 12)
}
}
class Person[T, S] {
def say(a: T, s: S) = {println(a)
println(s)
}
}
# 47. 泛型函数
Scala 会从调用该方法使用的实际类型来推断出类型
object Ops1 {
def main(args: Array[String]): Unit = {
val p = new Person
p.say(23, 34)
}
}
class Person {
def sayT, S = {
println(a)
println(s)
}
}
# 48. 类型限定
限定类型的 **"类型"**
## 48.1 上界(重要)
**<:**
[T<: Person] T 是 Person 的子类
## 48.2 下界
**>:**
[T>:Person] T 是 Person 的父类
## 48.3 视图界定(重要)
**<%**
[T <% Person] T 能 ** 隐式转换 ** 成 Person 类型
** 视图界定也包含上界 **
## 48.4 上下文界定
**:**
[T :Person]
** 视图界定的一种语法糖 **
# 49. Scala 中的 Actor
**Java 中多线程 +Socket**
# 50. Scala 中的 Akka
** 基于 Actor 的一个网络通信框架 **
- 缺点
== 版本不兼容 ==
# 51. Spark 中的网络通信
- spark1.6 之前内部通信使用的是 Akka
- spark1.6 的版本中引入了另外一种 ** 默认 ** 的实现 Netty
- 其实在 spark1.6 的版本中有两种网络通信框架 akka+**netty(默认)**
- spark2.x 的版本中 akka 被彻底的弃用了, 纯使用 netty