目标1:熟练使用scala编写Spark程序
目标2:动手编写一个简易版的Spark通信框架
目标3:为阅读Spark内核源码做准备
2、 scala的基本介绍
1、什么是Scala
scala官方网址:
Scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。http://www.scala-lang.org
2、 为什么要学Scala
1、优雅:这是框架设计师第一个要考虑的问题,框架的用户是应用开发程序员,API是否优雅直接影响用户体验。
2、速度快:Scala语言表达能力强,一行代码抵得上Java多行,开发速度快;Scala是静态编译的,所以和JRuby,Groovy比起来速度会快很多。
3、能融合到Hadoop生态圈:Hadoop现在是大数据事实标准,Spark并不是要取代Hadoop,而是要完善Hadoop生态。JVM语言大部分可能会想到Java,但Java做出来的API太丑,或者想实现一个优雅的API太费劲。
3、 Scala编译器安装
1、 安装JDK
因为Scala是运行在JVM平台上的,所以安装Scala之前要安装JDK。
2、 \d{4}(\-|\/|.)\d{1,2}\1\d{1,2}\s\d\d\D\d\d\D\d\d\D\d{1,10}\s\w{1,10}\s{2}\w{1,20}\D\w{1,20}\s\D\w{1,50}\D\w{4}\D\w{6}\D\d{3}\D\D\s\D\s\w{1,11}\s\w{1,12}\d{1,15}\w\d{1,10}\s\w{12}\s\w{4}\s\w{1,15}\s\w\w\s\w{1,20}安装Scala
Windows安装Scala编译器
访问Scala官网http://www.scala-lang.org/下载Scala编译器安装包,目前最新版本是2.12.x,这里下载scala-2.11.8.msi后点击下一步就可以了(自动配置上环境变量)。也可以下载scala-2.11.8.zip,解压后配置上环境变量就可以了。
Linux安装Scala编译器
下载Scala地址https://www.scala-lang.org/download/2.11.8.html
然后解压Scala到指定目录
tar -zxvf scala-2.11.8.tgz -C /usr/java
配置环境变量,将scala加入到PATH中
vi /etc/profile export JAVA_HOME=/usr/java/jdk1.8 export PATH=$PATH:$JAVA_HOME/bin:/usr/java/scala-2.11.8/bin |
Scala开发工具安装
目前Scala的开发工具主要有两种:Eclipse和IDEA,这两个开发工具都有相应的Scala插件,如果使用Eclipse,直接到Scala官网下载即可http://scala-ide.org/download/sdk.html。
由于IDEA的Scala插件更优秀,大多数Scala程序员都选择IDEA,可以到http://www.jetbrains.com/idea/download/下载,点击下一步安装即可,安装时如果有网络可以选择在线安装Scala插件。
这里我们使用离线安装Scala插件:
1.安装IDEA,点击下一步即可。
2.下载IEDA的scala插件
插件地址: https://plugins.jetbrains.com/plugin/1347-scala
3.安装Scala插件:Configure -> Plugins -> Install plugin from disk -> 选择Scala插件 -> OK -> 重启IDEA
3、scala的REPL
REPL ==> 交互式解析器环境
R(read)、E(evaluate) 、P(print)、L(loop)
输入值,交互式解析器会读取输入内容并对它求值,再返回结果,并重复此过程。(所见即所得)
REPL特性:
变量在会话周期内一直可用
REPL历史命令跨会话存储
在命令行输入scala以启动scala REPL
4、 创建工程支持scala代码开发
第一步:idea当中创建创建普通maven工程
File ==> New ==> Project
第二步:修改pom.xml添加scala的版本以及打包插件
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.8</version>
<!-- 如果想要用java -jar 来运行我们打包之后的jar包,则下面这个配置必须注释掉 -->
<!-- <scope>provided</scope>-->
</dependency>
</dependencies>
<build>
<plugins>
<!-- 限制jdk版本插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 编译scala需要用到的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 项目打包用到的插件 -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>cn.itcast.scala.demo1.ScalaFirst</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
第三步:创建scala代码保存的文件夹
src ==> main ==> new ==> Directory ==> scala
第四步:开发scala的代码并打包运行
开发代码如下:
object ScalaFirst {
def main(args: Array[String]): Unit = {
println("hello world")
}
}
第五步:打包我们的scala代码并准备运行
将我们的代码打包,之后,进行运行
双击package之后,就会出现我们打好的jar包,然后选择下面这个就可以运行了
运行我们的代码一共可以有四个命令,两种是打包的时候选择了我们的main程序类的,两种是我们打包时候没有选择main程序类的
其中第一种和第三种,都是选定了我们的main程序类
第二种和第四种都是没有选定我们的main程序类
四种运行方式都可以用于运行我们的jar包
第一种运行方式:我们打包的时候指定了对应的main方法所在的程序类
scala scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar
第二种运行方式:不管打包的时候有没有指定main方法所在的程序类,都可以运行
scala -cp scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar cn.itcast.scala.demo1.ScalaFirst
第三种方式:我们打包的时候指定了对应的main方法所在的程序类
java -jar scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar
第四种方式:不管打包的时候有没有指定main方法所在的程序类,都可以运行
java -cp scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar cn.itcast.scala.demo1.ScalaFirst
我们可以看到我们的scala代码最终也编译成了class文件
4、scala基础入门
1、scala当中申明值和变量
scala当中的变量申明可以使用两种方式,第一种使用val来申明变量。第二种使用var来申明变量。
申明变量语法
val/var 变量名 [:变量类型] = 变量值
其中val定义的变量是不可变的。类似于java当中使用final来进行修饰
REPL自动分配变量名
如果我们在REPL当中没有定义变量名,那么我们的变量名系统会自动给定
注意:scala当中的变量类型可以不用指定,系统会自动推断。为了减少可变性引起的bug,scala当中推荐尽量使用不可变类型来申明变量。var和val申明变量的时候,变量都必须初始化
2、块表达式
定义变量时用 {} 包含一系列表达式,其中块的最后一个表达式的值就是块的值。
var hello = {
println("world")
val d = 20
val c = 10
d-c
}
块表达式最后的结果,就是我们变量的值
3、scala当中常用数据类型
Scala和Java一样,有7种数值类型Byte、Char、Short、Int、Long、Float、Double类型和1个Boolean类型。
Boolean |
true 或者 false |
Byte |
8位, 有符号 |
Short |
16位, 有符号 |
Int |
32位, 有符号 |
Long |
64位, 有符号 |
Char |
16位, 无符号 |
Float |
32位, 单精度浮点数 |
Double |
64位, 双精度浮点数 |
String |
其实就是由Char数组组成 |
与java当中不同,scala当中并不区分基本数据类型和引用数据类型,所有的这些类型全部都是对象,可以调用相对应的方法。在scala当中,String直接引用的是java.lang.String这个java当中的类型。由于String在需要时能隐式转换为StringOps,因此不需要任何额外的转换,String就可以使用这些方法。
每一种数据类型都有对应的Rich* 类型,如RichInt、RichChar等,为基本类型提供了更多的有用操作。
4、scala当中的常用类型结构图
Scala中,所有的值都是类对象,而所有的类,包括值类型,都最终继承自一个统一的根类型Any。统一类型,是Scala的又一大特点。更特别的是,Scala中还定义了几个底层类(Bottom Class),比如Null和nothing。
1) Null是所有引用类型的子类型,而nothing是所有类型的子类型。Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型,但是不能赋值给值类型。
2) nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。
3) Unit类型用来标识过程,也就是没有明确返回值的函数。 由此可见,Unit类似于Java里的void。Unit只有一个实例,(),这个实例也没有实质的意义。
5、算数操作符重载
+-*/%可以完成和Java中相同的工作,但是有一点区别,他们都是方法。你几乎可以用任何符号来为方法命名。
举例:
scala> 1 + 2 等同于: scala> 1.+(2) |
注意:Scala中没有++、--操作符,需要通过+=、-=来实现同样的效果。
5、流程控制语句以及方法和函数
1、if else表达式
scala中没有三目运算符,因为根本不需要。scala中if else表达式是有返回值的,如果if或者else返回的类型不一样,就返回Any类型(所有类型的公共超类型)。
例如:if else返回类型一样
val a = 20
val b = if(a >10){
15
}else{
35
}
例如:if else返回类型不一样
val c = 50
val d = if(c > 20){
println("返回一个字符串")
"ABC"
}else{
println("helloworld")
}
如果缺少一个判断,什么都没有返回,但是Scala认为任何表达式都会有值,对于空值,使用Unit类,写做(),叫做无用占位符,相当于java中的void。
注意:行尾的位置不需要分号,只要能够从上下文判断出语句的终止即可。但是如果在单行中写多个语句,则需要分号分割。在Scala中,{}快包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。
2、while表达式
scala提供了类似于java的while和do循环,但是while语句的本身是没有任何返回值类型的,也就是while语句最终的返回结果是Unit类型的()。
var e = 1;
val f = while(e <= 10){
e +=1
}
println(e)
println(f)
scala当中while循环的contine和break:注意:scala当中并没有提供类似于java的continue和break操作,如果需要终止循环,我们可以有以下几种方式
1、 使用Boolean标识来进行终端
3、 使用Breaks对象当中的break方法
var g = 10
val loop = new Breaks
loop.breakable{
val h = while(g
<=20){
g +=1
if(g == 15){
loop.break()
}
}
println(h)
}
println(g+"=============")
3、for表达式
scala当中,为for循环这一常用的循环结构提供了很多的特性,这些特性被称之为for推导式或者for表达式
示例一:使用to实现左右两边均为闭合的访问
for(i <- 1 to 3; j <- 1 to 5){
println( i *j +" result result result result")
}
示例二:使用util实现左右两边分别为前闭后开的访问
for(i <- 1 until 5 ;j <- 2 until 5){
println(i * j )
}
示例三:引入保护式(也称条件判断式)。我们可以在for循环条件里面加入判断表达式,如果满足则进入for循环,如果不满足则不进入for循环。类似于java当中的continue的功能类似
for(i <- 1 to 5 if i!=2){
println(i)
}
示例四:引入变量
for(i <- 1 to 3 ;j = 4-i){
println(i * j)
}
示例五:将遍历过程中处理的结果返回到一个变量,使用yield关键字进行接收
val for5 = for(i <- 1
to 10) yield i
println(for5+"for5")
示例六:使用大括号代替小括号
for{
i <- 1 to 5
j = 5-i
}
println( i* j +"myij")
4、调用函数与方法
在scala中,一般情况下我们不会刻意的去区分函数与方法的区别,但是他们确实是不同的东西。后面我们再详细探讨。首先我们要学会使用scala来调用函数与方法。
scala> import scala.math._ scala> sqrt(100) |
2) 调用方法,静态方法(scala中没有静态方法这个概念,需要通过伴生类对象来实现)
scala> BigInt.probablePrime(16, scala.util.Random) |
scala> "HelloWorld".distinct |
4) apply与update方法
apply方法是调用时可以省略方法名的方法。用于构造和获取元素:
"Hello"(4) 等同于 "Hello".apply(4) Array(1,2,3) 等同于 Array.apply(1,2,3) 如: println("Hello"(4)) println("Hello".apply(4)) |
在StringOps中你会发现一个 def apply(n: Int): Char方法定义。update方法也是调用时可以省略方法名的方法,用于元素的更新:
arr(4) = 5 等同于 arr.update(4,5) 如: val arr1 = new Array[Int](5) arr1(1) = 2 arr1.update(1, 2) println(arr1.mkString(",")) |
5、scala当中的函数与方法
在scala当中,函数与方法是两个不同的概念,函数是scala当中的一等公民,scala是一门函数式的编程语言,同时兼顾了面向对象语言的特性
scala当中方法的定义
scala定义方法的标准格式为
示例一:定义一个最标准的方法,且定义方法的返回值类型为Int类型
def hello(first:String,second:Int) :Int = {
second
}
注意:如果定义的方法没有返回值,那么方法的返回值会做自动推断。根据我们方法的最后一个返回类型来推断我们的方法返回类型
def hello2(first:Int , second:String) ={
//println(first)
//20
}
val hello2Result = hello2(20,"abc")
println( hello2Result)
示例三:定义一个方法,不定义返回值,可以通过自动推断,返回不同类型的值
def hello3(first:Int,second:String) ={
if(first > 10){
first
}else{
second
}
}
val hello3Result = hello3(5,"helloworld")
println(hello3Result)
示例四:定义一个方法,参数给定默认值,如果不传入参数,就使用默认值来代替
def hello4(first:Int = 10,second:String)={
println(first+"\t"+ second)
}
//注意我们在调用方法的时候我们可以通过参数名来指定我们的参数的值
hello4(second="helloworld")
示例五:变长参数,方法的参数个数不定的,类似于java当中的方法的...可变参数
def hello5(first:Int*)={
var result = 0;
for(arg <- first){
result += arg
}
println(result)
}
hello5(10,20,30)
hello5(10,50)
示例六:递归函数。我们可以定义一个方法,使得方法自己调用自己,形成一个递归函数,但是方法的返回值类型必须显示的手动指定
def hello6(first:Int):Int={
if(first <= 1){
1
}else{
first * hello6(first -1)
}
}
val hello6Result = hello6(10)
println(hello6Result)
示例七:定义一个方法,没有显示的指定返回值,那么我们方法当中定义的等号可以省掉
注意:如果省掉了=号,那么这个方法强调的就是一个代码执行的过程
/**
* 定义了一个方法,但是方法的返回值没有显示指定,
* 此时我们就可以省掉方法定义的=号,如果省掉 = 号,
* 那么这个方法强调的是一个过程,代码执行的过程,
* 不会产生任何的返回值
* @param first
*/
def hello7(first:Int){
println(first)
30
}
hello7(20)
def hello8=10;
val hello8Result = hello8
println(hello8Result)
def hello10(first:Int,second:Int)
= first+second
val hello10Result = hello10(10,20)
println(hello10Result)
scala当中函数的定义
函数定义的两种形式
第一种形式:
val 函数名 = (参数名1:参数类型1,参数名2:参数类型2) => {函数体}
第二种形式:
val 函数名 :(参数类型1,参数类型2) => (返回类型) = {
函数体
}
val func1 =(x:Int,y:Int)
=>{
x+y
}
func1(2,8)
(x:Int,y:String) =>{x + y}
示例三:函数定义的另外一种形式,定义一个函数,参数只有一个且是Int类型,返回值也是Int类型
val func3 :Int => Int =
{x => x * x }
val func3Result = func3(10)
示例四:定义一个函数,参数值是两个,分别是Int和String,返回值是一个元组,分别是String和Int
val func4:(Int,String) =>(String,Int) ={
(x,y) => (y,x)
}
val func4Result = func4(10,"hello")
println(func4Result)
scala当中函数与方法的区别以及方法转换成函数
在scala当中,函数与方法是有区别的,函数可以作为一个参数,传入到方法里面去
我们可以定义一个函数,然后再定义一个方法,但是方法的参数是一个函数
val myFunc = (x:Int) =>{
x * x
}
val myFunc2 :(Int) => Int ={
x => x * x
}
def methodFunction(f:Int =>
Int):Int ={
println(f(100))
f(100)
}
val methodFunctionResult =
methodFunction(myFunc)
val methodFunctionResult2 =
methodFunction(myFunc2)
println(methodFunctionResult)
println(methodFunctionResult2)
def method2(x:Int) ={ x * x
}
def methodFunc2(x:Int => Int):Int
={
x(100)
}
val methodFunc2Result =
methodFunc2(method2)
println(methodFunc2Result)
def method3(x:Int,y:String ) :Int = {
println(x)
x
}
val methodToFunc = method3 _
println( methodToFunc)
6、懒值加载
当val被声明为lazy时,他的初始化将被推迟,直到我们首次对此取值,适用于初始化开销较大的场景。
def init(): String = {
println("init方法执行")
"嘿嘿嘿,喵喵喵~"
}
lazy val msg = init()
println("lazy方法没有执行")
println(msg)
6、数据结构
6.1、数据结构特点
Scala同时支持可变集合和不可变集合,不可变集合从不可变,可以安全的并发访问。
两个主要的包:
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable
Scala优先采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。
不可变集合继承层次:
可变集合继承层次:
6.2、数组
1、定义定长数组
我们可以定义一个固定长度大小和类型的定长数组
//定义一个数组长度为10,类型为Int的固定大小数组
val array = new Array[Int](10)
array(1) = 10
array(2) = 20
//访问数组当中的某一个下标元素值
println(array(1))
//直接使用apply方法进行生成一个数组
val array2 = Array(1,2,3)
//访问数组的元素
println(array2(2))
2、变长数组
我们也可以通过ArrayBuffer来定义一个变长数组
val array3 = new ArrayBuffer[Int]()
array3.append(20)
val array4 = ArrayBuffer[String]()
array4.append("helloworld")
3、定长数组与变长数组的相互转换
定长数组转换成变长数组
//定长数组转换成变长数组
val toBuffer = array.toBuffer
toBuffer.append(50)
变长数组转换为定长数组
//变长数组准换成定长数组
val toArray = array3.toArray
4、多维数组
我们可以通过Array的ofDim方法来定义一个多维的数组,多少行,多少列,都是我们自己定义说了算
val dim = Array.ofDim[Double](3,4)
dim(1)(1) = 11.11
println(dim.mkString(","))
5、scala当中数组的遍历
val array5 = ArrayBuffer(1,2,3,4,5,6)
for(x <- array5){
println(x )
}
5、 数组的常见算法
val array6 = Array(1,2,3,4,5,6)
//求和
array6.sum
//求最大值
array6.max
//排序
array6.sorted
6.3、元组tuple
在scala当中提供元组tuple的数据类型,可以理解tuple为一个容器,可以存放各种不同的数据类型的数据,例如一个Tuple当中既可以存放String类型数据,同时也可以存放Int类型的数据
注意:注意元组一旦创建之后,就是不可变的,也就是说元组当中没有添加和删除元素这一说
1、 创建元组
创建元组,直接使用小括号,小括号当中存放我们元组当中各种类型的元素即可
val tuple1 = ("hello",1,5.0f)
println(tuple1)
2、元组数据的访问
访问元组当中的数据直接使用_加角标即可,但是要注意,元组当中的数据角标是从1开始的
val tuple1 = ("hello",1,5.0f)
println(tuple1)
val tuple1Result = tuple1._1
println(tuple1Result)
3、元组的遍历
val tuple1 = ("hello",1,5.0f)
println(tuple1)
val tuple1Result = tuple1._1
println(tuple1Result)
//第一种方式遍历元组
for(x <- tuple1.productIterator){
println(x)
}
//第二种方式遍历元组
tuple1.productIterator.foreach( x => println(x))
6.4、映射Map
scala当中的Map集合与java当中的Map类似,也是key,value对形式的
1、不可变映射
val map1 = Map("hello" ->"world","name" -> "zhangsan","age" -> 18)
2、 可变映射及其操作
val map2 =
scala.collection.mutable.Map("hello"
->"world","name" -> "zhangsan","age" -> 18)
//可变map添加元素
map2.+=("address" ->"地球")
println(map2)
//可变map删除元素.注意,删除元素是返回一个删除元素之后的map,原来的map并没有改变
val map3 = map2.-("address")
println(map2)
println(map3)
//或者使用覆盖key的方式来更细元素
map2
+= ("address" -> "北京")
println(map2)
//或者使用 + 来进行更新元素
//注意,map当中没有phonNo这个key,则不能更细
map2
+("address" ->"上海","phonNo"
-> "13688886666")
println(map2)
3、获取map当中指定的key值
//通过key来进行取值
map2.get("address")
//通过key来进行取值,如果没有这个key,就用后面给定的默认值
map2.getorElse("address","非洲")
//通过key来进行取值,真的没有这个key,那么就用后面给定的默认值
map2.getorElse("phoNo","13133335555")
4、遍历Map当中的元素
//遍历key与value
for((k,v) <- map2){
println(k)
println(v)
}
//遍历获取所有的key
for(k <- map2.keys) {
println(k)
}
//遍历获取所有的value
for(v <- map2.values) {
println(v)
}
//打印key,value对
for(kv <- map2){
println(kv)
}
5、将对偶的数组转变为map
//将对偶的元组转变为map
val arrayMap = Array(("name","zhangsan"),("age",28))
val toMap = arrayMap.toMap
println(toMap)
6.5、列表(List)
scala当中也提供有与java类似的List集合操作
1、 创建列表
注意:列表当中的元素类型可以是不同的,这一点与我们元组类似,但是列表当中的元素是可以删减的
val list1 = List("hello",20,5.0f)
println(list1)
2、访问列表当中的元素
//访问列表当中的元素
val list1Result = list1(0)
println(list1Result)
3、列表当中添加元素
我们可以从列表头部或者尾部添加元素
val list2 = list1:+50
val list3 = 100+:list1
println(list2)
println(list3)
4、List的创建与追加元素
//尾部添加了Nil,那么就会出现List集合里面装List集合的现象
val list4 = 1::2 ::3 :: list1 ::Nil
println(list4)
//尾部没有添加Nil的值,那么所有的元素都压平到一个集合里面去了
val list5 = 1::2::3::list1
println(list5)
5、变长List的创建与使用
val list6 = new ListBuffer[String]
list6.append("hello")
list6.append("world")
println(list6.mkString(","))
val list7 = list6.toList
println(list7)
6、 List操作延伸阅读
Scala是函数式风格与面向对象共存的编程语言,方法不应该有副作用是函数风格编程的一个重要的理念。方法唯一的效果应该是计算并返回值,用这种方式工作的好处就是方法之间很少纠缠在一起,因此就更加可靠和可重用。另一个好处(静态类型语言)是传入传出方法的所有东西都被类型检查器检查,因此逻辑错误会更有可能把自己表现为类型错误。把这个函数式编程的哲学应用到对象世界里以为着使对象不可变。
前面一章介绍的Array数组是一个所有对象都共享相同类型的可变序列。比方说Array[String]仅包含String。尽管实例化之后你无法改变Array的长度。因此,Array是可变的对象。
说到共享相同类型的不可变对象类型,Scala的List类才是。和数组一样,List[String]包含的仅仅是String。Scala的List不同于Java的java.util.List,总是不可变的(Java的List是可变)。更准确的说法,Scala的List是设计给函数式风格的编程用的。
(1)List类型定义以及List的特点:
//字符串类型List
scala> val fruit=
List(
"Apple",
"Banana",
"Orange")
fruit:
List[String] =
List(Apple, Banana, Orange)
//前一个语句与下面语句等同
scala> val fruit=
List.apply(
"Apple",
"Banana",
"Orange")
fruit:
List[String] =
List(Apple, Banana, Orange)
//数值类型List
scala> val nums=
List(
1,
2,
3,
4,
5)
nums:
List[Int] =
List(
1,
2,
3,
4,
5)
//多重List,List的子元素为List
scala> val
list
=
List(
List(
1,
2,
3),
List(
"adfa",
"asdfa",
"asdf"))
list:
List[
List[Any]] =
List(
List(
1,
2,
3),
List(adfa, asdfa, asdf))
//遍历List
scala>
for(i <-
list; from=i; j<-from)println(j)
1
2
3
adfa
asdfa
asdf
(2)List与Array的区别:
1、List一旦创建,已有元素的值不能改变,可以使用添加元素或删除元素生成一个新的集合返回。
如前面的nums,改变其值的话,编译器就会报错。而Array就可以成功
scala>nums(
3)=
4
<
console>:
10: error: value update
is
not a member
of List[Int]
nums(
3)=
4
^
2、List具有递归结构(Recursive Structure),例如链表结构
List类型和气他类型集合一样,它具有协变性(Covariant),即对于类型S和T,如果S是T的子类型,则List[S]也是List[T]的子类型。
例如:
scala>
var listStr:
List[
Object] =
List(
"This",
"Is",
"Covariant",
"Example")
listStr:
List[
Object] =
List(This, Is, Covariant, Example)
//空的List,其类行为nothing,nothing在Scala的继承层次中的最底层
//,即nothing是任何Scala其它类型如String,Object等的子类
scala>
var listStr =
List()
listStr:
List[nothing] =
List()
scala>
var listStr:
List[
String] =
List()
listStr:
List[
String] =
List()
(3)List常用构造方法
//1、常用::及Nil进行列表构建
scala> val nums =
1
:: (
2:: (
3:: (
4 :: Nil)))
nums:
List[Int] =
List(
1,
2,
3,
4)
//由于::操作符的优先级是从右向左的,因此上一条语句等同于下面这条语句
scala> val nums =
1::
2::
3::
4::Nil
nums:
List[Int] =
List(
1,
2,
3,
4)
至于
::
操作符的使用将在下面介绍
(4)List常用操作
//判断是否为空
scala> nums.isEmpty
res5: Boolean =
false
//取第一个元素
scala> nums.head
res6: Int =
1
//取列表第二个元素
scala>nums.tail.head
res7: Int =
2
//取第三个元素
scala>nums.tail.tail.head
res8: Int =
3
//插入操作
//在第二个位置插入一个元素
scala>nums.head::(
3::nums.tail)
res11: List[Int] = List(
1,
3,
2,
3,
4)
scala> nums.head::(nums.tail.head::(
4::nums.tail.tail))
res12: List[Int] = List(
1,
2,
4,
3,
4)
//插入排序算法实现
def isort(xs: List[Int]):List[Int] =
{
if(xs.isEmpty)
Nil
else insert(xs.head, issort(xs.tail))
}
def insert(x:Int, xs:List[Int]):List[Int] =
{
if(xs.isEmpty
|| x <= xs.head) x::xs
else
xs.head :: insert(x, xs.tail)
}
//连接操作
scala>List(
1,
2,
3):::List(
4,
5,
6)
res13: List[Int] = List(
1,
2,
3,
4,
5,
6)
scala> nums.init
res13: List[Int] = List(
1,
2,
3)
//取出列表最后一个元素
scala>nums.last
res14: Int =
4
//列表元素倒置
scala> nums.reverse
res15: List[Int] = List(
4,
3,
2,
1)
scala> nums.reverse.reverse == nums
//丢弃前面n个元素
scala>nums drop
3
res16: List[Int] = List(
4)
//获取前面n个元素
scala>nums take
1
res17: List[Int] = List[
1]
//将列表进行分割
scala> nums.splitAt(
2)
res18: (List[Int], List[Int]) = (List(
1,
2),List(
3,
4))
//前一个操作与下列语句等同
scala> (nums.take(
2),nums.drop(
2))
res19: (List[Int], List[Int]) = (List(
1,
2),List(
3,
4))
//Zip操作
scala> val nums=List(
1,
2,
3,
4)
nums: List[Int] = List(
1,
2,
3,
4)
scala> val chars=List(
'1',
'2',
'3',
'4')
chars: List[Char] = List(
1,
2,
3,
4)
//返回的是List类型的元组(Tuple),返回的元素个数与最小的List集合的元素个数一样
scala> nums zip chars
res20: List[(Int, Char)] = List((
1,
1),
(
2,
2), (
3,
3), (
4,
4))
//List toString方法
scala> nums.toString
res21: String = List(
1,
2,
3,
4)
//List mkString方法
scala> nums.mkString
res22: String =
1234
//转换成数组
scala> nums.toArray
res23: Array[Int] = Array(
1,
2,
3,
4)
(5)List伴生对象方法
//apply方法
scala>
List.apply(
1,
2,
3)
res24:
List[Int] =
List(
1,
2,
3)
//range方法,构建某一值范围内的List
scala>
List.range(
2,
6)
res25:
List[Int] =
List(
2,
3,
4,
5)
//步长为2
scala>
List.range(
2,
6,
2)
res26:
List[Int] =
List(
2,
4)
//步长为-1
scala>
List.range(
2,
6,
-1)
res27:
List[Int] =
List()
scala>
List.range(
6,
2
,
-1)
res28:
List[Int] =
List(
6,
5,
4,
3)
//构建相同元素的List
scala>
List.make(
5,
"hey")
res29:
List[
String] =
List(hey, hey, hey, hey, hey)
//unzip方法
scala>
List.unzip(res20)
res30: (
List[Int],
List[Char]) = (
List(
1,
2,
3,
4),
List(
1,
2,
3,
4))
//list.flatten,将列表平滑成第一个无素
scala> val xss =
|
List(
List(
'a',
'b'),
List(
'c'),
List(
'd',
'e'))
xss:
List[
List[Char]] =
List(
List(a, b),
List(c),
List(d, e))
scala> xss.flatten
res31:
List[Char] =
List(a, b, c, d, e)
//列表连接
scala>
List.concat(
List(
'a',
'b'),
List(
'c'))
res32:
List[Char] =
List(a
, b, c)
(6)::和:::操作符介绍
List中常用'::',发音为"cons"。Cons把一个新元素组合到已有元素的最前端,然后返回结果List。
scala>
val twoThree = List(2, 3)
scala>
val oneTwoThree = 1 :: twoThree
scala>
oneTwoThree
oneTwoThree: List[Int] = List(1, 2, 3)
上面表达式"1::twoThree"中,::是右操作数,列表twoThree的方法。可能会有疑惑。表达式怎么是右边参数的方法,这是Scala语言的一个例外的情况:如果一个方法操作符标注,如a * b,那么方法被左操作数调用,就像a.* (b)--除非方法名以冒号结尾。这种情况下,方法被右操作数调用。
List有个方法叫":::",用于实现叠加两个列表。
scala> val one =
List(
'A',
'B')
val one =
List(
'A',
'B')
scala> val two =
List(
'C',
'D')
scala> one:::two
res1:
List[Char] =
List(A, B, C, D)
6.6、Set集合
集是不重复元素的结合。集不保留顺序,默认是以哈希集实现。
如果想要按照已排序的顺序来访问集中的元素,可以使用SortedSet(已排序数据集),已排序的数据集是用红黑树实现的。
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包。
1、 不可变集合的创建
val set1 =Set("1","1","2","3")
println(set1.mkString(","))
2、 可变集合的创建以及添加元素
如果我们引入的集合的包是可变的,那么我们创建的集合就是可变的
import scala.collection.mutable.Set
val set2 = Set(1, 2, 3)
set2.add(4)
set2 += 5
//使用.这个方法添加元素,会返回一个新的集合
val set3 = set2.+(6)
println(set2.mkString(","))
println(set3.mkString("\001"))
3、可变集合删除元素
set3
-= 1
println(set3.mkString("."))
set3.remove(2)
println(set3.mkString("."))
4、 遍历Set集合元素
for(x <- set3){
println(x )
}
注意:如果要创建有序的set,那么需要使用SortedSet。用法与Set类似
更多Set集合操作参见如下:
http://www.runoob.com/scala/scala-sets.html
5、Set更多常用操作介绍
序号 |
描述 |
|
1 |
def +(elem: A): Set[A] |
|
2 |
def -(elem: A): Set[A] |
移除集合中的元素,并创建一个新的集合 |
3 |
def contains(elem: A): Boolean |
如果元素在集合中存在,返回 true,否则返回 false。 |
4 |
def &(that: Set[A]): Set[A] |
返回两个集合的交集 |
5 |
def &~(that: Set[A]): Set[A] |
返回两个集合的差集 |
6 |
def ++(elems: A): Set[A] |
合并两个集合 |
7 |
def drop(n: Int): Set[A]] |
返回丢弃前n个元素新集合 |
8 |
def dropRight(n: Int): Set[A] |
返回丢弃最后n个元素新集合 |
9 |
def dropWhile(p: (A) => Boolean): Set[A] |
从左向右丢弃元素,直到条件p不成立 |
10 |
def max: A |
查找最大元素 |
11 |
def min: A |
查找最小元素 |
12 |
def take(n: Int): Set[A] |
返回前 n 个元素 |
6.7、集合元素与函数的映射
我们可以使用map方法,传入一个函数,然后将这个函数作用在集合当中的每一个元素上面
val listFunc = List("name","age","zhangsan","lisi")
println(listFunc.map(x => x +"hello"))
println(listFunc.map(_.toupperCase()))
flatmap:flat即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合
val listFunc2 = List("address","phonNo")
println(listFunc2.flatMap( x => x +"WORLD"))
6.8、队列Queue
队列Queue是一个先进先出的结构
1、 创建队列
//创建可变的队列
val queue1 = new mutable.Queue[Int]()
println(queue1)
2、队列当中添加元素
//队列当中添加元素
queue1
+= 1
//队列当中添加List
queue1
++=List(2,3,4)
println(queue1)
3、按照进入队列顺序,删除队列当中的元素(弹出队列)
val dequeue =
queue1.dequeue()
println(dequeue)
println(queue1)
4、向队列当中加入元素(入队列操作)
//塞入元素到队列
queue1.enqueue(5,6,7)
println(queue1)
5、获取第一个与最后一个元素
//获取第一个元素
println(queue1.head)
//获取最后一个元素
println(queue1.last)
6.9、集合当中的化简、折叠与扫描操作
1、折叠、化简 reduce操作
val reduceList = List(1,2,3,4,5)
//1-2-3-4-5 = -13
val reduceLeftList =
reduceList.reduceLeft(_ - _)
val reduceLeftList2 =
reduceList.reduceLeft((x,y) => x-y)
println(reduceLeftList)
println(reduceLeftList2)
//reduceRight操作
// 4-5 = -1
// 3- (-1) = 4
//2-4 = -2
//1 -(-2) = 3
val reduceRightList =
reduceList.reduceRight(_ - _)
println(reduceRightList)
2、 折叠、化简folder操作
fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历。可以把reduceLeft看做简化版的foldLeft。相关函数:fold,foldLeft,foldRight,可以参考reduce的相关方法理解。
reduce的本质其实就是fold操作,只不过我们使用fold操作的时候,需要指定初始值
fold操作
val foldList = List(1,9,2,8)
val foldResult = foldList.fold(10)((x,y) => x+y)
println(foldResult)
foldLeft操作
//50-1-9-2-8 = 30
val foldLeftResult =
foldList.foldLeft(50)((x,y) => x-y)
println(foldLeftResult)
6.10、拉链操作
对于多个List集合,我们可以使用Zip操作,将多个集合当中的值绑定到一起去
val zipList1 = List("name","age","sex")
val zipList2 = List("zhangsan",28)
val zip = zipList1.zip(zipList2)
val toMap1 = zip.toMap
println(zip)
println(toMap1)
6.11、迭代器
对于集合当中的元素,我们也可以使用迭代器来进行遍历
val listIterator = List(1,2,"zhangsan")
val iterator = listIterator.iterator
while(iterator.hasNext){
println(iterator.next())
}
6.12、线程安全的集合
scala当中为了解决多线程并发的问题,提供对应的线程安全的集合
https://www.scala-lang.org/api/2.11.8/#package
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizedSet
6.13、操作符
大致了解即可
1) 如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号反引号:
val `val` = 42 |
2) 这种形式叫中置操作符,A操作符B等同于A.操作符(B)
3) 后置操作符,A操作符等同于A.操作符,如果操作符定义的时候不带()则调用时不能加括号
4) 前置操作符,+、-、!、~等操作符A等同于A.unary_操作符。
5) 赋值操作符,A操作符=B等同于A=A操作符B
7、 高阶函数
7.1、作为参数的函数
def main(args: Array[String]): Unit = {
val myFu(x:Int) =>{
x * x
}nc1 =
val myArray = Array(1,3,5,7,9).map(myFunc1)
println(myArray.mkString(","))
}
7.2、匿名函数
没有名字的函数即是匿名函数,我们可以通过函数表达式来设置匿名函数
def main(args: Array[String]): Unit = {
println((x:Int,y:String) => x + y)
}
7.3、高阶函数
def main(args: Array[String]): Unit = {
val func3:(Int,String) =>(String,Int)={
(x,y)=>(y,x)
}
def myMethod3(hello:(Int,String) => (String,Int)):Int ={
val resultFunc = hello(20,"hello")
resultFunc._2
}
println(myMethod3(func3))
}
def main(args: Array[String]): Unit = {
def myFunc4(x:Int) = (y:String) => y
println(myFunc4(50))
}
7.4、参数类型推断
def main(args: Array[String]): Unit = {
val array = Array(1,2,3,4,5,6,7,8,9)
//map当中需要传入一个函数,我们可以直接定义一个函数
array.map((x:Int) => x * 2 )
//进一步化简 参数推断省去类型信息
array.map((x) => x * 2 )
//进一步化简 单个参数可以省去括号
array.map( x => x * 2 )
//进一步化简 如果变量只在=>右边只出现一次,可以用_来代替
array.map( 2 * _ )
}
7.5、闭包与柯里化
柯里化存在的意义是什么???注意:柯里化是scala当中面向函数式编程导致的一种必然的结果,最终推导而来产生的一种现象
def main(args: Array[String]): Unit = {
//柯里化的定义形式
def kery(x:Int)(y:Int):Int={
x + y
}
println(kery(3)(5))
//柯里化的推导过程,注意方法的返回值不要定义任何的返回值类型
val keryResult = (x:Int) => (y:Int) => {x + y}
def keryMethod(x:Int) ={
(y:Int) => x+ y
}
println(keryMethod(20))
println(keryMethod(20)(10))
//注意,方法当中的函数,调用了方法的参数,就叫做闭包
}
再来看一个案例
/**
* 柯里化的应用,比较数组集合当中两个对应下标字符串是否相等
* @param args
*/
def main(args: Array[String]): Unit = {
val a = Array("Hello", "World")
val b = Array("hello", "world")
println(a.corresponds(b)(_.equalsIgnoreCase(_)))
}
8、scala当中的类
1、类的定义与创建
创建一个scala class来定义我们的一个类。类当中可以定义各种属性或者方法,或者函数都可以
class Person {
//定义一个属性,叫做name的,使用val不可变量来进行修饰
// 用val修饰的变量是可读属性,有getter但没有setter(相当与Java中用final修饰的变量)
val name:String
="zhangsan"
//定义一个属性,叫做age的,使用var可变量来进行修饰
//用var修饰的变量都既有getter,又有setter
var age:Int = 28
//类私有字段,只能在类的内部使用或者伴生对象中访问
private val address:String = "地球上"
//类私有字段,访问权限更加严格的,该字段在当前类中被访问
//在伴生对象里面也不可以访问
private[this] var pet =
"小强"
//在类当中定义了一个方法,
def hello(first:Int,second:String):Int ={
println(first+"\t"+second)
250
}
/**
* 定义了一个函数
*/
val func1 =(x:Int,y:Int) =>{
x+y
}
}
class Person{
|
2、类的实例化以及使用
如果想要使用类的话,那么REPL就满足不了我们的要求了,我们重新创建一个对应的Object的scala文件
object ScalaClass {
def main(args: Array[String]): Unit = {
//创建对象两种方式。这里都是使用的无参构造来进行创建对象的
val person = new Person
val person1 = new Person()
//注意,我们可以使用对象的属性加上_=
给var修饰的属性进行重新赋值
//其实就是调用set方法,方法名叫做
age_=
person.age_= (50)
//直接调用类的属性,其实就是调用get方法
println(person.age)
println(person.hello(50,"helloworld"))
val func = person.func1(10,20)
println(func)
println("============")
}
}
3、属性的getter和setter方法
对于scala类中的每一个属性,编译后,会有一个私有的字段和相应的getter、setter方法生成
//getter方法
println(person
age)
//setter方法
println(person
age_= (18))
//getter方法
println(person.age)
当然了,你也可以不使用自动生成的方式,自己定义getter和setter方法
class Dog2 { private var _leg = 4 def leg = _leg def leg_=(newLeg: Int) { _leg = newLeg } } |
使用之:
val dog2 = new Dog2 dog2.leg_=(10) println(dog2.leg) |
规范提示:自己手动创建变量的getter和setter方法需要遵循以下原则:
1) 字段属性名以“_”作为前缀,如:_leg
2) getter方法定义为:def leg = _leg
3) setter方法定义时,方法名为属性名去掉前缀,并加上后缀,后缀是:“leg_=”,如例子所示
4、类的构造器
scala当中类的构造器分为两种:主构造器和辅助构造器
scala当中规定,所有的辅助构造器,最后都必须调用另外一个构造器,另外一个构造器可以是辅助构造器,也可以是主构造器
//主构造器,直接定义在类上面
class Dog (name:String,age:Int){
//在scala当中,可以直接将代码写在class当中,而在java当中,
//代码必须包含在方法当中。
//其实在scala当中,虽然你把代码写在了Class类当中,经过编译之后,
//class类的代码都进入到了主构造器方法当中去了
println(name)
println(age)
var gender:String
= "";
def this(name:String,age:Int,gender:String){
//每个辅助构造器,都必须以其他辅助构造器,或者主构造器的调用作为第一句
this(name:String,age:Int)
this.gender = gender
}
var color ="";
/**
* 我们也可以通过private来进行修饰我们的构造器,
* @param name
* @param age
* @param color
* @param gender
*/
private def this(name:String,age:Int,color:String,gender:String){
this(name:String,age:Int)
this.color = color
}
}
9、scala当中的对象
1、scala当中的Object
在scala当中,没有类似于像java当中的static修饰的静态属性或者静态方法或者静态代码块之类的,但是我们可以通过scala当中的Object来实现类似的功能。可以理解为scala当中的Object里面的属性或者方法都是静态的,可以直接调用
定义一个class类,然后在class类当中定义一个Object的对象。object对象当中的所有属性或者方法都是静态的
class Session {
def hello(first:Int):Int={
println(first)
first
}
}
object SessionFactory{
val session
= new Session
def getSession():Session ={
session
}
def main(args: Array[String]): Unit = {
for(x <- 1
to 10){
//通过直接调用,产生的对象都是单列的
val session = SessionFactory.getSession()
println(session)
}
}
}
2、伴生类与伴生对象
- 如果有一个class文件,还有一个与class同名的object文件,那么就称这个object是class的伴生对象,class是object的伴生类;
- 伴生类和伴生对象必须存放在一个.scala文件中;
- 伴生类和伴生对象的最大特点是,可以相互访问;
- 举例说明:
class ClassObject {
val id = 1
private var name = "itcast"
def printName(): Unit ={
//在Dog类中可以访问伴生对象Dog的私有属性
println(ClassObject.CONSTANT + name )
}
}
object ClassObject{
//伴生对象中的私有属性
private val CONSTANT = "汪汪汪 : "
def main(args: Array[String]) {
val p = new
ClassObject
//访问私有的字段name
p.name = "123"
p.printName()
}
}
3、scala当中的apply方法
- object 中非常重要的一个特殊方法,就是apply方法;
- apply方法通常是在伴生对象中实现的,其目的是,通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
- 通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,...参数n)时apply方法会被调用;
- 在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用 class(),隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;
- 举例说明:
class ApplyObjectClass (name:String){
println(name)
}
object ApplyObjectClass{
def apply(name:String): ApplyObjectClass = {
new ApplyObjectClass(name)
}
def main(args: Array[String]): Unit = {
//调用的apply方法来创建对象
val applyObjectClass = ApplyObjectClass("lisi")
//调用的是new Class来创建对象
val applyObjectClass2 =new ApplyObjectClass("wangwu")
}
}
4、scala当中的main方法
- 同Java一样,如果要运行一个程序,必须要编写一个包含 main 方法的类;
- 在 Scala 中,也必须要有一个 main 方法,作为入口;
- Scala 中的 main 方法定义为 def main(args: Array[String]),而且必须定义在 object 中;
- 除了自己实现 main 方法之外,还可以继承 App Trait,然后,将需要写在 main 方法中运行的代码,直接作为 object 的 constructor 代码即可,而且还可以使用 args 接收传入的参数;
- 案例说明:
//1.在object中定义main方法
object Main_Demo1 {
def main(args: Array[String]) {
if(args.length > 0){
println("Hello, " + args(0))
}else{
println("Hello World1!")
}
}
}
//2.使用继承App Trait ,将需要写在 main 方法中运行的代码
// 直接作为 object 的 constructor 代码即可,
// 而且还可以使用 args 接收传入的参数。
object Main_Demo2 extends App{
if(args.length > 0){
println("Hello, " + args(0))
}else{
println("Hello World2!")
}
}
5、枚举
Scala中没有枚举类型,但是我们可以退通过定义一个扩展Enumeration类的对象,并以value调用初始化枚举中的所有可能值:
10、scala当中的继承
1、Scala中继承(extends)的概念
- Scala 中,让子类继承父类,与 Java 一样,也是使用 extends 关键字;
- 继承就代表,子类可继承父类的 field 和 method ,然后子类还可以在自己的内部实现父类没有的,子类特有的 field 和method,使用继承可以有效复用代码;
- 子类可以覆盖父类的 field 和 method,但是如果父类用 final 修饰,或者 field 和 method 用 final 修饰,则该类是无法被继承的,或者 field 和 method 是无法被覆盖的。
- private 修饰的 field 和 method 不可以被子类继承,只能在类的内部使用;
- field 必须要被定义成 val 的形式才能被继承,并且还要使用 override 关键字。 因为 var 修饰的 field 是可变的,在子类中可直接引用被赋值,不需要被继承;即 val 修饰的才允许被继承,var 修饰的只允许被引用。继承就是改变、覆盖的意思。
- Java 中的访问控制权限,同样适用于 Scala
|
类内部 |
本包 |
子类 |
外部包 |
|
|
|
|
|
public |
√ |
√ |
√ |
√ |
|
|
|
|
|
protected |
√ |
√ |
√ |
× |
|
|
|
|
|
default |
√ |
√ |
× |
× |
|
|
|
|
|
private |
√ |
× |
× |
× |
|
|
|
|
|
- 举例说明:
package cn.itcast.extends_demo
class Person {
val name="super"
def getName=this.name
}
class Student extends Person{
//继承加上关键字
override
val name="sub"
//子类可以定义自己的field和method
val score="A"
def getscore=this.score
}
2、Scala中override 和 super 关键字
- Scala中,如果子类要覆盖父类中的一个非抽象方法,必须要使用 override 关键字;子类可以覆盖父类的 val 修饰的field,只要在子类中使用 override 关键字即可。
- override 关键字可以帮助开发者尽早的发现代码中的错误,比如, override 修饰的父类方法的方法名拼写错误。
- 此外,在子类覆盖父类方法后,如果在子类中要调用父类中被覆盖的方法,则必须要使用 super 关键字,显示的指出要调用的父类方法。
- 举例说明:
class Person1 {
private val name = "leo"
val age=50
def getName = this.name
}
class Student1 extends Person1{
private val score = "A"
//子类可以覆盖父类的 val field,使用override关键字
override
val age=30
//覆盖父类非抽象方法,必须要使用 override 关键字
override def getName = "your name is " + super.getName
}
3、Scala中isinstanceOf 和 asInstanceOf
如果实例化了子类的对象,但是将其赋予了父类类型的变量,在后续的过程中,又需要将父类类型的变量转换为子类类型的变量,应该如何做?
- 首先,需要使用 isinstanceOf 判断对象是否为指定类的对象,如果是的话,则可以使用 asInstanceOf 将对象转换为指定类型;
- 注意: p.isinstanceOf[XX] 判断 p 是否为 XX 对象的实例;p.asInstanceOf[XX] 把 p 转换成 XX 对象的实例
- 注意:如果没有用 isinstanceOf 先判断对象是否为指定类的实例,就直接用 asInstanceOf 转换,则可能会抛出异常;
- 注意:如果对象是 null,则 isinstanceOf 一定返回 false, asInstanceOf 一定返回 null;
- Scala与java类型检查和转换
Scala |
Java |
obj.isinstanceOf[C] |
obj instanceof C |
obj.asInstanceOf[C] |
(C)obj |
classOf[C] |
C.class |
- 举例说明:
package cn.itcast.extends_demo
class Person3 {}
class Student3 extends Person3
object Student3{
def main (args: Array[String] ) {
val p: Person3 = new Student3
var s: Student3 = null
//如果对象是 null,则 isinstanceOf 一定返回 false
println (s.isinstanceOf[Student3])
// 判断 p 是否为 Student3 对象的实例
if (p.isinstanceOf[Student3] ) {
//把 p 转换成 Student3 对象的实例
s = p.asInstanceOf[Student3]
}
println (s.isinstanceOf[Student3] )
}
}
4、Scala中getClass 和 classOf
- isinstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象;
- 如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 了;
- p.getClass 可以精确地获取对象的类,classOf[XX] 可以精确的获取类,然后使用 == 操作符即可判断;
- 举例说明:
package cn.itcast.extends_demo
class Person4 {}
class Student4 extends Person4
object Student4{
def main(args: Array[String]) {
val p:Person4=new Student4
//判断p是否为Person4类的实例
println(p.isinstanceOf[Person4])//true
//判断p的类型是否为Person4类
println(p.getClass == classOf[Person4])//false
//判断p的类型是否为Student4类
println(p.getClass == classOf[Student4])//true
}
}
5、Scala中使用模式匹配进行类型判断
- 在实际的开发中,比如 spark 源码中,大量的地方使用了模式匹配的语法进行类型的判断,这种方式更加地简洁明了,而且代码的可维护性和可扩展性也非常高;
- 使用模式匹配,功能性上来说,与 isinstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断。
- 等同于 Java 中的 switch case 语法;
- 举例说明:
package cn.itcast.extends_demo
class Person5 {}
class Student5 extends Person5
object Student5{
def main(args: Array[String]) {
val p:Person5=new Student5
p match {
// 匹配是否为Person类或其子类对象
case per:Person5 => println("This is a Person5's Object!")
// 匹配所有剩余情况
case _ =>println("UnkNown type!")
}
}
}
6、Scala中protected
- 跟 Java 一样,Scala 中同样可使用 protected 关键字来修饰 field 和 method。在子类中,可直接访问父类的 field 和 method,而不需要使用 super 关键字;
- 举例说明:
package cn.itcast.extends_demo
class Person6{
protected var name:String="tom"
protected[this] var hobby:String ="game"
protecteddef sayBye=println("再见...")
}
class Student6 extends Person6{
//父类使用protected 关键字来修饰 field可以直接访问
def sayHello =println("Hello "+name)
//父类使用protected 关键字来修饰method可以直接访问
def sayByeBye=sayBye
def makeFriends(s:Student6)={
println("My hobby is "+hobby+", your hobby is UnKNown")
}
}
object Student6{
def main(args: Array[String]) {
val s:Student6=new Student6
s.sayHello
s.makeFriends(s)
s.sayByeBye
}
}
7、Scala中调用父类的constructor
- Scala中,每个类都可以有一个主constructor和任意多个辅助constructor,而且每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码;因此子类的辅助constructor是一定不可能直接调用父类的constructor的;
- 只能在子类的主constructor中调用父类的constructor。
- 如果父类的构造函数已经定义过的 field,比如name和age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的field,且要求一定要使用 override 关键字。
- 举例说明:
package cn.itcast.extends_demo
class Person7(val name:String,val age:Int){
var score :Double=0.0
var address:String="beijing"
def this(name:String,score:Double)={
//每个辅助constructor的第一行都必须调用其他辅助constructor或者主constructor代码
//主constructor代码
this(name,30)
this.score=score
}
//其他辅助constructor
def this(name:String,address:String)={
this(name,100.0)
this.address=address
}
}
class Student7(name:String,score:Double) extends Person7(name,score)
8、Scala中抽象类
- 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法;
- 一个类中,如果含有一个抽象方法或抽象field,就必须使用abstract将类声明为抽象类,该类是不可以被实例化的;
- 在子类中覆盖抽象类的抽象方法时,可以不加override关键字;
- 举例说明:
package cn.itcast.extends_demo
abstract class Person9(val name:String) {
//必须指出返回类型,不然默认返回为Unit
def sayHello:String
def sayBye:String
}
classStudent9(name:String) extends Person9(name){
//必须指出返回类型,不然默认
def sayHello: String = "Hello,"+name
def sayBye: String ="Bye,"+name
}
object Student9{
def main(args: Array[String]) {
val s = new Student9("tom")
println(s.sayHello)
println(s.sayBye)
}
}
9、Scala中抽象field
- 如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field;
- 举例说明:
package cn.itcast.extends_demo
abstract class Person10 (val name:String){
//抽象fields
val age:Int
}
class Student10(name: String) extends Person10(name) {
val age: Int = 50
}
11、scala当中的特质trait
1、将trait作为接口使用
- Scala中的trait是一种特殊的概念;
- 首先先将trait作为接口使用,此时的trait就与Java中的接口 (interface)非常类似;
- 在trait中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可;
- 类可以使用extends关键字继承trait,注意,这里不是 implement,而是extends ,在Scala中没有 implement 的概念,无论继承类还是trait,统一都是 extends;
- 类继承后,必须实现其中的抽象方法,实现时,不需要使用 override 关键字;
- Scala不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可。
- 举例说明:
- Scala中的trait不仅可以定义抽象方法,还可以定义具体的方法,此时 trait 更像是包含了通用方法的工具,可以认为trait还包含了类的功能。
- 举例说明:
- Scala 中的 trait 可以定义具体的 field,此时继承 trait 的子类就自动获得了 trait 中定义的 field;
- 但是这种获取 field 的方式与继承 class 的是不同的。 如果是继承 class 获取的 field ,实际上还是定义在父类中的;而继承 trait获取的 field,就直接被添加到子类中了。
- 举例说明:
package cn.itcast.triat
trait HelloTrait {
def sayHello(): Unit
}
trait MakeFriendsTrait {
def makeFriends(c: Children): Unit
}
//多重继承 trait
class Children(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable{
def sayHello() =println("Hello, " + this.name)
def makeFriends(c: Children) = println("Hello, my name is " + this.name + ", your name is " + c.name)
}
object Children{
def main(args: Array[String]) {
val c1=new Children("tom")
val c2=new Children("jim")
c1.sayHello()//Hello, tom
c1.makeFriends(c2)//Hello, my name is tom, your name is jim
}
}
2、在trait中定义具体的方法
package cn.itcast.triat
/**
* 比如 trait 中可以包含很多子类都通用的方法,例如打印日志或其他工具方法等等。
* spark就使用trait定义了通用的日志打印方法;
*/
trait Logger {
def log(message: String): Unit = println(message)
}
class PersonForLog(val name: String) extends Logger {
def makeFriends(other: PersonForLog) = {
println("Hello, " + other.name + "! My name is " + this.name + ", I miss you!!")
this.log("makeFriends method is invoked with parameter PersonForLog[name = " + other.name + "]")
}
}
object PersonForLog{
def main(args: Array[String]) {
val p1=new PersonForLog("jack")
val p2=new PersonForLog("rose")
p1.makeFriends(p2)
//Hello, rose! My name is jack, I miss you!!
//makeFriens method is invoked with parameter PersonForLog[name = rose]
}
}
3、在trait中定义具体field
package cn.itcast.triat
trait PersonForField {
val age:Int=50
}
//继承 trait 获取的field直接被添加到子类中
class StudentForField(val name: String) extends PersonForField {
def sayHello = println("Hi, I'm " + this.name + ", my age is "+ age)
}
object StudentForField{
def main(args: Array[String]) {
val s=new StudentForField("tom")
s.sayHello
}
}
4、在trait中定义抽象field
- Scala中的trait也能定义抽象field, 而trait中的具体方法也能基于抽象field编写;
- 继承trait的类,则必须覆盖抽象field,提供具体的值;
- 举例说明:
package cn.itcast.triat
trait SayHelloTrait {
val msg:String
def sayHello(name: String) = println(msg + ", " + name)
}
class PersonForAbstractField(val name: String) extends SayHelloTrait {
//必须覆盖抽象 field
val msg = "Hello"
def makeFriends(other: PersonForAbstractField) = {
this.sayHello(other.name)
println("I'm " + this.name + ", I want to make friends with you!!")
}
}
object PersonForAbstractField{
def main(args: Array[String]) {
val p1=new PersonForAbstractField("Tom")
val p2=new PersonForAbstractField("Rose")
p1.makeFriends(p2)
}
}
5、在实例对象指定混入某个trait
- 可在创建类的对象时,为该对象指定混入某个trait,且只有混入了trait的对象才具有trait中的方法,而其他该类的对象则没有;
- 在创建对象时,使用 with 关键字指定混入某个 trait;
- 举例说明:
- Scala中支持让类继承多个trait后,可依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法,在最后都依次执行 super 关键字即可;
- 类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条;
- 这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现;
- 案例说明:
package cn.itcast.triat
trait LoggedTrait {
// 该方法为实现的具体方法
def log(msg: String) = {}
}
trait MyLogger extends LoggedTrait{
// 覆盖 log() 方法
override def log(msg: String) = println("log: " + msg)
}
class PersonForMixTraitMethod(val name: String) extends LoggedTrait {
def sayHello = {
println("Hi, I'm " + this.name)
log("sayHello method is invoked!")
}
}
object PersonForMixTraitMethod{
def main(args: Array[String]) {
val tom= new PersonForMixTraitMethod("Tom").sayHello //结果为:Hi, I'm Tom
// 使用 with 关键字,指定混入MyLogger trait
val rose = new PersonForMixTraitMethod("Rose") with MyLogger
rose.sayHello
// 结果为: Hi, I'm Rose
// 结果为: log: sayHello method is invoked!
}
}
6、trait 调用链
package cn.itcast.triat
trait HandlerTrait {
def handle(data: String) = {println("last one")}
}
trait DataValidHandlerTrait extends HandlerTrait {
override def handle(data: String) = {
println("check data: " + data)
super.handle(data)
}
}
trait SignatureValidHandlerTrait extends HandlerTrait {
override def handle(data: String) = {
println("check signature: " + data)
super.handle(data)
}
}
class PersonForRespLine(val name: String) extends SignatureValidHandlerTrait with DataValidHandlerTrait {
def sayHello = {
println("Hello, " + this.name)
this.handle(this.name)
}
}
object PersonForRespLine{
def main(args: Array[String]) {
val p=new PersonForRespLine("tom")
p.sayHello
//执行结果:
// Hello, tom
// check data: tom
// check signature: tom
// last one
}
}
7、混合使用 trait 的具体方法和抽象方法
- 在 trait 中,可以混合使用具体方法和抽象方法;
- 可以让具体方法依赖于抽象方法,而抽象方法则可放到继承 trait的子类中去实现;
- 这种 trait 特性,其实就是设计模式中的模板设计模式的体现;
- 举例说明:
package cn.itcast.triat
trait ValidTrait {
//抽象方法
def getName: String
//具体方法,具体方法的返回值依赖于抽象方法
def valid: Boolean = {"Tom".equals(this.getName)
}
}
class PersonForValid(val name: String) extends ValidTrait {
def getName: String = this.name
}
object PersonForValid{
def main(args: Array[String]): Unit = {
val person = new PersonForValid("Rose")
println(person.valid)
}
}
8、trait的构造机制
- 在Scala中,trait也是有构造代码的,即在trait中,不包含在任何方法中的代码;
- 继承了trait的子类,其构造机制如下:
- 父类的构造函数先执行, class 类必须放在最左边;多个trait从左向右依次执行;构造trait时,先构造父 trait,如果多个trait继承同一个父trait,则父trait只会构造一次;所有trait构造完毕之后,子类的构造函数最后执行。
- 举例说明:
package cn.itcast.triat
class Person_One {
println("Person's constructor!")
}
trait Logger_One {
println("Logger's constructor!")
}
trait MyLogger_One extends Logger_One {
println("MyLogger's constructor!")
}
trait TimeLogger_One extends Logger_One {
println("TimeLogger's contructor!")
}
class Student_One extends Person_One with MyLogger_One with TimeLogger_One {
println("Student's constructor!")
}
object exe_one {
def main(args: Array[String]): Unit = {
val student = new Student_One
//执行结果为:
// Person's constructor!
// Logger's constructor!
// MyLogger's constructor!
// TimeLogger's contructor!
// Student's constructor!
}
}
9、trait 继承 class
- 在Scala中trait 也可以继承 class,此时这个 class 就会成为所有继承该 trait 的子类的超级父类。
- 举例说明:
package cn.itcast.triat
class MyUtil {
def printMsg(msg: String) = println(msg)
}
trait Logger_Two extends MyUtil {
def log(msg: String) = this.printMsg("log: " + msg)
}
class Person_Three(val name: String) extends Logger_Two {
def sayHello {
this.log("Hi, I'm " + this.name)
this.printMsg("Hello, I'm " + this.name)
}
}
object Person_Three{
def main(args: Array[String]) {
val p=new Person_Three("Tom")
p.sayHello
//执行结果:
// log: Hi, I'm Tom
// Hello, I'm Tom
}
}
12、模式匹配和样例类
Scala有一个十分强大的模式匹配机制,可以应用到很多场合:如switch语句、类型检查等。并且Scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配。
1、字符匹配
def main(args: Array[String]): Unit = {
val charStr = '6'
charStr match {
case '+' => println("匹配上了加号")
case '-' => println("匹配上了减号")
case '*' => println("匹配上了乘号")
case '/' => println("匹配上了除号")
//注意。所有的模式匹配都必须最终匹配上一个值,如果没有匹配上任何值,就会报错
// case _ => println("都没有匹配上,我是默认值")
}
}
2、 匹配字符串
def main(args: Array[String]): Unit = {
val arr = Array("hadoop", "zookeeper", "spark")
val name = arr(Random.nextInt(arr.length))
name match {
case "hadoop" => println("大数据分布式存储和计算框架...")
case "zookeeper"
=> println("大数据分布式协调服务框架...")
case "spark"
=> println("大数据分布式内存计算框架...")
case _ => println("我不认识你...")
}
}
3、守卫
模式匹配当中,我们也可以通过条件进行判断
def main(args: Array[String]): Unit = {
var ch = "500"
var sign = 0
ch match {
case "+"
=>
sign = 1
case "-"
=>
sign = 2
case _ if ch.equals("500") => sign = 3
case _ => sign = 4
}
println(ch + " " + sign)
}
4、 匹配类型
注意在map当中会存在泛型擦除的情况。注意在进行非数组的类型匹配的时候,类型都会进行擦除
def main(args: Array[String]): Unit = {
//注意泛型擦除,在模式匹配当中的类型匹配中,除了Array类型以为,所有的其他的数据类型都会被擦除掉
val a = 3
val obj = if(a == 1) 1
else if(a == 2) "2"
else if(a == 3) BigInt(3)
else if(a == 4) Map("aa" -> 1)
else if(a == 5) Map(1 -> "aa")
else if(a == 6) Array(1, 2, 3)
else if(a == 7) Array("aa", 1)
else if(a == 8) Array("aa")
val r1 = obj match {
case x: Int => x
case s: String
=>
s.toInt
case BigInt
=>
-1 //不能这么匹配
case _: BigInt
=> Int.MaxValue
case m: Map[String, Int] => "Map[String, Int]类型的Map集合"
case m: Map[_, _] => "Map集合"
case a: Array[Int] => "It's an Array[Int]"
case a: Array[String] => "It's an Array[String]"
case a: Array[_] => "It's an array of something other than Int"
case _ => 0
}
println(r1 + ", " + r1.getClass.getName)
}
5、匹配数组、元组、集合
def main(args: Array[String]): Unit = {
val arr = Array(0, 3, 5)
arr match {
case Array(0, x, y) => println(x
+ " " + y)
case Array(0) => println("only 0")
//匹配数组以1 开始作为第一个元素
case Array(1, _*) => println("0 ...")
case _ => println("something else")
}
val lst = List(3, -1)
lst match {
case 0 :: Nil => println("only 0")
case x :: y :: Nil => println(s"x: $x y: $y")
case 0 :: tail => println("0 ...")
case _ => println("something else")
}
val tup = (1, 3, 7)
tup match {
case (1, x, y) => println(s"1, $x , $y")
case (_, z, 5) => println(z)
case _ => println("else")
}
}
注意:在Scala中列表要么为空(Nil表示空列表)要么是一个head元素加上一个tail列表。
9 :: List(5, 2) :: 操作符是将给定的头和尾创建一个新的列表
注意::: 操作符是右结合的,如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))
6、 样例类
样例类首先是类,除此之外它是为模式匹配而优化的类,样例类用case关键字进行声明。样例类主要是使用在我们后面的sparksql当中,通过样例类来映射我们的表当中的对象
定义形式:
case class 类型,是多例的,后面要跟构造参数。 case class Student(name:String)
case object 类型,是单例的。 case object Person
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask
//1、样例类当中的主构造器参数默认为val的
//2、样例类当中的apply和unapply方法自动生成
object CaseDemo04 extends App {
val arr
= Array(CheckTimeOutTask,
HeartBeat(12333), SubmitTask("0001", "task-0001"))
arr(2) match {
case SubmitTask(id, name) => {
println(s"$id,$name")
println(id)
println(name)
println(id+"\t"+name)
}
case HeartBeat(time) => {
println(time)
}
case CheckTimeOutTask => {
println("check")
}
}
}
7、偏函数
被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表输入参数类型,B代表返回结果类型,常用作输入模式匹配,偏函数最大的特点就是它只接受和处理其参数定义域的一个子集。
val func1: PartialFunction[String, Int] = {
case "one"
=> 1
case "two"
=> 2
// case _ => -1
}
def func2(num: String) : Int = num match {
case "one"
=> 1
case "two"
=> 2
case _ => -1
}
def main(args: Array[String]) {
println(func1("one"))
println(func2("one"))
//如果偏函数当中没有匹配上,那么就会报错,我们可以通过isDefinedAt来进行判断
// println(func1("three"))
println(func1.isDefinedAt("three"))
if(func1.isDefinedAt("three")){
println("hello world")
}else{
println("world hello")
}
}
13、scala当中的类型参数(了解)
类型参数主要就是研究scala当中的类或者scala当中的方法的泛型
1、 scala当中的类的泛型
object Demo8 {
def main(args: Array[String]): Unit = {
val result1 = new MyClass("hello",50)
val result2 = new MyClass[Any,Any]("zhangsan","Lisi");
}
}
/**
* 定义一个class类,接收两个参数,但是两个参数都是泛型,泛型的类型,会根据我们
* 创建类的实例化对象的时候,动态的传递进行动态的推断
* @param first
* @param second
* @tparam T
* @tparam B
*/
class MyClass[T,B](first:T,second:B){
println(first+","+second)
}
2、函数的泛型
object methodType{
def getMiddle[T](canshu:T) ={
canshu
}
def main(args: Array[String]): Unit = {
// 从参数类型来推断类型
println(getMiddle(Array("Bob", "had", "a", "little", "brother")).getClass.getTypeName)
//指定类型,并保存为具体的函数。
val f = getMiddle[String] _
println(f("Bob"))
}
}
3、scala当中的上下界之泛型类型的限定
在scala当中,我们可以通过上界或者下界来限定我们泛型的类型,类似于java当中的
? extends T ?号就表示我们使用的泛型,必须是T类型的子类,这种情况叫做上界
? super T ?号就表示我们使用的泛型,必须是T类型的父类,这种情况叫做下界
在scala当中上界的表示方法使用的是 “<:”, 这个符号就是表示上界,这种形式称之为泛型的上界。
在scala当中下界的表示方式使用的是 “>:”, 这个符号就是表示下界,这种形式称之为泛型的下界
1、泛型的上界限定
我们可以通过上界的限定,限定我们传入的类型必须是某个类型的子类
class Pair1[T <: Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareto(second) < 0) first else
second
}
object Main1 extends App{
override def main(args: Array[String]): Unit = {
val p = new
Pair1("hello", "brooks")
println(p.smaller)
}
}
2、泛型的下界限定
我们可以通过下界的限定,限定我们传入的类型必须是某个类型的父类
class Pair2[T](val first: T, val second: T) {
def replaceFirst[R >: T](newFirst: R) = new
Pair2[R](newFirst, second)
override def toString = "(" + first + "," + second + ")"
}
object Main2 extends App{
override def main(args: Array[String]): Unit = {
val p = new
Pair2("Nick", "Alice")
println(p)
println(p.replaceFirst("Joke"))
println(p)
}
}
在Java中,T同时是A和B的子类型,称之为多界,形式如:<T extends A & B>。
在Scala中,对上界和下界不能有多个,但是可以使用混合类型,如:[T <: A with B]。
在Java中,不支持下界的多界形式。如:<T super A & B>这是不支持的。
在Scala中,对复合类型依然可以使用下界,如:[T >: A with B]。
4、scala当中的视图界定
说白了就是将我们的泛型转化成了具体的类型
在Scala中,如果你想标记某一个泛型可以隐式的转换为另一个泛型,可以使用:[T <% Comparable[T]],由于Scala的Int类型没有实现Comparable接口,所以我们需要将Int类型隐式的转换为RichInt类型,比如:
我们如果需要比较两个值的大小,那么我们的两个值必须是Comparable的子类,那么我们可以使用泛型 T <% Comparable 来限制我们泛型必须是Comparable的子类,并且我们的泛型在执行真正比较的方法的时候,会根据我们传入的类型,自动推断,进行隐式的转换,例如我们传入4,2 进行比较,那么我们会将4, 2 这两个类型做自动推断,转换成真正的RichInt类型然后再继续进行比较
/**
* 使用 <% 来实现我们类型的隐式转换
* @param first
* @param second
* @tparam T
*/
class Pair3[T <% Comparable[T]](val first: T, val second: T) {
def smaller = if (first.compareto(second) < 0) first else
second
override def toString = "(" + first + "," + second + ")"
}
object Main3 extends App {
val p = new Pair3(4, 2)
println(p.smaller)
}
5、scala当中的协变,逆变和非变
协变和逆变主要是用来解决参数化类型的泛化问题。Scala的协变与逆变是非常有特色的,完全解决了Java中泛型的一大缺憾;举例来说,Java中,如果有 A是 B的子类,但 Card[A] 却不是 Card[B] 的子类;而 Scala 中,只要灵活使用协变与逆变,就可以解决此类 Java 泛型问题;
由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?Java中这种情况下是不可泛化的,然而Scala提供了三个选择,即协变(“+”)、逆变(“-”)和非变。
下面说一下三种情况的含义,首先假设有参数化特征Queue,那它可以有如下三种定义。
(1) trait Queue[T] {}
这是非变情况。这种情况下,当类型B是类型A的子类型,则Queue[B]与Queue[A]没有任何从属关系,这种情况是和Java一样的。
(2) trait Queue[+T]
{}
这是协变情况。这种情况下,当类型B是类型A的子类型,则Queue[B]也可以认为是Queue[A]的子类型,即Queue[B]可以泛化为Queue[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变。
(3) trait Queue[-T] {}
这是逆变情况。这种情况下,当类型B是类型A的子类型,则Queue[A]反过来可以认为是Queue[B]的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变。
协变、逆变、非变总结
- C[+T]:如果A是B的子类,那么C[A]是C[B]的子类。
- C[-T]:如果A是B的子类,那么C[B]是C[A]的子类。
- C[T]: 无论A和B是什么关系,C[A]和C[B]没有从属关系。
class Anmial
class Dog extends Anmial
协变
List[Dog] extends List[Anmial]
逆变
List[Anmial] extends List[Dog]
不变
List[Dog] = List[Dog]
案例
package cn.itcast.scala.enhance.covariance
class Super
class Sub extends Super
//协变
class Temp1[+A](title: String)
//逆变
class Temp2[-A](title: String)
//非变
class Temp3[A](title: String)
object Covariance_demo{
def main(args: Array[String]) {
//支持协变 Temp1[Sub]还是Temp1[Super]的子类
val t1: Temp1[Super] = new Temp1[Sub]("hello scala!!!")
//支持逆变 Temp1[Super]是Temp1[Sub]的子类
val t2: Temp2[Sub] = new Temp2[Super]("hello scala!!!")
//支持非变 Temp3[Super]与Temp3[Sub]没有从属关系,如下代码会报错
//val t3: Temp3[Sub] = new Temp3[Super]("hello scala!!!")
//val t4: Temp3[Super] = new Temp3[Sub]("hello scala!!!")
println(t1.toString)
println(t2.toString)
}
}
14、scala当中的Actor并发编程
1、课程目标
目标一:熟悉Scala Actor并发编程
目标二:为学习Akka做准备
注:Scala Actor是scala 2.10.x版本及以前版本的Actor。
Scala在2.11.x版本中将Akka加入其中,作为其默认的Actor,老版本的Actor已经废弃。
2、scala当中的Actor介绍
①Scala中的并发编程思想与Java中的并发编程思想完全不一样,Scala中的Actor是一种不共享数据,依赖于消息传递的一种并发编程模式, 避免了死锁、资源争夺等情况。在具体实现的过程中,Scala中的Actor会不断的循环自己的邮箱,并通过receive偏函数进行消息的模式匹配并进行相应的处理。
②如果Actor A和 Actor B要相互沟通的话,首先A要给B传递一个消息,B会有一个收件箱,然后B会不断的循环自己的收件箱, 若看见A发过来的消息,B就会解析A的消息并执行,处理完之后就有可能将处理的结果通过邮件的方式发送给A
3、什么是Scala Actor
概念
Scala中的Actor能够实现并行编程的强大功能,它是基于事件模型的并发机制,Scala是运用消息的发送、接收来实现高并发的。
Actor可以看作是一个个独立的实体,他们之间是毫无关联的。但是,他们可以通过消息来通信。一个Actor收到其他Actor的信息后,它可以根据需要作出各种相应。消息的类型可以是任意的,消息的内容也可以是任意的。
java并发编程与Scala Actor编程的区别
对于Java,我们都知道它的多线程实现需要对共享资源(变量、对象等)使用synchronized 关键字进行代码块同步、对象锁互斥等等。而且,常常一大块的try…catch语句块中加上wait方法、notify方法、notifyAll方法是让人很头疼的。原因就在于Java中多数使用的是可变状态的对象资源,对这些资源进行共享来实现多线程编程的话,控制好资源竞争与防止对象状态被意外修改是非常重要的,而对象状态的不变性也是较难以保证的。
与Java的基于共享数据和锁的线程模型不同,Scala的actor包则提供了另外一种不共享任何数据、依赖消息传递的模型,从而进行并发编程。
Actor的执行顺序
3、向Actor发送消息
发送消息的方式
! |
发送异步消息,没有返回值。 |
!? |
发送同步消息,等待返回值。 |
!! |
发送异步消息,返回值是 Future[Any]。 |
注意:Future 表示一个异步操作的结果状态,可能还没有实际完成的异步任务的结果。
Any 是所有类的超类,Future[Any]的泛型是异步操作结果的类型。
4、Actor实战
注意:如果要开发scala的actor的代码,那么需要我们将scala的SDK添加到我们的项目当中去
第一个例子
怎么实现actor并发编程:
1、定义一个class或者是object继承Actor特质,注意导包import scala.actors.Actor
2、重写对应的act方法
4、当act方法执行完成,整个程序运行结束
import scala.actors.Actor
class Actor1 extends Actor{
override def act(): Unit = {
for(i <- 1 to 10){
println("actor1====="+i)
}
}
}
object Actor2 extends Actor{
override def act(): Unit = {
for(j <- 1 to 10){
println("actor2====="+j)
}
}
}
object Actor1{
def main(args: Array[String]): Unit = {
val actor = new
Actor1
actor.act()
Actor2.act()
}
}
说明:上面分别调用了两个单例对象的start()方法,他们的act()方法会被执行,相同与在java中开启了两个线程,线程的run()方法会被执行
注意:这两个Actor是并行执行的,act()方法中的for循环执行完成后actor程序就退出
第二个例子
怎么实现actor发送、接受消息
1、定义一个class或者是object继承Actor特质,注意导包import scala.actors.Actor
2、重写对应的act方法
4、通过不同发送消息的方式对actor发送消息
5、act方法中通过receive方法接受消息并进行相应的处理
import scala.actors.Actor
class MyActor2 extends Actor{
override def act(): Unit = {
receive{
case "start" => println("starting......")
// case _ =>
println("我没有匹配到任何消息")
}
}
}
object MyActor2{
def main(args: Array[String]): Unit = {
val actor = new
MyActor2
actor.start()
actor ! "start"
}
}
第三个例子
怎么实现actor可以不断地接受消息:
在act方法中可以使用while(true)的方式,不断的接受消息。
class MyActor3 extends Actor{
override def act(): Unit = {
while (true){
receive{
case "start" => println("starting")
case "stop"
=>println("stopping")
}
}
}
}
object MyActor3{
def main(args: Array[String]): Unit = {
val actor = new
MyActor3
actor.start()
actor ! "start"
actor ! "stop"
}
}
说明:在act()方法中加入了while (true) 循环,就可以不停的接收消息
注意:发送start消息和stop的消息是异步的,但是Actor接收到消息执行的过程是同步的按顺序执行
第四个例子
好处:react方式会复用线程,避免频繁的线程创建、销毁和切换。比receive更高效
注意: react 如果要反复执行消息处理,react外层要用loop,不能用while
class MyActor4 extends Actor{
override def act(): Unit = {
loop{
react{
case "start"
=> println("starting")
case "stop"
=> println("stopping")
}
}
}
}
object MyActor4{
def main(args: Array[String]): Unit = {
val actor = new
MyActor4
actor.start()
actor ! "start"
actor ! "stop"
}
}
第五个例子
结合case class样例类发送消息和接受消息
1、将消息封装在一个样例类中
2、通过匹配不同的样例类去执行不同的操作
3、Actor可以返回消息给发送方。通过sender方法向当前消息发送方返回消息
case class Asyncmessage(id:Int,message:String)
case class Syncmessage(id:Int,message:String)
case class ReplyMessage(id:Int,message:String)
class MyActor5 extends Actor{
override def act(): Unit = {
loop{
react{
case Asyncmessage(id,message) => {
println(s"$id,$message")
sender ! ReplyMessage(2,"异步有返回值的消息处理成功")
}
case Syncmessage(id,message) =>{
println(s"$id,$message")
sender ! ReplyMessage(id,"我是同步消息的返回值,等到我返回之后才能继续下一步的处理")
}
}
}
}
}
object MyActor5{
def main(args: Array[String]): Unit = {
val actor: MyActor5 = new MyActor5
actor.start()
actor ! Asyncmessage(1,"helloworld")
val asyncmessage: Future[Any] = actor !! Asyncmessage(2,"actorSend")
val apply: Any = asyncmessage.apply()
println(apply)
println("helloworld22222")
//同步阻塞消息
val syncmessage: Any = actor !? Syncmessage(3,"我是同步阻塞消息")
println(syncmessage)
}
}
练习实战
需求:
用actor并发编程写一个单机版的WordCount,将多个文件作为输入,计算完成后将多个任务汇总,得到最终的结果。
大致的思想步骤:
1、通过loop +react 方式去不断的接受消息
2、利用case class样例类去匹配对应的操作
3、其中scala中提供了文件读取的接口Source,通过调用其fromFile方法去获取文件内容
4、将每个文件的单词数量进行局部汇总,存放在一个ListBuffer中
5、最后将ListBuffer中的结果进行全局汇总。
import scala.actors.{Actor,
Future}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.{BufferedSource,
Source}
case class FileName(path: String)
case class ResultTask(mapWithWord:
Map[String, Int])
class WordCount extends Actor {
override def act(): Unit = {
loop {
react {
//使用loop + react的方式接受我们的数据
case FileName(path: String) => {
//使用Source来读取文件内容
val file: BufferedSource = Source.fromFile(path)
//获取文件所有内容
val fileContent: String = file.mkString
//
println(fileContent)
//对文件内容进行切分
val split: Array[String] = fileContent.split("\r\n")
//
println(split.toBuffer)
//对每一行进行按照空格进行切分
// val map: Array[Array[String]] = split.map(x
=> x.split(" "))
//切分之后,将数据进行压平
// val flatten: Array[String] = map.flatten
val flatten: Array[String] = split.flatMap(x => x.split(" "))
val map1: Array[(String, Int)] = flatten.map(x => (x, 1))
//
println(map1.toBuffer)
val byKey: Map[String, Array[(String, Int)]] = map1.groupBy(x => x._1)
val values: Map[String, Int] = byKey.mapValues(x =>
x.length)
sender ! ResultTask(values)
}
}
}
}
}
object WordCount {
def main(args: Array[String]): Unit = {
//申明一个变量,存放我们的结果数据
val resultTasks = new ListBuffer[ResultTask]
//申明一个set集合用于存放我们异步发送的返回消息值
val futureSet: mutable.HashSet[Future[Any]] = new mutable.HashSet[Future[Any]]()
//定义我们需要统计的数据文件路径
val files: Array[String] = Array("F:\\scala与spark课件资料教案\\2、scala第二天\\wordCount\\1.txt", "F:\\scala与spark课件资料教案\\2、scala第二天\\wordCount\\2.txt", "F:\\scala与spark课件资料教案\\2、scala第二天\\wordCount\\3.txt")
//循环遍历我们的数据文件,然后进行发送
for (f <- files) {
val count: WordCount = new WordCount
count.start();
val value: Future[Any] = count !! FileName(f)
futureSet.add(value)
}
while (futureSet.size > 0) {
//过滤我们的set集合,只取那些有值的set集合
val completeFuture: mutable.HashSet[Future[Any]] =
futureSet.filter(x => x.isSet)
for (future <- completeFuture) {
// 调用apply方法,获取到我们的future实例,实际上就是ResultTask
val futureApply: Any = future.apply()
//判断我们的结果值如果是ResultTask类型的话,那么我们就添加到我们的ListBuffer当中去,表示已经获取到了返回结果
resultTasks +=
futureApply.asInstanceOf[ResultTask]
//添加完ListBuffer之后,将set集合当中的元素减少,以便于退出while循环
futureSet -= future
}
}
println(resultTasks)
val flatten: ListBuffer[(String, Int)] = resultTasks.map(x =>
x.mapWithWord).flatten
val by: Map[String, ListBuffer[(String, Int)]] = flatten.groupBy( x
=> x._1)
println(by)
//第一个下划线表示我们累加之后的结果
// 第二个下划线表示我们集合当中每一个元组
// _2
表示元组当中第二个元素
val values: Map[String, Int] = by.mapValues(x =>
x.foldLeft(0)( _ + _._2))
for((k,v) <-
values){
println(k+"====>"+v)
}
}
}
15、scala当中的文件操作和网络请求
1、读取文件当中每一行的数据
def main(args: Array[String]): Unit = {
//注意文件的编码格式,如果编码格式不对,那么读取报错
val file: BufferedSource = Source.fromFile("F:\\scala与spark课件资料教案\\3、scala第三天\\files\\file.txt","GBK");
val lines: Iterator[String] = file.getLines()
for(line <- lines){
println(line)
}
//注意关闭文件
file.close()
}
2、读取词法单元和数字
如果想将以某个字符或某个正则表达式分开的字符成组读取,可以这么做:
def main(args: Array[String]): Unit = {
val file: BufferedSource = Source.fromFile("F:\\scala与spark课件资料教案\\3、scala第三天\\files\\file2.txt","GBK");
val split: Array[String] = file.mkString.split(" ")
println(split.mkString("\t"))
file.close()
}
3、读取网络资源、文件写入、控制台操作
1、读取网络资源
def main(args: Array[String]): Unit = {
val source: BufferedSource = Source.fromURL("http://www.baidu.com")
val string: String
= source.mkString
println(string)
source.close()
}
2、文件写入操作
def main(args: Array[String]): Unit = {
val writer = new PrintWriter("F:\\scala与spark课件资料教案\\3、scala第三天\\files\\printWriter.txt")
for(i <- 1
to 100){
writer.println(i)
writer.flush()
}
writer.close()
}
3、控制台交互操作
def main(args: Array[String]): Unit = {
//控制台交互--老API
print("请输入内容:")
val consoleLine1 = Console.readLine()
println("刚才输入的内容是:" + consoleLine1)
//控制台交互--新API
print("请输入内容(新API):")
val consoleLine2 = StdIn.readLine()
println("刚才输入的内容是:" + consoleLine2)
}
4、scala当中的序列化
@SerialVersionUID(1L)
class Person extends Serializable{
override def toString = name + ","
+ age
val name
= "Nick"
val age
= 20
}
object PersonMain extends App{
override def main(args: Array[String]): Unit = {
import java.io.{FileOutputStream, FileInputStream,
ObjectOutputStream, ObjectInputStream}
val nick = new
Person
val out = new
ObjectOutputStream(new FileOutputStream("Nick.obj"))
out.writeObject(nick)
out.close()
val in = new
ObjectInputStream(new FileInputStream("Nick.obj"))
val saveNick = in.readobject()
in.close()
println(saveNick)
}
}
5、scala当中的正则表达式
我们可以通过正则表达式匹配一个句子中所有符合匹配的内容,并输出:
def main(args: Array[String]): Unit = {
import scala.util.matching.Regex
val pattern1 = new Regex("(S|s)cala")
val pattern2 = "(S|s)cala".r
val str = "Scala
is scalable and cool"
println((pattern2 findAllIn
str).mkString(","))
}
16、隐式转换和隐式参数
隐式转换
Scala提供的隐式转换和隐式参数功能,是非常有特色的功能。是Java等编程语言所没有的功能。它可以允许你手动指定,将某种类型的对象转换成其他类型的对象或者是给一个类增加方法。通过这些功能,可以实现非常强大、特殊的功能。
Scala的隐式转换,其实最核心的就是定义隐式转换方法,即implicit conversion function。定义的隐式转换方法,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换方法的签名,在程序中使用到隐式转换方法接收的参数类型定义的对象时,会自动将其传入隐式转换方法,转换为另外一种类型的对象并返回。这就是“隐式转换”。其中所有的隐式值和隐式方法必须放到object中。
然而使用Scala的隐式转换是有一定的限制的,总结如下:
- implicit关键字只能用来修饰方法、变量(参数)。
- 隐式转换的方法在当前范围内才有效。如果隐式转换不在当前范围内定义(比如定义在另一个类中或包含在某个对象中),那么必须通过import语句将其导。
隐式参数
所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的参数,即隐式值,并注入参数。
Scala会在两个范围内查找:
- 当前作用域内可见的val或var定义的隐式变量;
- 一种是隐式参数类型的伴生对象内的隐式值;
隐式转换方法作用域与导入
(1)Scala默认会使用两种隐式转换,一种是源类型或者目标类型的伴生对象内的隐式转换方法;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换方法。
(2)如果隐式转换方法不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换方法,比如import test._。通常建议,仅仅在需要进行隐式转换的地方,用import导入隐式转换方法,这样可以缩小隐式转换方法的作用域,避免不需要的隐式转换。
隐式转换的时机
(1)当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换
(2)当方法中的参数的类型与目标类型不一致时
隐式转换和隐式参数案例
隐式转换案例一(将我们的Double类型的数据自动转换成Int类型)
object Chapter14 {
implicit def ConvertDoubletoInt(first:Double):Int= first.toInt
}
object Convert{
//导入隐式转换的方法
import Chapter14._
def main(args: Array[String]): Unit = {
val first:Int = 3.5
}
}
例如我们也可以定义猫和狗,并且让狗学会抓老鼠的技能
object CatandDog {
implicit def dogCatchMouse(dog:Dog)
= new Cat()
def main(args: Array[String]): Unit = {
val dog = new
Dog
dog.catMouse("大黄狗")
}
}
class Cat{
def catMouse(name:String): Unit ={
println(name+"catch a mouse")
}
}
class Dog{
def wangwangwang(name:String) ={
println(name+"看门汪汪汪")
}
}
隐式转换案例二(让File类具备RichFile类中的read方法)
import java.io.File
import scala.io.source
object MyPredef{
//定义隐式转换方法
implicit def file2RichFile(file: File)=new RichFile(file)
}
class RichFile(val f:File) {
def read()=Source.fromFile(f,"GBK").mkString
}
object RichFile{
def main(args: Array[String]) {
val f=new File("F:\\scala与spark课件资料教案\\3、scala第三天\\files\\file.txt")
//使用import导入隐式转换方法
import MyPredef._
//通过隐式转换,让File类具备了RichFile类中的方法
val content=f.read()
println(content)
}
}
隐式转换案例三(超人变身)
class Man(val name:String)
class SuperMan(val name: String)
{
def heat=print("超人打怪兽")
}
object SuperMan{
//隐式转换方法
implicit def man2SuperMan(man:Man)=new SuperMan(man.name)
def main(args: Array[String]) {
val hero=new
Man("hero")
//Man具备了SuperMan的方法
hero.heat
}
}
隐式转换案例四(一个类隐式转换成具有相同方法的多个类)
class A(c:C) {
def readBook(): Unit ={
println("A说:好书好书...")
}
}
class B(c:C){
def readBook(): Unit ={
println("B说:看不懂...")
}
def writeBook(): Unit ={
println("B说:不会写...")
}
}
class C
object AB{
//创建一个类的2个类的隐式转换
implicit def C2A(c:C)=new A(c)
implicit def C2B(c:C)=new B(c)
}
object B{
def main(args: Array[String]) {
//导包
//1. import AB._ 会将AB类下的所有隐式转换导进来
//2. import AB._C2A 只导入C类到A类的的隐式转换方法
//3. import AB._C2B 只导入C类到B类的的隐式转换方法
import AB._
val c=new C
//由于A类与B类中都有readBook(),只能导入其中一个,否则调用共同方法时代码报错
//c.readBook()
//C类可以执行B类中的writeBook()
c.writeBook()
}
}
隐式参数案例五(员工领取薪水)
object Company{
//在object中定义隐式值 注意:同一类型的隐式值只允许出现一次,否则会报错
implicit val aaa="zhangsan"
implicit val bbb=10000.00
}
class Boss {
//注意参数匹配的类型 它需要的是String类型的隐式值
def callName()(implicit name:String):String={
name+" is coming !"
}
//定义一个用implicit修饰的参数
//注意参数匹配的类型 它需要的是Double类型的隐式值
def getMoney()(implicit money:Double):String={
" 当月薪水:"+money
}
}
object Boss extends App{
//使用import导入定义好的隐式值,注意:必须先加载否则会报错
import Company._
val boss
=new Boss
println(boss.callName()+boss.getMoney())
}
17、scala编程实战
使用Akka实现一个简易版的spark通信框架
项目概述
需求
目前大多数的分布式架构底层通信都是通过RPC实现的,RPC框架非常多,比如前我们学过的Hadoop项目的RPC通信框架,但是Hadoop在设计之初就是为了运行长达数小时的批量而设计的,在某些极端的情况下,任务提交的延迟很高,所以Hadoop的RPC显得有些笨重。
Spark 的RPC是通过Akka类库实现的,Akka用Scala语言开发,基于Actor并发模型实现,Akka具有高可靠、高性能、可扩展等特点,使用Akka可以轻松实现分布式RPC功能。
Akka简介
Akka基于Actor模型,提供了一个用于构建可扩展的(scalable)、弹性的(Resilient)、快速响应的(Responsive)应用程序的平台。
Actor模型:在计算机科学领域,Actor模型是一个并行计算(Concurrent computation)模型,它把actor作为并行计算的基本元素来对待:为响应一个接收到的消息,一个actor能够自己做出一些决策,如创建更多的actor,或发送更多的消息,或者确定如何去响应接收到的下一个消息。
Actor是Akka中最核心的概念,它是一个封装了状态和行为的对象,Actor之间可以通过交换消息的方式进行通信,每个Actor都有自己的收件箱(MailBox)。通过Actor能够简化锁及线程管理,可以非常容易地开发出正确地并发程序和并行系统,Actor具有如下特性:
(1)、提供了一种高级抽象,能够简化在并发(Concurrency)/并行(Parallelism)应用场景下的编程开发
(2)、提供了异步非阻塞的、高性能的事件驱动编程模型
(3)、超级轻量级事件处理(每GB堆内存几百万Actor)
项目实现
实战一:
利用Akka的actor编程模型,实现2个进程间的通信。
架构图
重要类介绍
ActorSystem:在Akka中,ActorSystem是一个重量级的结构,他需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个ActorSystem创建很多Actor。
注意:
(1)、ActorSystem是一个进程中的老大,它负责创建和监督actor
(3)、actor负责通信
Actor
在Akka中,Actor负责通信,在Actor中有一些重要的生命周期方法。
(1)preStart()方法:该方法在Actor对象构造方法执行后执行,整个Actor生命周期中仅执行一次。
(2)receive()方法:该方法在Actor的preStart方法执行完成后执行,用于接收消息,会被反复执行。
具体代码
第一步:创建maven工程,导入jar包
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.11.8</scala.version>
<scala.compat.version>2.11</scala.compat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.11</artifactId>
<version>2.3.14</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_2.11</artifactId>
<version>2.3.14</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<!-- 限制jdk的编译版本插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>meta-inf/*.SF</exclude>
<exclude>meta-inf/*.DSA</exclude>
<exclude>meta-inf/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>reference.conf</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass></mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
第二步:master进程代码开发
import akka.actor.{Actor,
ActorRef, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
//todo:利用akka的actor模型实现2个进程间的通信-----Master端
class Master extends
Actor{
//构造代码块先被执行
println("master constructor invoked")
//prestart方法会在构造代码块执行后被调用,并且只被调用一次
override def preStart(): Unit = {
println("preStart method invoked")
}
//receive方法会在prestart方法执行后被调用,表示不断的接受消息
override def receive: Receive
= {
case "connect"
=>{
println("a client connected")
//master发送注册成功信息给worker
sender ! "success"
}
}
}
object Master{
def main(args: Array[String]): Unit = {
val
host = args(0)
val port = args(1)
// val host="localhost"
// val port = 8888
//准备配置文件信息
val configStr=
s"""
|akka.actor.provider =
"akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname
= "$host"
|akka.remote.netty.tcp.port =
"$port"
""".stripMargin
//配置config对象 利用ConfigFactory解析配置文件,获取配置信息
val config=ConfigFactory.parseString(configStr)
// 1、创建ActorSystem,它是整个进程中老大,它负责创建和监督actor,它是单例对象
val masteractorSystem = ActorSystem("masteractorSystem",config)
// 2、通过ActorSystem来创建master actor
val masteractor: ActorRef = masteractorSystem.actorOf(Props(new Master),"masteractor")
// 3、向master actor发送消息
masteractor ! "connect"
}
}
注意:如果使用参数传递的方式来接收参数,那么必须在idea当中配置程序运行传递的参数
第三步:worker进程代码开发
import akka.actor.{Actor,
ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config,
ConfigFactory}
//todo:利用akka中的actor实现2个进程间的通信-----Worker端
class Worker extends
Actor{
println("Worker constructor invoked")
//prestart方法会在构造代码块之后被调用,并且只会被调用一次
override def preStart(): Unit = {
println("preStart method invoked")
//获取master actor的引用
//ActorContext全局变量,可以通过在已经存在的actor中,寻找目标actor
//调用对应actorSelection方法,
// 方法需要一个path路径:1、通信协议、2、master的IP地址、3、master的端口 4、创建master actor老大 5、actor层级
val master: ActorSelection = context.actorSelection("akka.tcp://masteractorSystem@127.0.0.1:8888/user/masteractor")
//向master发送消息
master ! "connect"
}
//receive方法会在prestart方法执行后被调用,不断的接受消息
override def receive: Receive
= {
case "connect"
=>{
println("a client connected")
}
case "success"
=>{
println("注册成功")
}
}
}
object Worker{
def main(args: Array[String]): Unit = {
//定义worker的IP地址
val host=args(0)
//定义worker的端口
val port=args(1)
//准备配置文件
val configStr=
s"""
|akka.actor.provider =
"akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname
= "$host"
|akka.remote.netty.tcp.port =
"$port"
""".stripMargin
//通过configFactory来解析配置信息
val config=ConfigFactory.parseString(configStr)
// 1、创建ActorSystem,它是整个进程中的老大,它负责创建和监督actor
val workerActorSystem = ActorSystem("workerActorSystem",config)
// 2、通过actorSystem来创建 worker actor
val workerActor: ActorRef = workerActorSystem.actorOf(Props(new Worker),"workerActor")
//向worker actor发送消息
workerActor ! "connect"
}
}
实战二
使用Akka实现一个简易版的spark通信框架
需求实现逻辑
1、 启动master和worker
2、 在worker端对应的preStart方法中拿到master的引用对象,通过这个master引用向master发送注册信息,注册信息包含workerId, workCores, workMemory等信息
3、 master接受worker注册信息,保存注册信息在一个map集合当中,key为workerId,value为注册信息样例类。master将worker注册成功的信息反馈给worker端
4、 worker接受master反馈的注册成功信息,定时向master发送心跳信息。发送心跳信息,证明worker还活着
5、 master接受worker心跳信息,定时检查超时worker,并从map当中移除掉超时的worker节点信息
架构图
具体代码
① Master类
package cn.itcast.spark |
② Worker类
package cn.itcast.spark
|
③ WorkerInfo类
package cn.itcast.spark
|
④ 样例类
package cn.itcast.spark |