Scala
前言
照着这文档敲个遍,把注释也一起敲,如果只是看的话,那么就不要浪费时间了。
这些代码都是可以运行,经过测试的。
为什么要学习Scala?
目前Spark是新一代的内存型大数据计算框架,是目前大数据技术生态圈中非常主流的一门技术。
而Spark就是使用Scala编写,包括Kafka早期底层也是用Scala写的,因此为了更好的学习Spark,看懂Spark源码,那就需要掌握Scala这门语言。
因此可以说Spark的兴起带动了Scala的兴旺。
静态类型语言和动态类型语言区别
静态类型语言是在还没运行期间就提醒哪里有错误,这个是在编译期间。
动态类型语言是在运行的时候才告诉你哪里错了,这个是在运行期间。
Scala是什么?
Scala是一门多范式语言(函数式编程和面向对象编程),且是一门静态类型语言,这门语言是产生自瑞士的洛桑联邦理工学院,Scala是可扩展语言(Scalable Language)的缩写。
Scala的作者是马丁奥德斯基,这个作者还是JDK5.0和JDK8.0编译器的编写者,因此我们可以看到在JDK5.0和8.0的很多内容和Scala似乎有重合之处,比如泛型、for循环增强、自动类型转换、Lambda表达式等都是从Scala引入的特性。
官网:https://www.scala-lang.org/
在Scala,一切都是对象,数字、符号都是对象
Scala和Java的关系
Scala是运行在JVM虚拟机上的
Scala可以调用Java的类库,如Scala的数组
Scala编译之后是class文件
Scala有自己的编译器,特定的语法使用Java是编译不过的
Scala的部分语法是可以使用Java编译器进行编译
面向XX编程解释
面向过程编程:看重业务步骤
面向对象编程:业务修改成对象之间的关系进行处理
面向接口编程:业务抽象成接口规则
面向切面编程:根据业务逻辑进行动态组织,假如从这里过的都要记录下姓名
面向命令式编程:解决问题的步骤
面向函数式编程:关系数据的映射、数学逻辑的运算
特点
1.运行在JVM和Javascript之上的语言
Scala不单是使用了JVM的高性能以及最优化性,连Java丰富的工具和类生态系统也为其所用,不过Scala并不只是运行在JVM之上,Scala-Js在让其在JavaScript的世界也可以运行。
Scala-js官网:http://www.scala-js.org/
2.静态类型语言
在Scala中静态类型是构建健壮应用系统的一个工具,Scala修正了Java类型系统中的一些缺陷,此外通过类型推导也免除了大量的冗余代码
3.混合式编程
Scala支持面向对象编程,Scala引入特质(trait),改进了Java的对象模型,trait能使用混合结构,简洁的实现新的类型,在Scala一切都是对象,符号、数组都是对象。
Scala完全支持函数式编程,函数式编程已经视为解决并发大数据以及代码正确性问题的最佳工具,使用不可变值,被使用一等公民的函数,无副作用的函数,高阶函数及函数集合。
4.复杂的类型系统
Scala对Java类型系统进行了扩展,提供了更灵活的泛型以及一些有助于提高代码的改进。
5.简洁优雅灵活的语法
Java冗长的表达式不见了。
6.可扩展框架
能够编写出简短的解释性脚本
使用特质实现的混合结构
HelloWorld.scala
object HelloWorld { //def 就是definition def main(args: Array[String]): Unit = { println("Scala Sao气冲天") } } |
Scala执行流程图
Scala数据类型
小结
在Scala中所有类型所有一个统一的引用类型Any,这是Scala的特点。
Null是所有引用类型的子类型
Nothing是有类型的子类型,通常作为没有正常返回值的返回类型
Unit相当于Java的void
Nil是空集合的意思
Scala没有++、--操作符,用+=、-=。如:var i = 0; i += 3
在Scala中 _ 是通用符,相当于Java的*,也可以作为替代符,用处很广
Scala不支持三元表达式
Scala可以在任意的地方使用import,Java只能规定在类的上方
Scala声明变量
/**
* Scala声明变量
* 语法:val / var 变量名 [: 变量类型] = 变量值
* @author AimSpeed
*/ object VariableDemo {
def main(args: Array[String]): Unit = {
val i = 1
//val声明的变量是不可变的,但是内容可变,就是引用地址不可变
//i = 111
//var 声明的变量是可变的
var j = 10
j = 11
//官方推荐使用val,那样内存的指向就不用跳来跳去,速度更快,特别是在大数据的场景下
//声明的变量的时候可以不指定类型,因为Scala会自动根据数据进行类型推导
val name = "AAA"
val name2:String = "BBB"
} |
条件判断表达式
/** 判断
* scala认为三元表达式不好,认为用if else就够了,所以就不支持三元表达式
* @author AimSpeed
*/
object ConditionDemo {
def main(args: Array[String]): Unit = {
val x = 1
//判断,x是否大于0,是就返回1,否则返回-1
val y = if(x > 0) 1 else -1
println(y)
//Scala支持类型推导,如果不指定类型的话,可以根据返回值进行推导,但是如果结果数据是指定了类型,那么返回的结果类型也要一致
//val z:Int = if(x > 0) 1 else "error"
val z = if(x > 0) 1 else "error"
//如果缺失是else,相当于if(x > 2) 1 else (),返回会是一个空的
val m = if(x > 2) 1
println(m)
val c = if(x > 2) 1 else ()
println(c)
val k = if(x > 0) 0 else if(x >= 1) 1 else 2
println(k)
}
} |
块表达式
object BlockExpressionDemo {
def main(args: Array[String]): Unit = {
val x = 0
// 在Scala中{}包含一系列表达式,就是块
// 最后一个表达式的值就是块的值
//Scala会将最后一个值作为返回
val result = {
if(x > 0){
-1
}else if(x >= 1){
1
}else {
"error"
}
}
println(result)
}
} |
循环
for
object ForDemo {
def main(args: Array[String]): Unit = {
//forearch
for(i <- 1 to 10) {
println(i)
}
println("--------------------")
//循环数组
val arr = Array(12,3,4,5,6)
for(i <- arr){
println(i)
}
println("--------------------")
//高级循环,这里面相当于嵌套循环,循环了9次,第一次大家都不相等,第二次,i = 1 ,j = 2
for(i <- 1 to 3; j <- 1 to 3; if i != j)
print((i + "--" + j ) + " === ")
println()
println("--------------------")
//for推导,如果for循环的循环体以yield开始,则读取该循环会构建出一个集合
//每次迭代生成集合中的一个值
var list = for(i <- 1.to(10)) yield i * 10
println(list)
println("--------------------")
val list2 = for(i <- list) yield if (1 %2 == 0) i
println("list2====" + list2)
println("--------------------")
//val list3 = for(i <- list;if(i % 2 == 0) i) yield i
println(list.filter(_ % 2 == 0))
println("--------------------")
//通过脚标的方式
for(i <- 0 to list.length)
//println(list(i)) //报错下角标越界
println("--------------------")
for (i <- 0 until list.length) println(list(i))
println("--------------------")
//until 是 0 到10为止 ,不包括10
println(list.length)
println(0 until list.length)
}
} |
while
/**
* Scala提供了java一样的while和do循环,
* 和if不一样,While语句本身没有值,
* 就是整个while循环的结果都是Unit类型的()
*/
object WhileDemo{
def main(args: Array[String]): Unit = {
//Scala可以在任意的地方使用import,Java只能规定在类的上方
import scala.util.control.Breaks._
var n = 10
while(n > 0){
n -= 1
println(n)
}
println("--------------------")
n = 10
do{
n -= 1
println(n)
}while(n > 0)
println("--------------------")
// Scala并没有提供break和continue语句来退出循环,
// 如果需要break, 可以通过以下几种方式
// 1.Boolean控制
// 2.嵌套函数,从函数中return
// 3.使用Breaks对象的break方法
n = 10
breakable{
while(n > 0){
n -= 1
println(n)
if(n == 2) break;
}
}
}
} |
方法和函数
/**
* 函数和方法
* 函数可以作为参数传入到方法里
* 在Scala可以用元组来装做种类型
*
* 函数和方法的区别
* = =>
* {} ()
*
* 函数:val f1 = (x:Int,y:Int) => x * y
* f1 函数名
* x和y是参数
* x*y 是执行逻辑
*
* 方法:def m1(x:Int,y:Int):Int = x * y
* m1 是方法名
* x和y是参数
* Int 是返回值类型
* x*y 是方法的执行逻辑
*
* 方法(函数作为参数):def m2(f:Int => Int):Int = {f(3)}
*
* 在函数式编程语言中,函数是头等公民,他可以像任何其他数据类型一样被传递和操作
*
*/
object FuncWithMethod {
//定义一个函数
//func 函数名
//x 输入值
// => 后是 函数体
// x * 10函数的执行逻辑
val func = (x:Int) => x *10
//方法里面要求传入的是函数
//f函数名 输入参数是Int 返回值也是Int
def m1(f:Int => Int) : Int = {
//在方法体内调用函数
f(3)
//在方法里也可以写3*10
//但是这样业务逻辑就写死了,那样就直接传函数,要改就改函数
//因为方法可能是别人给的
}
//方法
def m2(x:Int,y:Int) : Int = x + y
//---相当于
val f2 = (a:Int,b:Int) => a + b
//方法
def m3(f:Int) {
println("m3")
}
//变长参数
def m4(args:Int*) ={
var r = 0
for (a <- args) r += a
// 方法的返回值会自动进行推导,
// 并判断方法的最后一行是否可以作为返回值,
// 多条件满足则作为返回值返回
r
}
//Lazy是懒加载,在需要的使用的时候才加载
lazy val propery = 66
//默认数据
def m5(x:Double = 22,name:String = "AAA") :Double = {
if(x == 2) x + 1.0
println("m5:" + x + "---" + name)
//可以使用return指定返回值
return x
} |
def main(args: Array[String]): Unit = {
//func 作为一个参数传入
val r = m1(func);
//最后一个会作为返回值
println(r)
val arr = Array(12,3,4,5,6,6,7)
arr(1) = 22 //内容可变,引用不可变
//x是arr循环读取时候的值 x * 5是这个匿名函数体的执行逻辑
val arr2 = arr.map(x => x *5)
val arr3 = arr.map(x => x - 1)
println(arr)
println(arr.toBuffer)
println(arr2.toBuffer)
println(arr3.toBuffer)
//_ 相当于占位符,还有很多功能,可以将方法转函数
arr.map(println(_))
//方法转函数
val f3 = m2 _
println(f3(3333,2))
m3(3)
println("m4:" + m4(1,2,3,4,5,6))
// Scala可以通过=右边表达式推断出方法的方回执类型,如果方法体需要多个表达式可以使用代码块{}
// 递归方法必须指定返回类型
// 变长参数使用*,所有参数会转换成一个seq序列
m5()
m5(2)
m5(2,"BB")
}
} |
异常
/**
* 异常
* Scala的异常工作和Java一样,但Scala没有受检一次,
* 你不需要说明函数或方法可能抛出某种异常,受检异常在编译器检查,
* java必须要声明方法所抛出的异常
*/
object ExceptionDemo {
def m1(x:Int): Unit ={
val x = 0
// throw表达式是有类型的,就是Nothing,
// Nothing是所有类型的子类型,
// 所以throw表达式可以用在需要的地方
if(x == 0) throw new IOException("异常了....")
}
def main(args: Array[String]): Unit = {
try{
m1(0)
}catch {
case x : IOException => println(x.getMessage)
}finally {
println("finally......")
}
}
} |
数据结构
Scala同时支持可变和不可变集合,可以安全并发的访问
可变包:scala.collection.mutable
不可变包:scala.collection.immutable
可变就是定义了这个集合,还可以对元素进行增删改
不可变就是定义了这个集合,就不可以对这个集合的元素进行增删改,这个可以用于并发的时候,不想别人修改你的容器内容
Scala优先采用不可变的集合(安全),所以Scala默认是导入scala.collection.immutable,要使用可变集合,就要自己去导入可变集合的包
集合主要由三大类:序列、集、映射
所有的集合都扩展自Iterable特质。
集合所有集合类,Scala都提供了可变和不可变的版本。
每个Scala集合特质或类都有一个Apply的伴生对象(类似Java静态方法),这个Apply方法可以用来构建该集合的实体,这样的设计叫做统一创建原则。
数组也有可变和不可变的,但是数组的可变和不可变是不同的类,也就是类名也不同。
在Scala中如果使用保留字或关键字的话要使用``,如:`class`
Scala默认导入:import java.lang._ 、 import scala._ 、 import Predef._
数组
/**
* 数组
*/
object ArrayDemo {
def main(args: Array[String]): Unit = {
//初始化一个定长数组
val arr = new Array[Int](10)
println(arr)
println(arr.toBuffer)
//val 是引用不可变,内容可变
arr(0) = 222
arr(1) = 344
println(arr.toBuffer)
println("---------")
//如果是new,相当于调用了apply方法,直接为数组赋值
//初始化一个长度为1的定长数组,里面有10个元素
val arr2 = Array[Int](10)
println(arr2)
println("---------")
val arr3 = Array(1,2,3,4,5)
println(arr3(2))
println("---------")
//引入可变数组
import scala.collection.mutable.ArrayBuffer
//变长数组
val ab = ArrayBuffer[Int]()
ab += 1
//多个元素
ab += (2,3,4,5,6)
//把Array内的元素追加进去
ab ++= Array(8,9)
println(ab.toBuffer)
//在数组的某个位置插入元素用insert
//在位置1 插入 55 、66,下角标从0开始
ab.insert(1,55,66)
println(ab.toBuffer)
//在角标为2的位置开始,删除后面2个元素
ab.remove(5,2)
println(ab.toBuffer)
println("---------")
//增强for循环
val arrF = Array(12,3,4,6,7,7)
for(i <- arrF) println(i)
println("=========") |
//通过角标读取
//until是前包后不包的去读取
//好用的until会生成一个Range
//reverse是将前面生成的Range反转
for(i <- (0 until arrF.length).reverse) println("^^^^^" + arrF(i))
arrF.map(print)
println("========")
arrF.map(println)
println("==========")
// _ 是一个匿名函数
arrF.map(println(_))
println("■■■■■■■■■■■■■■■■■■■■■■■■■")
//数组转换
//yield关键字是将原始的数组进行转换会产生一个新的数组,原始的数组不变
val arrN = Array(1,2,3,4,6,6,7,87)
//生成新的数组
val res = for(e <- arrN) yield e * 2
println(res.toBuffer)
//map方法更好用
//匿名函数
arrN.map((x:Int) => println(x * 10))
arrN.map(x => print(x * 10 + " "))
println("**************************88")
val arrN2 = arrN.map(_ * 2)
println(arrN2.toBuffer)
println("##############################################")
//取出偶数
val arrO = Array(1,2,3,4,5,7,8)
//x是读取arrO里面的每一个值
println(arrO.filter((x:Int) => x % 2 == 0).toBuffer)
println(arrO.filter(x => x % 2 == 0).toBuffer)
println(arrO.filter(_ % 2 == 0).toBuffer)
println("##############################################")
//大数据为什么使用Scala,就是因为表达能力强
//过滤之后在循环,循环到的每一个值 * 10
//这个时候就体现了函数的作用了,可以通过传入的函数做业务逻辑处理,等于是增强了方法
println(arrO.filter(_ % 2 == 0).map(_ * 10).toBuffer)
println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
println(arrO.sum)
println(arrO.sorted.toBuffer)
//排序后反转结果
println(arrO.sorted.reverse.toBuffer)
println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
//按照本身进行排序
println(arrO.sortBy(x => x).toBuffer)
//从大到小
println(arrO.sortWith(_ > _).toBuffer)
//取出前后两个值做比较排序
println(arrO.sortWith((x,y) => x > y).toBuffer)
println("-======================-")
arrO.foreach(x => println(x))
println("-======================-")
//数组的转换2
//数组转换不会修改原数组,会产生一个新的数组
val arrC = Array[Int](1,33,4,5,6)
val arrayBuffer = arrC.toBuffer
println(arrayBuffer)
val arrBCR = arrayBuffer.toArray
println(arrBCR)
//多维数组
val arrDim = Array.ofDim[Double](3,4)
arrDim(0)(1) = 222.22
}
} |
List
object ListDemo {
def main(args: Array[String]): Unit = {
//创建一个List
val list = List(11,23,444,5,12,3,4,5)
//将List中的每个元素乘以10后生成一个新的集合
val list2 = list.map(x => x * 10)
val list3 = list.map(_ * 10)
println(list2)
println(list3)
println("*****************************")
//将List中的偶数取出生成一个新的集合
val list4 = list.filter(x => x % 2 == 0)
val list5 = list.filter(_ % 2 ==0)
println(list4)
println(list5)
println("*****************************")
//将List排序后生成一个新的集合
val list6 = list.sorted
val list7 = list.sortWith(_ > _)
val list8 = list.sortWith((x,y) => x < y)
println(list6)
println(list7)
println(list8)
println("*****************************")
//反转顺序
val list9 = list8.reverse
val list10 = list.sorted
println(list9)
println(list10)
println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$")
//将List中的元素按照4个分成一组,形成一个大的数组,里面嵌套小的List
val it = list.grouped(4)
println(it.toBuffer)
println("%%%%%%%%%")
//将Iterator转换成List
val list11 = it.flatten.toList
println( "^^^^^^^^"+ list11)
println("*****************************")
//将多个List压成一个
/*val list12 = list11.flatten
println(list12)*/
println("(((((((((((((((((((((***************")
//先按照空格切分后压平
val arr = Array("a b c","d r t")
println(arr.toBuffer)
println(arr.flatten.toList)
val arr2 = arr.flatMap(_.split(" "))
println(arr.toBuffer)
println("*****************************")
//并行计算求和
println(list.sum)
println(list.par.sum)
println("*****************************")
//如果List为空就用Nil来表示
println(9 :: 4::2::Nil)
|
//可变序列
import scala.collection.mutable.ListBuffer
val listBuffer = ListBuffer[Int](1)
listBuffer += 2
listBuffer.append(2,3,4,5,6)
listBuffer += (7,8,8,9)
println(listBuffer)
val listBuffer2 = ListBuffer[Int](1)
listBuffer ++= listBuffer2
}
} |
Set
object SetDemo {
def main(args: Array[String]): Unit = {
//不可变
// import scala.collection.immutable.HashSet
// val set1 = new HashSet[Int]()
//将元素和set1合并成一个新的set,原有的set不变
// val set2 = set1 + 4
//set中的元素不能重复,类型要一样
// val set3 = set1 ++ Set(5,4,7)
//可变
import scala.collection.mutable.HashSet
val set3 = new HashSet[Int]()
//添加
set3 ++= Set(1,3,4)
set3.add(1)
set3 += 2
//删除
set3 -= 1
set3.remove(2)
}
} |
Map
/**
* Map映射
*/
object MapDemo {
def main(args: Array[String]): Unit = {
val m = Map("a" -> 1, "b" -> 2)
println(m("a"))
//报错,因为这个是不可变的Map
//m("a") = 2
import scala.collection.mutable.Map
val m2 = Map("a" -> 1, "b" -> 2)
//添加
m2("c") = 3
m2 += (("d",3),("e",2))
m2.put("f",3)
println(m2)
//删除
m2 -= "d"
m2.remove("e")
//修改,在原有的值上添加
m2("c") += 22
println(m2)
}
} |
元组
/**
* 元组
*/
object Tuple {
def main(args: Array[String]): Unit = {
//元组就是存储一堆不同类型的数据的容器
val t = (1,2,3,"AAA",0.3)
//方式一:取出第二个元素
val second = t._2
println(second)
//方式二:取出第一个元素
val first = t _1
// 注意:元组的下角标是从1开始,不是从0开始
// 元组可用于函数需要返回不止一个值的情况,
// 使用元组的原因就是把多个值绑在一起,以便于他们能够一起处理
println(first)
}
} |
队列
/**
* 队列操作
* 队列是一个先进先出的容器
*/
object QueueDemo {
def main(args: Array[String]): Unit = {
val q1 = new scala.collection.mutable.Queue[Int]
//添加
q1 += 1
q1 ++= List(3,4)
println(q1.toList)
//返回并从队列中是删除第一个元素
println(q1.dequeue())
println(q1.toList)
//追加多个元素
q1.enqueue(5,6,7)
println(q1.toList)
//头部和尾部
println(q1.head)
println(q1.tail)
}
} |
堆栈
/**
* Stack是一个先进后厨的结构
*/
object StackDemo {
def main(args: Array[String]): Unit = {
val s = new scala.collection.mutable.Stack[Int]()
//入栈
s.push(1)
s.push(1,2,3,4,5)
println(s.toList)
//出栈
println(s.pop())
println(s.toList)
//提取栈元素而不出栈
println(s.top)
println(s.toList)
}
} |
其他
object Other {
/**
* Unit代表没有返回值
* @param name
*/
def m2(name:String):Unit = {
println(name)
}
def main(args: Array[String]): Unit = {
val r = m2("222")
println(r)
var arr = List(List(1,2,3),List(1,2,3),List(4,3),List(0))
//聚合计算
println(arr.aggregate(0)(_+_.sum,_+_))
//多线程计算
println(arr.aggregate(10)(_+_.sum,_+_))
//求并集
val l1 = List(5,6,7,8)
val l2 = List(1,2,3,4,5)
println(l1.union(l2))
println("交集:" + l1.intersect(l2))
println("差集:" + l1.diff(l2))
//拉链操作,多的会舍弃
val arr2 = Array(1,2,3)
val arr3 = Array("a","b")
val zipR = arr2.zip(arr3).toList
println(zipR)
/**
* 在Scala中列表要么为空(Nil表示空列表),要么就是一个head元素加上一个tail列表
* 9::List(5,2) ::操作符,是将给定的头和尾创建一个新的列表 9 5 2
* 注意:::操作符号是右结合,如9::5::2::Nil == 9::(5::(2::Nil))
*/
//:::和++的作用是相同的
val l3 = l1 ::: l2
println(l3)
//在原有的列表前面添加一个元素 0
val l4 = 0 :: l1
println(l4)
val l5 = 0 :: 9 :: l1
println(l5)
// 1.向后( :+ ) 或向前 ( +: ) 追加元素到序列当中
// 2.添加( + )元素到无先后次序的集合中
// 3.用( - )移除元素
// 4.用( ++ )和( -- )和批量添加和删除元素
// 5.对于集合优先使用::和:::
// 6.修改值操作有 ( += )( ++= )( -= )( --= )
// 7.对于集合也可以使用( ++ )( & )( -- )
// 8.尽量不使用( ++: )( += )( ++= ),因为在内存弹来弹出很累的
//
val names = List("aa","bb","cc")
println(names.map(_.toUpperCase)) |
println("-------------------------")
//先按照空格切分后压平
val arrTest1 = Array("a b c","d r t")
println(arrTest1.toBuffer)
println(arrTest1.map(_.split(" ").toList).toList)
//压平就是把多个集合内的每一个集合的元素取出合并成一个
println(arrTest1.map(_.split(" ").toList).flatten.toList)
//flat应用到集合的每一个元素,对于每一个元素产出一个集合并将集合的元素串联到一起
val arrTest2 = arrTest1.flatMap(_.split(" "))
println(arrTest2.toBuffer)
println("-------------------------")
//折叠
println(List(1,2,3).reduce(_ + _))//默认就是Left
println(List(1,2,3).reduceLeft(_ + _))
println(List(1,2,3).reduceRight(_ + _))
//折叠 fold
val a = Array(1,2,3,4,5,6,7,8)
println(a.sum)
println(a.reduce(_+_))//默认是reduceLeft - (1+2)+3)+4)+5)
println(a.reduceRight(_+_))//反过来(5+4)+3)+2+1)
println("--------------------")
val list = List(1,2,3,4,5,6)
println(list.par.reduce(_+_))//并行,多少个线程由CPU决定
//10是一个初始值,这个值会加进去
println(list.fold(10)(_+_))
// Scala为了充分使用多喝CPU,提供了并行集合
// par关键字就是把集合转换成一个并行操作的集合,
// 并行集合的类型都是扩展自ParSeq、ParSet、ParMap特质类型,
// 所有特质都是ParInterable的子类型,
// 但不是Iterable的子类型,
// 所以不能将并行集合传递为预期的Iterable、Seq、Set或Map的方法,
// 可以通过ser方法将并行转换回来串行
// 并行计算,由于是开多个线程,就出现了加多个10的情况了
println(list.par.map(_))
println(list.par.fold(10)(_+_))
println(list.par.fold(0)(_+_))
println(list.par.fold(0)(_+_))
println(list.foldRight(10)(_+_))
println("--------------------")
//扫描操作
println((1 to 10).scanLeft(0)(_ + _))
//Scala的线程安全的集合
// import org.apache.commons.collections.SynchronizedPriorityQueue
// import org.apache.commons.collections.buffer.SynchronizedBuffer
// import org.apache.commons.collections.set.{SynchronizedSet, SynchronizedSortedSet}
// import org.apache.directory.api.util.SynchronizedLRUMap
// SynchronizedBuffer
// SynchronizedLRUMap
// SynchronizedPriorityQueue
// SynchronizedSet
// SynchronizedSortedSet
println("--------------------")
//Scala一切都是对象
println(1.to(10))
println(1 to 10)
}
} |
case匹配
//匹配字符串
object CaseStr extends App{
val arr = Array("hello","scala","java")
val name = arr(Random.nextInt(arr.length))
println(name)
name match {
case "hello" => println("hello")
case "scala" => println("scala")
case "java" => println("java")
}
}
//按照类型匹配
object CaseType extends App{
val arr = Array("hello",-1,-3.0,2,5.0,new CaseDemo())
val elem = arr(Random.nextInt(arr.length))
println(elem)
elem match {
case x : Int => println("Int " + x)
case y : Double if(y >= 0) => println("Double " + y)
case z : String => println("String " + z)
case _ => throw new Exception("now match exeception")
}
}
//匹配数组
object CaseArray extends App{
// val arr = Array(2,1,5)
// val arr = Array(1,1,5)
val arr = Array(0,1,5)
arr match{
case Array(1,x,y) => println(arr.toBuffer)
case Array(2,1,5) => println("22222")
case Array(0,_*) => println("0.....")
case _ => println("something")
}
}
//匹配集合
object CaseList extends App {
val list = List(0,3,111)
//Nil是Null
list match {
//0开始
case 0 :: Nil => println("only 0")
case x :: y :: Nil => println(s"x $x y: $y")
case 0 :: a => println(s"0....$a")
case _ => println("something else")
}
}
//匹配元组
object caseTuple extends App {
val tup = (1,2,3)
tup match {
case (1,x,y) => println(s"helo 123 $x , $y")
case (_,z,5) => println(z)
case _ => println("else")
}
} |
样例类
/**
* 样例类
* 在Scala中样例类是特殊的类,可用于模式匹配,case class 是多例的,后面要跟构造餐你输,case object是单例的
*/
object ExampleClass {
}
import scala.util.Random
case class SubmitTask(id:String,name:String)
// /不加参数就会显示已废弃,没有意义了
//case class SubmitTask1
case class SubmitTask1()
case class HeartBeat(id:String)
//object是到哪里,不用加参数
case object CheckTimeOutTask
// /加参数就报错了
//case object CheckTimeOutTask(id:String)
object CaseDemoObject extends App{
val arr = Array(CheckTimeOutTask,new HeartBeat("1111") ,SubmitTask("111", "222"))
arr(Random.nextInt(arr.length)) match{
case SubmitTask (id,name) => println(id + " " + name)
case HeartBeat(time) => println("=======" + time)
case CheckTimeOutTask => println(this)
}
} |
/**
* 样例类
* 在Scala中Option类型样例类,
* 用来表示可能存在或也可能不存在的值(Option的内部有Some和None),
* Some包装了某个值,None表示没有值
*
*/
object OptionDemo {
def main(args: Array[String]): Unit = {
val map = Map("a" -> 1,"b" -> 2)
//返回Option类型的,可以查看源码,有值就返回Option的Some子类,没有就返回Option的None子类
val v = map.get("a") match{
case Some(a) => a
case None => 0
}
println(v)
//如果不存在就返回0
println(map.getOrElse("c",0))
}
} |
密封类
/**
* 密封类
* 如果想让case类的所有子类都必须在声明该类相同的文件中定义
* 可以将样例类的通用超类声明为sealed,叫做密封类
* 密封就是外部的用户不能在其他文件中定义子类
*/
object SealedClassDemo {
}
sealed abstract class MyObject
//其他按文件无法调用
class SubObject extends MyObject |
偏函数
/**
* 偏函数
* 被包在花括号内,没有match的一组case语句是一个偏函数,
* 他是PartialFunction[A,B]的一个实例,A代表输入参数,B是输出参数
*
* 偏函数之所以偏,原因在于他们并不处理所有可能的输入,
* 而只输入那些能与case语句匹配的输入
* 在偏函数中只能使用case语句,而整个函数必须使用花括号{}
* 这与普通的函数字面量不同,普通的函数字面量可以使用花括号也可以使用圆括号
* 如果偏函数给调用而偏函数的输入却与所有语句都不匹配,系统会配出MatchError运行错误,
* 可以使用isDefineAt方法来测试输入和偏函数是否匹配,以避免错误
*
*/
object PartialFuncDemo {
//第一种写法:输入String类型,输出Int类型
def func1:PartialFunction[String,Int] = {
case "one" => 1
case "two" => 2
case _ => -1
}
//第二种写法:输入是string,返回类型是Int
def func2(num:String) : Int = num match{
case "one" => 1
case "two" => 2
case _ => 3
}
def main(args: Array[String]): Unit = {
println(func1("one"))
println(func2(("two")))
println(func2(("9999")))
val f :PartialFunction[String,Int] = {
case "A" => 1
case "B" => 2
}
println(f("c"))
println(f.isDefinedAt("c"))
}
} |
高阶函数
/**
* 高阶函数
* 能接受函数作为参数的函数就叫做高阶函数
*/
object HightFunc {
//函数,输入Int,输出Int,执行 输入值*输入值
val func : Int => Int = {x => x * x}
//方法1
def multiply (x : Int) : Int = x * x
//方法2
def multi = (x : Int) => {
x *x
}
def multi2() = (x:Int) => {x * x}
//参数可以是任意的Double类型的参数,并返回Double的函数,相当于函数作为返回值了
def m1 (f:(Double) => Double) = f (0.2)
//高阶函数也可以返回函数,也就是函数作为返回值
def m2(f:Double) = (x:Double) => f * x
//m2可以看成
def m3(f:Double) = {(x:Double) => f * x}
def main(args: Array[String]): Unit = {
val arr = Array(1,2,3,4,5)
val al = arr.map(func)
println(al.toBuffer)
//方法转换为函数
val f = multi2
println(f)
val f2 = multi2 _
println(f2)
val f3 = multi2()
println(f3)
println(m1(x => 5 * x))
println(m2(5))
//所谓的高阶函数就是传入的参数为函数,
//方法的返回值可以以函数作为返回值,
//将方法转换为一个新的函数,并且可以定义一个模板
val func3 = 3 * (_:Double)
val func4:(Double) => Double = 3 * _
val func2 = 3 * _
}
} |
闭包
/**
* 闭包
* 可以理解为就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来
* 闭包机制使用起来就像一个函数模板
*/
object ClosureDemo {
/**
* 匿名函数 (x:Double) => f * x 嵌套在方法m1里面
* 匿名函数(x:Double) => f * x 使用了外部变量,就是m1的f,不是全局变量
* m1返回了引用局部变量f的匿名函数
* @param f
* @return
*/
def m1(f:Double) = (x:Double) => f * x
def main(args: Array[String]): Unit = {
val f1 = m1(3)
println(f1)
println(f1(5))
// f1这个函数就叫做闭包
// 闭包由代码和代码用到的任何非局部变量定义构成
// 就是m1的结果返回的函数扔给了f1,
// 而f1这个函数里面有个x,就是这个函数要传入的值,
// 而这个时候在运行函数的逻辑运算的时候就用到了之前传入的3
//3 * 5
}
} |
柯里化
/**
* 柯里化
* 在函数式编程中,
* 接收多个参数的函数可以转化为接收单个参数,这个转换过程就叫做柯里化,
* 柯里化证明了函数需要一个值,就是将多个参数的方法,转化为函数的时候有一个默认值
*/
object CurryingFunc {
//柯里化定义方式1
def m1(x:Int)(y:Int) = x * y
//定义方式2
def m2(x:Int) = (y:Int) => x * y
def m3 = (x:Int) => {x * x}
def main(args: Array[String]): Unit = {
val func = m1(3)(_)
func (4) //填补第二个( y )
//报错了
// /m1(3,4)
val arr = Array(1,2,3,4,5)
//调用的时候变成函数了,相当于调用方法,这个方法返回的是一个函数
val al = arr.map(m3)
println(al.toBuffer)
}
} |
类
/**
* 定义类
* 在Scala中类并不用public声明,一个scala原文件可以包含多个类,
* 这里面的类都具有公有可见性,调用无参方法时可以加(),
* 要可以不加,如果方法定义中不带括号那么调用时也可以不带括号
*/
class DefClass {
}
//只能在这个包或子包下是可使用,虽然在别的子包还是可以new,但是调用不到这个类里面的变量、方法等内容了
private[defclass] class Person{
//protected [defclass] class Person{
//val修饰的变量是只读的,有getter,但是没有setter
val id = "t"
//用var修饰的变量既有getter也有setter
var name = "555"
//类私有字段,只能在类的内部使用
private var age:Int = 22
// 对象私有字段,方法权限更加严格的,
// Person类的方法只能访问到当前对象的字段
//也就没有setter和setter方法
private [this] val pet = "小强"
//在Scala中必须要初始化,如果不初始化就用_代替
private [this] var pop:String = _
def printPop:Unit = {
println(pop)
}
}
object Person{
def main(args: Array[String]): Unit = {
val p = new Person
//age在Classdef2中是访问不到的,在当前文件可以访问
//per在这和在别的地方都是访问不到的,只有类的内部可以访问
//p.id="22" //报错
p.name = "AA"
p.age = 22333 //
println("33333333333")
}
} |
构造器
主构造器
/**
* 构造器
* Scala分为主构造器和辅助构造器
* 注意:主构造器会在执行类定义中的所有语句
* 每个类都有主构造器(默认都会有一个主构造器是无参的),主构造器的参数可以直接放置在类名的后面与类交织在一起
* 跟在类后面叫做主构造器
* @param name
* @param age
*/
class Student(val name:String,val age:Int){
println("执行语句")
}
object Student{
def main(args: Array[String]): Unit = {
val s = new Student("aa",22)
}
}
//gender相当于private[this]
class People(val id:String,var name:String,gender:String,val age:Int = 18){
}
object People{
def main(args: Array[String]): Unit = {
val p = new People("123","AAA","f",20)
println(p.id)
println(p.name)
println(p.age)
//调用不了gender,因为gender在构造器中没有指定修饰val还是var,相当于private[this]
//无法修改,因为age在构造器是val
//p.age = 33
}
} |
辅构造器
/**
* 辅助构造器
* 辅助构造器需要去在第一行去调用主构造器或者已定义的辅助构造器
* 也就是说要么自身去调用主构造器要么间接的去调用
* this(参数)
* 如果构造器参数至少一个被一个方法使用了,那么就会自动升级为字段,就可以使用x.字段
*/
object AssistConstract {
}
class Cat (val id:String){
var name : String = _
/**
* 辅助构造器
* 定义构造器是没有 = =>等符号的
* @param id
*/
def this(id:String,name:String){
//辅助构造器必须要在第一行调用主构造器
this(id)
this.name = name
}
def this(id:String,name:String,age:Int){
//辅助构造器必须要在第一行调用主构造器
this(id)
this.name = name
}
def m1(age:Int) = {println(age)}
}
object Cat{
def main(args: Array[String]): Unit = {
val c1 = new Cat("123")
val c2 = new Cat("123","AAA")
println(c1.name)
println(c2.name)
}
} |
嵌套类/内部类
/**
* 嵌套类/内部类
* 在Scala中几乎可以在任何语法结构中嵌套任何语法结构
* 在函数中定义函数,在勒种定义类,Java中的内部类从属于外部类,
* Scala中的每一个实例都有一个内部类,内部类从属于实例
*/
class InteriorClass {
val name:String = "str"
class Member{
//调用外部类的属性
InteriorClass.this.name
}
}
object InteriorClass{
def main(args: Array[String]): Unit = {
println("Test")
}
} |
伴生对象
/**
* 伴生类对象
* 在Scala的类中,与类名相同的对象叫做伴生对象,
* 类和伴生对象之间可以相互调用、访问私有方法和属性
* 相当于Java的静态类
*
* 在定义类的时候想要有静态方法可以定义一个同名的 object 类名
*/
class AssociatedClass {
//不是伴生类对象。无法调用,
//Dog.CONSTANT
}
class Dog {
val id = 1
private var name = "test"
def printName(): Unit ={
println(name)
println(Dog.CONTSTNTS)
}
}
/**
* apply方法
* 通常在类的伴生对象定义apply方法,
* 当遇到类名(参数1,参数n)时,apply方法就会被调用
*/
object Dog{
/*def apply():Unit = {
println ("执行")
}*/
def apply():Dog = {
new Dog
}
//def apply(name:String) : Unit = {
def apply(name:String*) : Unit = {
println(name)
}
private val CONTSTNTS = "WWW"
def main(args: Array[String]): Unit = {
val p = new Dog
//访问私有字段
p.name = "123"
p.printName()
println("=-=======================")
//不执行apply方法
val d1 = Dog
println(d1)
println("------------")
//执行apply方法
val d2 = Dog("22222","3333333")
println(d2)
}
} |
import scala.collection.mutable.ArrayBuffer
/**
* 单例对象
* 在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到这个目的
* 1.存放工具方法和常量
* 2.高效攻陷单个不可变实例
* 3.单例模式
*/
object ObjectWithSingletonDemo {
def main(args: Array[String]): Unit = {
//说明是一个对象
val s = ObjectWithSingletonDemo
val s1 = ObjectWithSingletonDemo
println(s)
println(s1)
println(SessionFactory.sessions.toBuffer)
println(SessionFactory.getSession)
}
}
object SessionFactory{
//这里相当于java静态代码块
var counts = 5
//存放session对象
val sessions = new ArrayBuffer[Session]()
while (counts > 0){
sessions += new Session
counts -= 1
}
//在object中的方法相当于java的静态方法
def getSession:Session= {
sessions.remove(0)
}
}
class Session{
} |
提取器
/**
* 提取器
* case模式的匹配是如何匹配数据、列表、元组的,
* 这个背后是有个机制叫做提取器,
* 一个对象如果带有从对象中提取值的unapply和unapplySeq方法,apply方法接收构造参数生成对象,
* 而unapply方法接收对象,提取值,是一个反向操作
*/
object Extractor {
//unapply 返回参数较少
def unapply(in:String):Option[(String,String)]= {
val pos = in.indexOf(" ")
if(pos == -1) None else Some(in.substring(0,pos),in.substring(pos+1))
}
//unapplySeq 返回参数较多
def unapplySeq(in:String):Option[Seq[String]] = {
val pos = in.indexOf(" ")
if(pos == -1) None else Some(Seq(in.substring(0,pos),in.substring(pos+1)))
}
def main(args: Array[String]): Unit = {
//匹配
println(Extractor.unapply("Cat Dog"))
//没有匹配
println(Extractor.unapply("Cat"))
//匹配
println(unapplySeq("Cat Dog"))
}
} |
应用程序对象
/**
* 应用程序对象
* Scala程序都必须从一个对象的main方法开始,
* 可以通过扩展App特质,从而不用写main方法
*/
object ApplicationDemo extends App{
println("9999999999999")
//重命名
import java.util.{HashMap => JavaHashMap}
val map = new JavaHashMap[String,Int]()
map.put("a",1)
println(map)
//隐藏某个成员
import java.util.{HashSet => _, _}
import scala.collection.mutable._
val hashSet = new HashSet[String]()
println(hashSet)
} |
枚举
/**
* 枚举
* 在Scala没有枚举,定义扩展Enumeration的对象,
* 并以value调用初始化枚举中的所有可能值
*/
object EnumerationClass extends Enumeration {
val Red = Value(0,"Stop")//id value
val Yellow = Value(10) //id
val Green = Value("Go") // id 11
}
object EnumerationTest{
def main(args: Array[String]): Unit = {
println(EnumerationClass.Red)
println(EnumerationClass.Red.id)
println(EnumerationClass.Red.toString)
//可以发现ID是顺延的,可以看到Yellow是10,所以Green是11
println(EnumerationClass.Yellow.id)
println(EnumerationClass.Green.id)
}
} |
包对象
/**
* 包对象
*/
object PackageObj {
}
package object people{
val defaltName = "Milk"
}
package people {
// 相当于在包里面定义了大量的公有内容,
// 这个包下的类都可以去使用
class Persons {
var name = defaltName
}
}
object TestPackageObj {
def main(args: Array[String]): Unit = {
val p = new Persons()
println(p.name)
}
} |
特质、继承
abstract class Human(val youName:String) {
println("Human.....")
//子类可见
protected val age:Int = 0
protected[this] val names:String = ""
//父类没有初始化的字段,子类必须重写
var face:String
} |
/**
* Trait相当于Java的interface接口,
* 在java8的时候interface可以有实现
*/
trait Animal {
// 特质构造顺序
// 特质也有可以有构造器,
// 由此字段的初始化和其他特质的语句构成,
// 特质内的语句也会给执行
println("Animal.........")
//父类没有初始化的字段,子类必须重写
val types:String
//def run()
//重写方法,在Scala重写一个非抽象方法必须使用override
def run():Unit = {
println("animal")
}
} |
//报错,Scala和Java都支持单继承,多个的时候,trait要用with
//class Chinese extends Animal with Human {
//只有主类才能带调用超类构造器
class Chinese(name:String) extends Human(name) with Animal {
println("Chinese...")
//子类重写父类的字段
override val names:String = "Mike,Amy"
//父类没有初始化的字段,子类必须重写
override var face: String = ""
/**
* 重写Animal的run方法
*/
override def run(): Unit = {
println("run")
}
override val types: String = ""
}
object Chinese{
def main(args: Array[String]): Unit = {
val c = new Chinese("Mike")
c.run()
// 构造顺序和提取定义
// 当子类重写了父类的方法或字段后,父类有依赖这些字段或方法初始化时,就会产生问题
// 子类在初始化的时候先去初始化父类的,父类定义了这个。
// 所以建议定义成lazy
// 特质和抽象类都可以定义具体的字段,如果初始化了,子类可以不重写,
// 如果没有初始化,子类必须重写,因为如果不初始化就是抽象字段
// 构造器的执行顺序
// 1.调用超类构造器
// 2.特质构造器在丑类构造器之后
// 3.类构造器执行执行
// 4.特质从左向右被构造 - Human..... Animal.........Chinese...
// 5.如果多个特质共有一个父特质,而那个父级特质已经被构造了,则不会再次构造
// 6、所有特质构造完成,子类被构造
// 初始化特质中的字段
// 特质不能有构造器参数,每个特质都有一个无参数构造器,
// 缺少构造器参数是特质和类之间唯一的区别,
// 除此之外,特质可以具有类的所有特性,比如具体的和抽象的字段以及超类
}
} |
类型转换&类型检查
/**
* 类型转换检查
*/
object CheckType {
def main(args: Array[String]): Unit = {
println("判断:" + Dog.isInstanceOf[String])
println("转换:" + new Chinese("").asInstanceOf[Animal])
println("具体类:" + classOf[Animal])
}
} |
隐式转换
import java.io.File
import scala.io.Source
object Context{
implicit val a:String = "2222"
implicit val i = 11
}
/**
* 相当于搞一个门面,以后所有的隐式转换通过这个门面进来
* 所有的隐式值和隐式方法必须放到object当中,放到cass会报错
*/
object ImplicitClass {
def sayHi(implicit name :String = "1111") : Unit = {
println(s"hi - $name")
}
def main(args: Array[String]): Unit = {
//引入
//因为在sayHi()要求传入一个隐式值,他会去上下文去找一样类型的
import Context._
//打印222
sayHi
//打印111,没有值的时候会隐式的去取
sayHi()
//打印123,最近原则
sayHi("123")
println(1 to 10)
}
}
object MyPredef{
//将这个File转换成RichFile这个类,从而调用到read方法
implicit def fileToRichFile(f:File) = new RichFile(f)
}
//装饰类
class RichFile(val f : File){
def read() = Source.fromFile(f).mkString
} |
object RichFile{
def main(args: Array[String]): Unit = {
val f = new File("e:/word.txt")
//没有read()方法
//val contents = f.read()
//2.装饰对方法的增强 - 这种是显示的
//val content = new RichFile(f)
//引入
import MyPredef._
// 隐式的增强,会去上下文寻找输入是File类型的,
// 并在引入的上下文中有隐式转换方法,是转换成File类型,
// 转换成RichFile之后就可以调用对应的read()方法了
val contents = f.read()
println(contents)
// Scala会从哪里寻找隐式转换函数?
// 1.位于目标源或目标类型的伴生对象中的隐式函数
// 2.位于当前作用域可以以单个标识符指带的隐式函数(导包)
// 隐式转换普遍发生在三种情况下:
// 1.表达式的类型和预期类型不一致
// 2.对象访问一个不存在的成员时
// 3.对象调用某个方法,而该方法的参数声明和传入参数不匹配时
// 隐式参数一般会从两个地方找
// 1.当前作用域所有可用单个标识符指代的满足类型val或def
// 2.该值需要声明为implicit
}
}
// 放到这就报错了,
// 除非单独抽取出去一个文件
// 否则必须在引用的上文
/*
object MyPredef{
//将这个File转换成RichFile这个类,从而调用到read方法
implicit def fileToRichFile(f:File) = new RichFile(f)
}*/ |
泛型
视图界定
//视图界定,必须传一个隐式转换函数
//因为这个视图是不确定的,这个视图的具体实现是在隐式类中,相当于装饰了这个类,增强了
//[T <: Object] 上界,必须要是继承Object这个对象,T必须是Object子类
//[T <% Object[T]] 视图界定,需要一个隐式转换,
//让A转换为B类型之后就可以使用B类型的方法,这个类型必须要是能隐式转换这个类
//[T >: Object] 下界,T一定是Object的父类
//[T : Object]上下文界定,T要是我能够转换成Object,需要一个隐式值
//[+T] 协变,用于替代传入的父类的子类,让子类也能传入
class Chooser[T <% Ordered[T]] {
def choose(first:T,second:T):T = {
if(first > second) first else second
}
}
object MyPredef2{
//Ordered是接口不能new,但是可以new匿名类
implicit def girl2Ordered(g:Girl) = new Ordered[Girl]{
override def compare(that:Girl):Int = {
g.faceValue - that.faceValue
}
}
}
object Chooser{
def main(args: Array[String]): Unit = {
//导入
import MyPredef2._
val c = new Chooser[Girl]
val g1 = new Girl("anglebaby",91)
val g2 = new Girl("hantano",999)
//在这里引入就会爆粗哦,因为new的时候就要进行引用了
//import MyPredef2._
// 在choose是用不了 > 进行比较的,
// 所以在匿名类中是定义了compareTo方法,
// 使得他可以使用 > 进行比较,因为传进去的类型是不确定的
val g = c.choose(g1,g2)
println(g.name)
}
} |
上下文界定
//上下文界定,必须要传入一个隐式转换的一个值
//[T <: Object] 上界,必须要是继承Object这个对象,T必须是Object子类
//[T <% Object[T]] 视图界定,需要一个隐式转换,
//让A转换为B类型之后就可以使用B类型的方法,这个类型必须要是能隐式转换这个类
//[T >: Object] 下界,T一定是Object的父类
//[T : Object]上下文界定,T要是我能够转换成Object,需要一个隐式值
//[+T] 协变,用于替代传入的父类的子类,让子类也能传入
class Chooser2[T:Ordering] {
def choose(first:T,second:T) :T = {
val ord = implicitly [Ordering[T]]
if(ord.gt(first,second)) first else second
}
}
object MyPredef3 {
//Ordered是接口不能new但是可以new匿名类
//第一种方式
//new Ordered[Girl]{}
//第二种:声明为匿名
implicit val girl2Oordered = (g : Girl) => new Ordered[Girl]{
override def compare(that:Girl):Int = {
g.faceValue - that.faceValue
}
}
//第三种
implicit val girl2Ordering = new Ordering[Girl] {
override def compare(x: Girl, y: Girl): Int = {
x.faceValue - y.faceValue
}
}
}
object Chooser{
def main(args: Array[String]): Unit = {
import MyPredef3._
val c = new Chooser2[Girl]
val g1 = new Girl("anglebaby",90)
val g2 = new Girl("hatano",99)
//在这会报错,在new的时候就要这样
//import MyPredef._
val g = c.choose(g1,g2)
println(g.name)
}
} |
上界
//上界
//<: 要求必须实现了Comparable接口,相当于java的 T extends ?
//[T <: Object] 上界,必须要是继承Object这个对象,T必须是Object子类
//[T <% Object[T]] 视图界定,需要一个隐式转换,
//让A转换为B类型之后就可以使用B类型的方法,这个类型必须要是能隐式转换这个类
//[T >: Object] 下界,T一定是Object的父类
//[T : Object]上下文界定,T要是我能够转换成Object,需要一个隐式值
//[+T] 协变,用于替代传入的父类的子类,让子类也能传入
class Pair[T <: Comparable[T]] {
def bigger(first: T,second: T) = {
//报错无法使用
//if(first > second) first else second
if(first.compareTo(second) > 0) first else second
}
}
object Pair{
def main(args: Array[String]): Unit = {
//String实现了Comparable
val p = new Pair[String]
println(p.bigger("hadoop","java"))
//报错没有实现comparable接口
//val p = new Pair[Int]
val p1 = new Pair[Integer]
println(p1.bigger(1,2))
}
} |
其他的自己去试……
pom.xml
4.0.0
com.levi.scala
scala
1.0-SNAPSHOT
1.8
1.8
UTF-8
2.10.6
2.10.6
1.6.1
2.6.4
org.scala-lang
scala-library
${scala.version}
org.scala-lang
scala-actors
${scala.actors.version}
org.apache.spark
spark-core_2.10
${spark.version}
org.apache.hadoop
hadoop-client
${hadoop.version}
|
src/main/scala
src/test/scala
net.alchim31.maven
scala-maven-plugin
3.2.2
compile
testCompile
-make:transitive
-dependencyfile
${project.build.directory}/.scala_dependencies
org.apache.maven.plugins
maven-shade-plugin
2.4.3
package
shade
reference.conf
|
Actor
Actor是Scala2.10.x版本(测试时注意pom文件引入的版本),及以前版本的Actor。
Scala在2.11.x版本后将Akka加入其中,并且作为默认的Actor,老版本的Actor已经废弃了。
Scala的Actor能够实现并行编程的强大功能,他是基于事件模型的并发机制,Scala是运用消息(message)的发送接收实现多线程,使用Scala能够更加容易的实现多线程开发。
对比
Java内置线程模型 |
Actor |
共享数据 – 锁 |
Share Nothing(不共享任何数据) |
每个Object有一个monitor,监视多线程对共享数据的访问 |
不共享数据,Actor直接通过message通信 |
加锁的代码段,用Synchronized标识 |
|
死锁的问题 |
|
每个线程内部都是顺序执行 |
每个Actor内部都是顺序执行 |
对于Java,我们都知道他的多线实现需要对共享资源(变量、对象等),使用Synchronized关键字进行代码块同步、对象锁互斥等,而且经常一大堆的try…catch语句加上wait方法、notify方法,notifyAll方法(新版本有单独的锁对象等等),感觉有点痛苦。
原因在于Java多数使用可变状态的对象资源,对这些资源进行共享来实现多线程编程的话,资源的竞争和防止对象状态被意外修改是非常重要的,而且对象状态的不变性也难以保证。
在Scala中,通过复制不可变状态的资源(也就是对象,连函数、方法也是)的一个副本,在基于Actor的消息发送接收机制进行并发编程。
Actor执行顺序
1.调用start()方法启动Actor
2.调用start()方法后,act()方法会被执行,还有一些初始化方法
3.向Actor发送消息
Actor并发是发送消息的方式
发送消息的方式
! |
发送异步消息,没有返回值 |
!? |
发送同步消息,等待返回值 |
!! |
发送异步消息,返回Future[Any] |
示例1
import scala.actors.Actor
//actor是一种模型,不是scala独有的
class MyActor extends Actor {
override def act(): Unit = {
//不断的循环,判断是否停止是否开始
while(true){
receive{//偏函数,接收任何类型,返回一个Any
case "start" => {
println("start....")
Thread.sleep(5000)
println(Thread.currentThread().getName)
println("started")
}
case "stop" => {
println("stop ....")
Thread.sleep(5000)
println(Thread.currentThread().getName)
println("stoped")
}
//退出程序
case "exit" => exit()
}
}
}
}
object MyActor{
def main(args: Array[String]): Unit = {
val actor = new MyActor
actor.start()
//! 发送消息,没有返回值
//!? 发送消息,等待消息返回,同步(阻塞)
//!! 发送消息,有消息返回(Funture[Any]),异步
actor ! "start"
actor ! "stop"
//主线程的优先级较高
println("消息发送完成!")
actor ! "exit"
}
} |
示例2
import scala.actors.Actor
class YourActor extends Actor {
override def act(): Unit = {
loop{
react{//复用线程,比receive高效
case "start" => {
println("start....")
Thread.sleep(5000)
println(Thread.currentThread().getName)
println("started")
}
case "stop" => {
println("stop ....")
Thread.sleep(5000)
println(Thread.currentThread().getName)
println("stoped")
}
//退出程序
case "exit" => exit()
}
}
}
}
object YourActor{
def main(args: Array[String]): Unit = {
val a2 = new YourActor
a2.start()
a2 ! "start"
a2 ! "stop"
Thread.sleep(5000)
a2 ! "exit"//不退出就一直等待消息的接收
}
} |
示例3
import scala.actors.Actor
/**
* Scala Actor并发编程
* 初识Actor
* Scla在2.11.x之后的版本(包含)不在支持自带的Actor,
* 而是在2.11.x之后默认改为akka的Actor
*
* 导包的时候要注意了
*/
object MyActor1 extends Actor{
//重新act方法
def act(){
for(i <- 1 to 20){
println("actor-1 " + i)
Thread.sleep(1000)
}
}
}
object MyActor2 extends Actor{
//重新act方法
def act(){
for(i <- 1 to 20){
println("actor-2 " + i)
Thread.sleep(1000)
}
}
}
object ActorTest extends App{
//启动Actor
MyActor1.start()
MyActor2.start()
println("main")
/*
相当于java的两个线程,执行了线程的run方法
这两个线程是并行执行的,act()方法中的for循环执行完成后,这个actor程序就退出了
*/
} |
示例4(WordCount)
import scala.actors.{Actor, Future}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source
class Task extends Actor {
override def act(): Unit = {
loop{
react {
case SubmitTask(fileName) => {
val result: Map[String, Int] = Source.fromFile(fileName).getLines().flatMap(_.split(" ")).map((_,1)).toList.groupBy(_._1).mapValues(_.size)
println(result)
sender ! new ResultTask(result)
}
case StopTask => exit()
}
}
}
}
//提交任务类
case class SubmitTask(fileName:String)
//结果
case class ResultTask(result: Map[String, Int])
//停止
case object StopTask
object ActorWordCount{
def main(args: Array[String]): Unit = {
val files = Array("e:/word.txt","e:/word2.txt")
//存放响应的结果
val replaySet = new mutable.HashSet[Future[Any]]
//循环的去执行多个任务
for(i <- files){
val task = new Task()
task.start()
val result = task !! SubmitTask(i)
replaySet += result
}
val resultList = new ListBuffer[ResultTask]
//判断是否有任务
while(replaySet.size > 0){
//判断这里的的是否有数据存在,isDone也是为true,isSet为true就意味着任务完成
val toCompute = replaySet.filter(_.isSet)
//如果为空就不循环了
for (f <- toCompute){
//类型转换
val rt = f.apply().asInstanceOf[ResultTask]
resultList += rt
//取出的结果要在返回容器中删除
replaySet.remove(f)
}
//避免还没有读取完
Thread.sleep(1000)
}
//对拿到的数据进行循环,拿出对应的对象的result属性的数据,把拿出的所有List进行压平,按照这个元组的数据进行分组_._1是取出元组的第一个值进行分组,之后对MapValues进行汇总
val finalResult = resultList.map(_.result).flatten.groupBy(_._1).mapValues(x => x.foldLeft(0)(_+_._2))
println(finalResult)
Thread.sleep(5000)
}
} |
Actor小结
每一个Actor都代表一个接收方和发送方
流程:Actor-App à ActorSystem à ActorRef[Message] à Dispacher à 目标Actor
ActorSystem是进入Actor世界的切入点。
Actor内部有提供调度任务:context.system.scheduler
Actor是一个体系,一个并发编程的模型。
Actor模型:在计算机科学领域,Actor模型是一个并行计算模型,它把Actor作为并行计算的基本元素来对待,为响应一个接受到的消息,Actor能够自己做出决策,如创建更多的Actor,发送更多的消息,确定如何去响应接收下一个消息等等。
Actor特性:
1.提供了一种高级抽象,能够简化在并发/并行应用场景下的编程开发,要什么就去找什么样的Actor。
2.提供了异步非阻塞、高性能的事件编程模型
3.轻量级的事件处理(每GB堆内存几百万Actor)
Akka
首先Akka是Actor这个模型的一个实现库。
新版本的Scala使用Akka的Actor作为默认Actor。
Spark的RPC就是Akka实现的(1.6后提供Akka和Netty两种可供选择),Akka用 Scala语言开发的,基于Actor并发模型实现,Akka具有高可靠性、高扩展、高弹性、可容错性、可恢复性等特点,使用Akka可以轻松的实现分布式RPC功能。
目前大多数分布式架构底层通常都是RPC实现的,RPC框架非常多,比如Hadoop的RPC,Hadoop在设计之初就是为了运行长达数个小时的批量任务而设计的,在某些极端的情况下,任务提交的延迟很高,所以Hadoop的RPC显得有点笨重。
Akka是基于Actor模型的,提供了一个用于构建可扩展、弹性,及快速响应的应用平台。
Actor是Akka最核心的概念,它是封装了状态和行为的对象,Actor之间可以通过交换消息的方式进行通信,每个Actor都有自己的收件箱。
通过Actor能够简化锁和线程管理,可以非常容易的开发出符合需求的并发程序和并发系统。
Akka程序分析
ActorSystem是进程内的Actor的大哥大,负责创建和监控Actor
ActorSystem是单例的
Actor负责通信和处理自己的业务操作
示例1
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
class Master extends Actor {
//Actor也有自己的生命周期方法.postStop()、preStart()等
println("构造器给调用了")
override def preStart():Unit= {
println("preStart invoked - 开始之前给调用")
}
override def receive: Receive = {
case "connect" => {
println("a client connnected!")
//回应消息
sender ! "master reply"
}
case "hello" => {
println("hello")
}
}
}
//运行要输入运行参数,地址和端口号
//192.168.1.1 8888
object Master {
def main(args: Array[String]): Unit = {
//地址和端口,因为akka是分布式应用,需要知道RPC访问的地址等
val host = args(0)
val port = args(1).toInt
//配置
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port="$port"
""".stripMargin
//akka的配置可以读取文件也可以是字符串
val config = ConfigFactory.parseString(configStr)
//ActorSystem负责创建和监控下面的子Actor,他是单例的
//一般需要指定两个参数,名称 配置
val actorSystem = ActorSystem("MasterSystem",config)
//创建Actor,Akka是分布式的RPC调用框架,Actor相当于Task
//Actor具体类,在RPC调用中的名称
val master = actorSystem.actorOf(Props(new Master),"Master")
//发送消息
//master ! "hello"
//等待执行完后处理完后则退出
actorSystem.awaitTermination()
}
} |
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
class Worker(val masterHost:String,val masterPort:String) extends Actor{
//这个是选择连接的Actor对象,用于寻找和连接想要的Actor
var master : ActorSelection = _
//在开始之前先连接上去Master
override def preStart(): Unit = {
//建立Master连接
//后面加上user是必备的,这个是规定
master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
//像master发送消息
master ! "connect"
println("发送消息" + s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
}
//接收消息
override def receive: Receive = {
case "master reply" => {
println("a reply from master")
}
println("Worker接收消息")
}
}
//运行要输入运行参数,地址和端口号 Master的地址和端口号
//192.168.1.1 8889 192.168.1.1 8888
object Worker{
def main(args: Array[String]): Unit = {
//自身的这个Worker的地址和端口
val host = args(0)
val port = args(1)
//master的
val masterHost = args(2)
val masterPort = args(3)
//配置信息
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port="$port"
""".stripMargin
//配置
val config = ConfigFactory.parseString(configStr)
//ActorSystem负责创建和监控下面的子Actor,他是单例的
//一般会指定两个参数,名称和配置
val actorSystem = ActorSystem("WorkerSystem",config)
//new worker调用构造函数和preStart
actorSystem.actorOf(Props(new Worker(masterHost,masterPort)),"Worker")
//调用
actorSystem.awaitTermination()
}
} |
示例2(定时监控)
//worker的消息封装类
class WorkerInfo(val id:String,val memory:Int,val cores:Int) {
//Worker的上一次心跳时间
var lastHeartbeatTime : Long = _
} |
//消息类
trait RemoteMessage extends Serializable
//Worker -> Master
//封装Worker信息,以后要序列化,走网络需要实现RemoteMessage
case class RegisterWorker(id:String,memory:Int,cores:Int) extends RemoteMessage
//Master -> Worker
//注册成功
case class RegisteredWorker(masterUrl:String) extends RemoteMessage
//Worker -> Worker
case object SendHeartbeat extends RemoteMessage
//心跳 Worker -> Master
//需要指明那个Worker发的
case class Heartbeat(id:String)
//Master -> self
//用于判断Worker是否过期,如果Worker的上一次消息时间大于指定的超时时间就表示过期,需要删除
case object CheckTimeOutWorke
|
Master
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.collection.mutable
import scala.concurrent.duration._
/**
* 1.Worker启动后在prestart方法中与master建立连接,像master发送注册,将Worker消息通过case class封装后发送给master
* 2.master接收到worker消息后,则将worker信息保存起来,然后向worker反馈消息,注册成功
* 3.worker定期向master发送心跳,为了报活
* 4.master定期清理超时的worker
*/
class Master(val host:String,val port:String) extends Actor {
//存储访问的Worker
val idToWorker = new mutable.HashMap[String, WorkerInfo]()
//存储发送过来的Worker信息
val workers = new mutable.HashSet[WorkerInfo]()
//判定Worker的超时时间
val CEHCK_INTERVAL = 15000
//开始之前
override def preStart(): Unit = {
println("preStart invoked.....")
import context.dispatcher
//定时的去检查是否挂了
//发送CheckTimeOutWorker的消息到自身,到receive中做处理
context.system.scheduler.schedule(0 millis,CEHCK_INTERVAL millis,self,CheckTimeOutWorker)
} |
//接收消息
override def receive: Receive = {
case "connect" =>{
println("connected.....")
sender() ! "master reply"
}
//注册消息,客户端会把自身的信息带过来
case RegisterWorker(id,memory,cores) => {
//如果注册过了,则不在注册
if(!idToWorker.contains(id)){
//封装客户端的信息
val workerInfo = new WorkerInfo(id,memory,cores)
//保存信息
idToWorker(id) = workerInfo
//返回给worker并告知master的地址
sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master")
println("RegisterWorker now live:" + idToWorker.size + " " + id)
}
}
//接收到Worker的心跳
case Heartbeat(id) => {
//前提要先注册
println("HeartBeat now live:" + idToWorker + "---" + id + "---" + idToWorker.contains(id))
if(idToWorker.contains(id)){
val workerInfo = idToWorker(id)
//报活
val currentTime = System.currentTimeMillis()
//时间是当前的时间
workerInfo.lastHeartbeatTime = currentTime
println("HeartBeat now live:" + idToWorker.size)
}
}
//在preStart就有个定时器,定时的检查是否过期
case CheckTimeOutWorker => {
val currentTime = System.currentTimeMillis()
//大于超时时间就表示超时了,将这些超时的全部过滤出来
val toRemove = workers.filter(x => currentTime - x.lastHeartbeatTime > CEHCK_INTERVAL)
//删除
for (w <- toRemove){
workers -= w
idToWorker -= w.id
}
//打印还存活的数量
println("now live:" + workers.size)
}
}
} |
//运行要输入运行参数,地址和端口号
//192.168.1.1 8888
object Master {
def main(args: Array[String]): Unit = {
//地址
val host = args(0)
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
//akka配置
val config = ConfigFactory.parseString(configStr)
//ActorSystem,负责创建和监控下面的子Actor,是单例的
val actorSystem = ActorSystem("MasterSystem",config)
//创建通信的Actor
val actor = actorSystem.actorOf(Props(new Master(host,port)),"Master")
//等待
actorSystem.awaitTermination()
}
} |
Worker
import java.util.UUID
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
import scala.language.postfixOps
/**
* 一个actor连接到另一个actor需要有ip,port。actor名称
* 1.worker和master需要建立连接
* 2.拿到Master的代理对象的引用
* 3.发送消息
* 4.master向Worker反馈消息
*/
class Worker(val masterHost:String,val masterPort:String,val memory:Int,val cores:Int) extends Actor{
//连接master
var master: ActorSelection = _
//workerId
val workerId = UUID.randomUUID().toString
//心跳过时时间,心跳发送时间
val HEARTBEAT_INTERVAL = 1000
//在构造器之后执行
override def preStart():Unit = {
//建立master连接
//user是规定的
master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
//向Master发送注册信息
master ! RegisterWorker(workerId,memory,cores)
} |
//接收消息
override def receive: Receive = {
case "master reply" => {
println("a reply from master")
}
//已经注册了的反馈消息
case RegisteredWorker(masterUrl) =>{
println(masterUrl)
import context.dispatcher
//启动akak定时器,定时发送心跳
//参数1:延迟 参数2:间隔时间 参数3:接收方(self自身,又会进入receive) 参数4:消息
context.system.scheduler.schedule(0 millis,HEARTBEAT_INTERVAL millis, self,SendHeartbeat)
println("receive master RegisteredWorker")
}
//收到心跳发送消息,那么则发送心跳
case SendHeartbeat => {
master ! Heartbeat(workerId)
println("SendHeartbeat")
}
}
}
//运行要输入运行参数,地址和端口号 内存 处理器核数 Master的地址和端口号
//192.168.1.1 8889 2 2 192.168.1.1 8888
//启动2个,启动完了之后,关闭一个,注意端口问题
object Worker{
def main(args: Array[String]): Unit = {
//自身信息和master信息
val host = args(0)
val port = args(1)
val memory = args(2).toInt
val cores = args(3).toInt
val masterHost = args(4)
val masterPort = args(5)
//配置
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port="$port"
""".stripMargin
//akka配置
val config = ConfigFactory.parseString(configStr)
val actorSystem = ActorSystem("WorkerSystem",config)
//启用worker actor
actorSystem.actorOf(Props(new Worker(masterHost,masterPort,memory,cores)),"Worker")
actorSystem.awaitTermination()
}
} |
Spark为什么不用Java写而是用Scala写?难道Java不能实现?
Spark当然也可以用Java来实现,也有人说如果Spark没有使用Scala写的话,Scala可能就变得无人问津了。
但是为什么当初在写Spark那群人选择Scala呢?
这个只是代表个人观点。
有几个方面,使用过Java的人在去用Scala,都会有一个感觉,这个语言太松散了,从而代码量也少,但是这种代码意味着健壮性比Java要弱,但是Scala的快速、简洁、及在使用过程中函数可以作为参数传入给方法,方法也可以返回一个函数,似乎方法的内容都是不确定、不可控,也使得结果存在着多种可能,似乎是要通过函数去加强这个方法,而这个恰恰就是数学的多变性体现。
学过数学的都知道,数学的一切都是人们通过不断猜想,不断的传入内容去实现自己的猜想(一堆值),从而得出一个结果,但是谁也不知道这个传入的猜想(一堆值)是不是可以运行出来,想要的结果。
我们学习了Scala,用Scala去写了例子之后会发现,Scala似乎就是为了这个实现数学计算而生的,Scala的目标也是为了做计算型语言。
我们都知道Spark是一个内存型计算框架,而Scala运行在JVM,使得运行速度、稳定性等各个方面都有所保障了。
以上所述小结
这种代码太烧脑了,可读性大大的降低,且和Java代码结合在一起时,会让人发疯。