大数据框架(处理海量数据/处理实时流式数据)
一:以hadoop2.X为体系的海量数据处理框架
离线数据分析,往往分析的是N+1的数据
- Mapreduce
并行计算,分而治之
- HDFS(分布式存储数据)
- Yarn(分布式资源管理和任务调度)
缺点:
磁盘,依赖性太高(io)
shuffle过程,map将数据写入到本次磁盘,reduce通过网络的方式将map task任务产生到HDFS
- Hive 数据仓库的工具
底层调用Mapreduce
impala
- Sqoop
桥梁:RDBMS(关系型数据库)- > HDFS/Hive
HDFS/Hive -> RDBMS(关系型数据库)
- HBASE
列式Nosql数据库,大数据的分布式数据库
二:以Storm为体系的实时流式处理框架
Jstorm(Java编写)
实时数据分析 -》进行实时分析
应用场景:
电商平台: 双11大屏
实时交通监控
导航系统
三:以Spark为体系的数据处理框架
基于内存
将数据的中间结果放入到内存中(2014年递交给Apache,国内四年时间发展的非常好)
核心编程:
Spark Core:RDD(弹性分布式数据集),类似于Mapreduce
Spark SQL:Hive
Spark Streaming:Storm
高级编程:
机器学习、深度学习、人工智能
SparkGraphx
SparkMLlib
Spark on R
Flink
Spark 学习计划
第一部分:scala编程语言
第二部分:Spark Core(最重要的内容)-》 概念RDD:相当于Mapreduce
第三部分:Spark Sql:相当于Hive,支持sql语句 - 》 底层依赖的Spark Core -》依赖RDD
第四部分:Spark Streaming:相当于Storm - 》底层依赖Spark Core -》依赖RDD
注意:但是Spark Streaming不能做到实时性非常高
学习建议:
- 类比法
Scala:Java
SPark Core:mapreduce
Spark Sql:Hive
streaming: Core, 基于Core Api的封装
- 听、练、记、理解
- 听:认真听
- 练:自己敲代码、不要拷贝
- 记:笔记
- 思:思考、理解
===============第一部分:Scala编程语言========================================
一:scala语言基础
*)scala简介
1)多范式编程语言
面向对象编程+面向函数式编程
2)编程简单
Java vs Scala编程语言
代码量:10:1
3)scala语言诞生了两个大数据框架,而且非常重要的大数据框架
- spark
分布式海量数据处理框架
- kafka
分布式流平台,基于消息的发布订阅队列框架,存储数据
3)学习网站
https://www.scala-lang.org
- 官网
http://twitter.github.io/scala_school/zh_cn/
- 推特
4)版本的选择
2.11.x版本, 此处为2.11.12
备注说明:
- Spark 1.6.x版本 推荐的scala 2.10.x版本
- Spark 2.x版本 推荐的Scala 2.11.x版本
*)scala的安装
1)windows平台
*)JDK 8
*)以scala-2.11.12.zip为例
1:解压缩:D:\developer\scala-2.11.12
2:设置SCALA_HOME:D:\developer\scala-2.11.12
3:将%SCALA_HOME%\bin加入到PATH路径
4:执行:scala -version
2)linux平台
*)JDK 8
*)跟安装JDK步骤相同
*)写一个scala程序
1)scala语言是基于JVM之上的语言
*.java
编译 *.class -> JVM
*.scala
编译 *.class -> JVM
#创建scala文件
vi HelloScala.scala
#编写scala程序
object HelloScala {
def main(args: Array[String]): Unit = {
println("Hello World !!!")
}
}
#编译scala代码
scalac HelloScala.scala
#执行scala程序
scala HelloScala
*)常用开发工具
1)REPL(Read Evaluate Print Loop):命令行
2)IDE:图形开发工具
The Scala IDE (Based on Eclipse):http://scala-ide.org/
IntelliJ IDEA with Scala plugin:http://www.jetbrains.com/idea/download/
Netbeans IDE with the Scala plugin
*)scala数据类型
1)在scala中,任何数据都是对象
举例:数字1 -》是一个对象,就有方法
scala> 1.toString
res0: String = 1
2)数据类型:复习
Byte: 8位的有符号 -128~127
Short: 16位的有符号的 -32768~32767
Int: 32位的有符号的
Long: 64位的有符号的
Float: 浮点型
Double: 双精度
3)var声明变量,variable 简写,表示的变量,可以改变值
4)val声明变量, value 简写,表示的意思为值,不可变.常量
5)对于字符串来说,在scala中可以进行插值操作
scala> var str = s"Hello ${name}"
str: String = Hello Tom
6)lazy 使用
scala> lazy val i = 10
i: Int = <lazy>
scala> i
res3: Int = 10
举例:读取文件(文件不存在的话)
scala> lazy val words = scala.io.Source.fromFile("d:\\aa.txt").mkString
words: String = <lazy>
7)在scala中操作符就是方法
scala> 1.+(2)
res3: Int = 3
在scala中不支持三种操作符
++ / --/ ?:
自增 自减 三目
scala中的操作符实际上就是scala中方法的调用,只不过为了简洁期间,将方法的调用转换为中缀表达式
Scala中操作符实际上是方法。例如:
a + b
是如下方法调用的简写:
a+(b)
a 方法 b可以写成 a.方法(b)
8)给变量赋
scala> var a:String = _
a: String = null
scala> var b:Int = _
b: Int = 0
scala> var b:Double = _
b: Double = 0.0
#如果使用默认值的话,一定指明变量的类型,否则报错
String类型的默认值是null
Int类型的默认值是0
Double类型的默认值是0.0
9)Nothing表示在程序运行中产生了Exception
scala> def f = throw new Exception("Someing")
f: Nothing
如果返回值是Nothing,表示返回了异常
注意:在Scala中,定义变量可以不指定类型,因为Scala会进行类型的自动推导
*)scala的条件表达式
IF 判断来说有三种结构:
-1, IF
IF(boolean) {
}
-2, IF...ELSE...
IF(boolean) {
//TODO true do something
}
ELSE {
//TODO false do something
}
-3,IF...ELSE IF...ELSE
IF(i==1) {
//TODO true 1 something
}
ELSE IF(i==2){
//TODO false 2 something
}
ELSE {
//TODO false 0 something
}
def main(args: Array[String]): Unit = {
}
#Unit表示无返回值,相当于Java中的void
块表达式
在scala中{}中课包含一系列表达式,块中最后一个表达式的值就是块的值
*)scala的循环
For 循环
循环表达式
在JAVA中进行循环的时候
for(int i = 0; i< 10; i++)
While 循环表达式
while loop
先判断再执行
Do While loop
先执行后判断,至少执行一次
For循环:
scala> for(s <- str) println(s)
a
b
c
d
scala> val list = Array("Hadoop", "Spark", "Hive")
list: Array[String] = Array(Hadoop, Spark, Hive)
scala> for(a <- list) println(a)
#to:左闭右闭区间
scala> 1 to 10
res4: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
#Range:左闭右开区间
scala> Range(1, 10)
res5: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9)
#Range:左闭右开区间,生成1-9之间数字,其中参数2是步长
scala> Range(1, 10, 2)
res7: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)
#until:左闭右开区间
scala> 1 until 10
res6: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9)
二:scala面向函数式编程(最有特色的一部分)-》将函数作为函数的参数传递过去
(*)方法和函数的区别
1.方法:
相对于OOP来说
类class
属性:名词
方法:动词,如一个函数在类中,则该函数为方法
2.函数:
不在类中的方法,称此方法为函数
将函数作为函数的参数传递过去
3.OOP编程中
比如JAVA语言来说,方法必须在类中,不能脱离class独立存在
但是FP(函数式编程)中, 函数可以独立存在,不需要依赖类class
4.SCALA语言,既可以面向对象编程,又可以面向函数式编程,所以类和函数,对象都是“一等公民”,地位相同,都可以独立存在,不需要依赖任何类和对象
(*)如何表示一个方法:
def m(x:Int, y:Int): Int
- def:声明一个方法
- m:方法名(如果没有方法名,那么这个方法是匿名方法)
- (x:Int, y:Int):左边的参数(指明了参数的个数和类型)
- : Int:函数的返回值(返回值类型)
(*)定义一个方法
scala> def m(x:Int, y:Int):Unit = x+y
m: (x: Int, y: Int)Unit
//如果不指名返回值类型,则根据方法体进行自动推导
scala> def m2(x:Int, y:Int) = { x + y }
m2: (x: Int, y: Int)Int
//无返回值
scala> def m3(x:Int, y:Int) = { println("") }
m3: (x: Int, y: Int)Unit
//不传递参数
scala> def m5() = println(100)
m5: ()Unit
//scala中,若无参数传递,则可以不加()
scala> m5
100
//定义的时候,若不需要传递参数,则不需要加()
scala> def m6 = print(100)
m6: Unit
//若定义的时候不加(), 调用时也不能加()
注意:方法的返回值类型可以不写,编译器可以自动推断出来,但是对于递归方法,必须指定返回类型
(*)如何定义一个函数
#val: 定义一个函数
#=>: 函数里面使用=>
#省略返回值
#func: 函数名称
#(Int, Int) :函数参数列表
#=> Int:函数返回值
#<function2>:函数参数个数, 最多只能有22个,如果想使用更多的参数,使用变长参数
scala> val func = (x: Int, y:Int) => x*y
func: (Int, Int) => Int = <function2>
#不指名函数名称,则称为匿名函数
scala> (x:Int) => x*2
res7: Int => Int = <function1>
#无参函数
scala> () => 2
res8: () => Int = <function0>
#输出函数签名
scala> res8
res9: () => Int = <function0>
#调用函数
scala> res8()
res10: Int = 2
#简单写法
scala> val f2 = (x:Int, y:Int) => x * y
f2: (Int, Int) => Int = <function2>
#完整写法,
#f3:函数名称
#(Int, Int):函数参数类型
#=> Int:函数返回值类型
#{(x, y) => x * y}:函数体,(参数名+函数体)组成
scala> val f3:(Int, Int) => Int = {(x, y) => x * y}
f3: (Int, Int) => Int = <function2>
(*)scala的数组、集合、元组
1)数组
scala> val v1 = Array(1,2,3,4,5,8)
v1: Array[Int] = Array(1, 2, 3, 4, 5, 8)
#数组里面既可以放Int, 也可以放String,都继承自Any,在scala中所有类型都继承自Any
scala> val v3 = Array(1,2,3,"Tom")
v3: Array[Any] = Array(1, 2, 3, Tom)
#显式指定数据类型,不可以放其他类型数据
#动态初始化一个数组,这里的5是数组的长度,
#对于Int来说,初始化的默认值是0
#对于String来说,初始化的默认值是null
scala> val v4 = new Array(5)
v4: Array[Nothing] = Array(null, null, null, null, null)
scala> val v4 = new Array[String](5)
v4: Array[String] = Array(null, null, null, null, null)
scala> val v5 = new Array[Int](5)
v5: Array[Int] = Array(0, 0, 0, 0, 0)
#取得集合总值,在scala中是在java基础上又一次进行高度的封装,方便用户使用
scala> v5.sum
res5: Int = 23
#升序排序
scala> v5.sorted
res8: Array[Int] = Array(0, 0, 10, 12, 13)
#降序排序
scala> v5.sorted.reverse
res9: Array[Int] = Array(13, 12, 10, 0, 0)
#通过匿名函数进行排序
scala> v5.sortBy(x => x)
res10: Array[Int] = Array(0, 0, 10, 12, 13)
scala> v5.sortBy(x => -x)
res11: Array[Int] = Array(13, 12, 10, 0, 0)
2)集合
#定义一个集合
scala> val ls = List(1,2,3,4,5)
ls: List[Int] = List(1, 2, 3, 4, 5)
scala> ls.length
scala> ls.sum
scala> ls(1)
3)Map
scala> val map = Map("a"-> 1, "b"->2, "c"->3)
#指定类型
scala> val map2 = Map[String, Int]("a"-> 1, "b"->2, "c"->3)
#取得不存在的key,如果没有返回一个默认值0, 避免程序报错
scala> map.getOrElse("d", 0)
res17: Int = 0
#遍历映射中所有的键/值对偶
scala> for((k, v) <- map) {
| println(k + " " + v)
| }
a 1
b 2
c 3
4)元组
1)定义
使用一堆圆括号包含起来
2)下标
访问元组中元素下标从1开始
3)元素个数
最多22个元素
4)数据类型
任意的scala语言支持的数据类型
#一个简单的元组
scala> val t = (1, "a", 2.0, 5L)
t: (Int, String, Double, Long) = (1,a,2.0,5)
下标从1开始
元组的好处:
1:可以放多种类型数据,在java中返回多个参数,需要将参数放到一个集合或者写个model实体类,返回该实体对象,但是在scala中可以放到元组中非常方便
#map中存放很多的对偶元组
scala> val m1 = Map(("a", 100), ("b", 200))
m1: scala.collection.immutable.Map[String,Int] = Map(a -> 100, b -> 200)
(*)scala函数的参数:求值策略
1、call by value:对函数的实参求值,并只求值一次
举例:def test1(x:Int, y:Int):Int = x+x 没有用到y
2、call by name:函数的实参每次在函数体内部被调用的时候,都会进行求值
举例:def test2(x: => Int, y: => Int):Int = x + x 没有用到y
3:、一个复杂的例子
x是call by value
y是call by name
def test3(x:Int, y: => Int):Int = 1
在定义一个函数
def loop():Int = loop
分别进行调用
scala> test3(1, loop) -->正常
res31: Int = 1
scala> test3(loop, 1) -> 死循环
(*)使用Scala实现WordCount词频统计程序(单机)
#map函数使用说明
scala> val arr = Array(1,2,3,4,5,6,7,8,9)
arr: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> var arr2 = arr.map((x:Int)=> x*10)
arr2: Array[Int] = Array(10, 20, 30, 40, 50, 60, 70, 80, 90)
scala> var arr2 = arr.map(_*10)
arr2: Array[Int] = Array(10, 20, 30, 40, 50, 60, 70, 80, 90)
scala> var arr = Array("Spark Hadopp Hive", "Hive Hbase", "Sqoop Redis Hadoop")
arr: Array[String] = Array(Spark Hadopp Hive, Hive Hbase, Sqoop Redis Hadoop)
#将元素进行拆分, 拆分后每个元素("Spark Hadopp Hive")形成独立的小数组
scala> var arr2 = arr.map(x => x.split(" "))
arr2: Array[Array[String]] = Array(Array(Spark, Hadopp, Hive), Array(Hive, Hbase), Array(Sqoop, Redis, Hadoop))
#压平操作,将子数组的元素压破,flatMap=flatten+map
scala> var arr4 = arr.flatMap(x => x.split(" "))
arr4: Array[String] = Array(Spark, Hadopp, Hive, Hive, Hbase, Sqoop, Redis, Hadoop)
#将每个元素做一次计数
scala> var arr3 = arr2.map(x => (x, 1))
arr3: Array[(String, Int)] = Array((Spark,1), (Hadopp,1), (Hive,1), (Hive,1), (Hbase,1), (Sqoop,1), (Redis,1), (Hadoop,1))
scala> var arr4 = arr3.groupBy(x => (x._1))
arr4: scala.collection.immutable.Map[String,Array[(String, Int)]] =
Map(
Sqoop -> Array((Sqoop,1)),
Hbase -> Array((Hbase,1)),
Hive -> Array((Hive,1), (Hive,1)),
Hadopp -> Array((Hadopp,1)),
Spark -> Array((Spark,1)),
Redis -> Array((Redis,1)),
Hadoop -> Array((Hadoop,1))
)
#对数据进行统计
scala> val arr5 = arr4.map(x => (x._1, x._2.length))
arr5: scala.collection.immutable.Map[String,Int] = Map(Sqoop -> 1, Hbase -> 1, Hive -> 2, Hadopp -> 1, Spark -> 1, Redis -> 1, Hadoop -> 1)
(*)函数的进一步说明
#可以将函数作为方法的参数进行传递,也可以将方法作为方法的参数传递,但是程序本身会做隐形转换
scala> val arr = Array(1,2,3,5,8)
scala> val f = (x: Int) => x*x
f: Int => Int = <function1>
scala> def m(x:Int) = x*x
m: (x: Int)Int
scala> arr.map(f)
res15: Array[Int] = Array(1, 4, 9, 25, 64)
scala> arr.map(m)
res16: Array[Int] = Array(1, 4, 9, 25, 64)
#显式将方法转换为函数
scala> m _
res19: Int => Int = <function1>
#将数组的元素小写转大写
scala> var arr = Array("tom", "mary", "mike")
scala> def toUpper(str:String):String = str.toUpperCase
toUpper: (str: String)String
scala> arr.map(toUpper)
res20: Array[String] = Array(TOM, MARY, MIKE)
#简化的写法,这时候传递的是简化版的函数,而非方法
scala> def m(x:Int) = x*x
m: (x: Int)Int
scala> arr.map(x => m(x))
res24: Array[Int] = Array(1, 9, 16, 36, 49)
scala> arr.map(m(_))
res25: Array[Int] = Array(1, 9, 16, 36, 49)
#根据自己的业务实现一个过滤器
scala> def m1(x:Int,y:Int) = x*y
m1: (x: Int, y: Int)Int
scala> m1 _
res26: (Int, Int) => Int = <function2>
scala> def ft(x:Int):Boolean = x%2 ==0
ft: (x: Int)Boolean
scala> arr.filter(ft(_))
res27: Array[Int] = Array(4, 6)
(*)函数的默认值参数
函数的参数默认值,建议有默认值的参数放到参数列表的最后
def sayHello(name:String, msg:String="Hi !"): Unit = {
println(msg + " " + name)
}
sayHello("Tom", "Hello") ->正常
sayHello("Tom") ->正常,如果没有传递msg参数则使用默认参数
调用函数式为可以指定函数的参数进行赋值,指定的名称要与函数声明时参数名称相同
def sayHello2(name:String = "Tom"): Unit = {
println("Hello , " + name)
}
sayHello2() -> 正常
sayHello2 -> Error
sayHello2(name = "Mike") ->正常
(*)函数的可变参数
# 定义变长参数的函数
# String* 表示接受一系列的String类型的值,类似于java语言的可变参数
# 内部来说:变长函数的类型实际上是一个数组,比如String*,Array[String]
def printCourses(courses:String*): Unit = {
//courses.foreach(x => println(x))
courses.foreach(println)
}
(*)一个高阶函数的案例
idea写个程序进行演示
(*)闭包
1)简单的例子
闭包是一个函数,它返回值取决于在此函数之外声明的一个或多个变量的值。
#函数赋值给addMore,该函数就是一个闭包
scala> val addMore = (x:Int) => x + more
<console>:11: error: not found: value more
val addMore = (x:Int) => x + more
^
scala> val addMore = (x:Int) => x + more
addMore: Int => Int = <function1>
scala> addMore(10)
res3: Int = 11
#在闭包创建以后,闭包之外的变量more修改以后,闭包中的引用也会随之变化,因此Scala的闭包捕获的是变量本身而不知当时变量的值
scala> more = 10
more: Int = 10
scala> addMore(10)
res5: Int = 20
#闭包之外的变量修改会影响闭包中相应的变量,同样,在闭包中修改闭包外的变量,则闭包外的变量也会跟着变化
scala> val some = List(1,3,10,-10)
some: List[Int] = List(1, 3, 10, -10)
scala> var sum = 0
sum: Int = 0
scala> some.foreach( sum += _ )
scala> sum
res7: Int = 4
2)复杂一点的例子
#这里的more相当于闭包参数,要在调用的时候才能确定
scala> def make(more:Int) = (x:Int) => x+more
make: (more: Int)Int => Int //方法由三部分组成,make(方法名)、(more: Int)Int(列表参数+返回值)+返回类型
#调用时确定闭包参数more为1,且返回函数值,并赋值给inc1
scala> val inc1 = make(1)
inc1: Int => Int = <function1>
scala> val inc99 = make(99)
inc99: Int => Int = <function1>
上面每次make函数调用时都会产生一个闭包,且每个闭包都会有自己的more变量值
//下面才是真正的调用函数,且各自都有自己的闭包参数more
scala> inc1(10) //闭包参数more值为1
res8: Int = 11
scala> inc99(10) //闭包参数more值为99
res9: Int = 109
(*)柯里化
1)概念:柯里化是将方法或者函数中一个带有多个参数的列表拆分成多个小的参数列表(一个或者多个参数)的过程,并且将参数应用前面参数列表时返回新的函数
scala> def sum(x:Int, y:Int) = x+ y
sum: (x: Int, y: Int)Int
scala> sum(2,4)
res17: Int = 6
#将sum写成柯里化的sum,前面方法使用一个参数列表,“柯里化”把方法或者函数定义成多个参数列表(且第一个参数只有一个参数,剩余的参数可以放在一个参数列表中)
scala> def sum(x:Int)(y:Int) = x+y
sum: (x: Int)(y: Int)Int
scala> sum(2)(4)
res18: Int = 6
#当你调用sum(2)(4)时, 实际上是依次调用了两个普通函数(非柯里化函数)
//第一次调用使用一个参数,x 返回一个函数值
//第二次使用参数y调用这个函数值,下面来用两个分开的定义来模拟sum柯里化函数的调用过程
scala> def first(x:Int) = (y:Int) => x+y
first: (x: Int)Int => Int //frist返回的是函数值,x既是方法参数,又是函数的闭包参数
#调用first方法会返回函数值,既产生第二个函数
scala> val second = first(1) //产生了第二个函数
second: Int => Int = <function1> //second是函数变量名,引用某个函数值
scala> second(2)
res21: Int = 3
#上面first,second的定义演示了柯里化函数的调用过程,他们本身和sum并没有任何关系,但是
scala> val second = sum(1) _ //sum(1)相当于第二方法的方法名
second: Int => Int = <function1>
scala> second(2)
res22: Int = 3
注意:
scala> val second = sum(1) _
second: Int => Int = <function1>
scala> val func = sum _ //这里是将整个sum方法转换为函数,该函数带有两个参数,而前面知识将方法sum的一部分转换为函数(既第二个列表参数),所以上面只带有一个参数
func: Int => (Int => Int) = <function1>
三:scala面向对象编程
(*)scala的类的定义
复习:面向对象的基本概念
1)定义:把数据和操作数据的方法放到一起,作为一个整体(类class)
2)面向对象的特质
(1)封装
(2)继承
(3)多态
3)scala既是面向函数式编程也是面向对象编程->多范式
(*)作用域
1)默认就是public
2)跟java语言类似
3)一般情况下使用
public 公共的,任意都可以访问
private 私有,表示当前类的对象(所有)都可以访问
private[this] 当前对象(实例)都可以可以访问,其他对象不能访问
private[pageage]: 表示某个包下面都可以访问,比如private[Spark],表示在spark包下所有的类都可以访问
(*)类的解析
对于Java和scala来说,运行程序必须main方法中
- 对JAVA语言来说,main method在class类中
public static void main(String[] args)
*.java -> compile -> *.class -> JVM
- 对于scala语言来说,main method在object中
def main(args: Array[String]) {}
*.scala -> compile -> *.class -> JVM
val 只会生成get方法
var 生成set和get方法
(*)伴生对象
1)如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类
2)伴生类和伴生对象必须存放在一个.scala文件之中
3)伴生类和伴生对象,最大的特点就在于,互相可以访问private field
(*)构造器
类似于java中的构造方法
1)主构造器( class Boy(val name:String) )
* 在scala中,主构造器是与类名放在一起的,有且只有一个,java可以写多个构造方法,多个构造方法间实现重载
* 在类中,没有定义在任何方法中的代码(包括成员字段),都属于主构造器的代码,且执行顺序于代码书写的顺序是一致的,其实与java一样
* 在java中方法之外的代码(成员及代码块),在构造器调用之前最先执行,姑且将这些代码看做也是一个主构造器中进行执行的
* 主构造器还可以通过使用默认参数,来给参数默认的值
2)辅助构造器( def this(name:String, age:Int) )
* 辅助构造可以有多个
* 多个辅助构造器之间可以调用
* 辅助构造中的参数不可以加val或者var
3)私有构造器
* 私有构造器是针对于主构造器的
* 如果声明了私有构造器,那么只能被他的伴生对象访问
* 如果主构造器设置为私有构造器,那么辅助构造器依然可以访问(访问权限)
(*)scala中的object
1)object:相当于Java中的静态类,类似于java单例模式,通常在里面放一些class层面共享的内容
2)可以将object看做是一个类class,只是这个类在内存中只有一个单例,且定义的object就是实例名,不需要我们自己实例化,运行于JVM,在jvm中帮我们new出来了
3)第一次调用object方法时,会执行object构造器,也就是说object内部不在method中的代码(并且只执行一次),但是object不能定义接受参数的构造器
4)注意object的构造器只会在第一次调用时执行,以后再次调用不会再执行构造器了
在scala中可以用object实现:
作为存放工具函数或者常量的地方
高效的共享单个不可变实例
单例模式
【一个简单的工具类】
object DateUtils {
def getCurrentDate: String = getCurrDateTime("EEEE, MMMM d")
def getCurrentTime: String = getCurrDateTime("K:m aa")
private def getCurrDateTime(dateTimeFormat:String) :String = {
val dateFormat = new SimpleDateFormat(dateTimeFormat)
val cal = Calendar.getInstance()
dateFormat.format(cal.getTime)
}
}
【实现一个单例模式】
/**
* 实现一个单例模式
* 构造函数被我们定义为private的目的:防止直接调用该类来创建对象
*
*/
class StaticTest private {
private def add_(x:Int, y:Int): Int = {
return x + y
}
}
/**
* 这个就是单例模式的定义,和类同名,且不带参数
* */
object StaticTest {
//内部声明一个StaticTest类实例对象
val staticTest = new StaticTest
//调用StaticTest类的方法
def add(x:Int, y:Int):Int = {
return staticTest.add_(x, y)
}
}
object StaticTest2{
def main(args: Array[String]): Unit = {
//调用,定义一个单例对象
val static = StaticTest
//调用add方法
println(static.add(2,3))
}
}
(*)scala的apply方法
1)当不是new关键字来创建对象的时候,使用apply可以使我们的代码更简洁
class Person {
var name: String = _
var age:Int = 0
}
object Person{
def apply(name:String): Person = {
val person = new Person
person.name = name
person
}
def apply(name:String, age:Int): Person = {
val person = new Person
person.name = name
person.age = age
person
}
}
现在可以不使用new关键字来创建Person对象了
val person2 = Person("Mike")
val person3 = Person("Mary", 23)
scala编译器会对伴生对象中apply进行特殊化处理,让你不使用new关键字即可创建对象
(*)继承
1)scala中,让子类继承父类,与java一样,使用extends关键字
2)继承就代表,子类可以从父类继承父类的field和method,然后子类可以在自己内部放入父类所没有,子类特有的filed和method,使用继承可以复用代码
3)子类可以覆盖父类的filed和method,但是要注意的是final关键字,代表field和method无法覆盖
4)子类中的方法要覆盖父类中的方法,必须写override(参见foo)
5)子类中的属性val要覆盖父类中的属性,必须写override(参见nameVal)
6)父类中的变量不可以覆盖(参见nameVar)
4)定义抽象类
abstract class Animal { //定义一个抽象类,用于继承
var age :Int = 2
val weight:Double = 35
//抽象方法,没有具体的实现
def color()
//非抽象方法,有具体的实现
def eat()={
println("吃食物")
}
//使用了final关键字,表示不能重写, override
final def action():Unit = {
println ("奔跑")
}
}
/**
* 如果想实现父类的方法:CTRL+I
* 如果想重写父类的方法:CTRL+O
*/
class Monkey extends Animal{
//重写父类字段
override var age:Int = 15
override val weight: Double = 15
//抽象方法,没有实现(重写父类抽象的方法,可以选择性的使用override)
override def color(): Unit = {
println("棕色")
}
//非抽象方法有实现了,(重写父类非抽象方法,必须使用override)
override def eat(): Unit = {
println("吃桃子")
}
}
object Monkey{
def main(args: Array[String]): Unit = {
val monkey = new Monkey
println(monkey.weight)
println(monkey.color())
println(monkey.eat())
println(monkey.action())
}
}
(*)trait:特质
1)scala 特征:相当于Java中的接口,实际上他比接口功能强大.
2)与接口不同的是:是可以定义属性和方法的实现
3)一般情况下scala的类只能被继承单一父类,但是如果是trait的话可以实现多个继承,从结果上来看就是实现了多继承
4)trait定义的方式与类类似,但是它使用的关键字是trait,通过extends来继承的,用with实现多继承
(*)多态
1)什么是多态:目的是为了让代码更加,降低耦合
有继承或实现特质(接口)
父类引用指向子类对象或接口指向实现类
方法需要重写
abstract class Element {
def demo(): Unit ={
println("Element's invoked")
}
}
class ArrayElement extends Element{
override def demo(): Unit = {
println("ArrayElement's invoked")
}
}
class LineElement extends ArrayElement{
override def demo(): Unit = {
println("LineElement's invoked")
}
}
class UniforElement extends Element //没有重写父类方法
object ElementTest{
//参数类型为祖宗类,任何子类实例都可以传递(基类)
def invokedDemo(e:Element): Unit ={
e.demo() //多态,在运行时调用相应子类方法
}
def main(args: Array[String]): Unit = {
invokedDemo(new ArrayElement) //父类引用指向子类对象
invokedDemo(new LineElement) //祖父类引用指向孙类对象
invokedDemo(new UniforElement) //没有重写父类方法,所以调用的时候输出祖宗类demo
}
}
四:scala的集合
(*)数组
java中的集合都是可变集合
在scala中集合分为可变集合和不可变集合
可变数组默认可以使用
不可变数组必须引用:import scala.collection.mutable.ArrayBuffer
不可变数组:长度不可改变, 但是内容可以改变
#添加一个元素
scala> arrBuffer += 10
res5: arrBuffer.type = ArrayBuffer(10)
#根据值删除,如果元素存在则直接remove,否则忽略(不报错)
scala> arrBuffer -= 10
res10: arrBuffer.type = ArrayBuffer(20, 30, 40)
#追加一组元素,使用List或者数组或者元组(需要用++=)
scala> arrBuffer ++= List(20,30,40,50)
res12: arrBuffer.type = ArrayBuffer(20, 30, 40, 20, 30, 40, 50)
#调用mkString方法,将数组中的元素组合成一个字符串,并且将各个元素之间使用指定的分隔符进行分割
scala> array.mkString(",")
res13: String = 20,30,40,20,30,40,50
#在字符串前后追加一个符号<>
scala> array.mkString("<", ",", ">")
res14: String = <20,30,40,20,30,40,50>
(*)集合
scala中的集合
Seq[T](List[T]、Set[T]、Map[T])
-1:都是泛型
类型而言,具体业务具体对待(看存储的数据)
-2:分为可变和不可变
在Java中所有的集合(List、Map、Set)都是可变的
不可变:集合中元素的个数不可改变,元素值不能改变
常见的三种集合:
可变的: List Map Set
不可变的: ListArray Map Set
包不同
可变的集合:
scala.collection.mutable._
不可变的集合:
scala.collection.immutable._
默认使用的集合类型,是不可变得,但是可以在程序中导入包即可使用
1)List
scala> val lst = List(1,2,3,5)
lst: List[Int] = List(1, 2, 3, 5)
scala> lst(1)
res2: Int = 2
scala> lst(3)
res3: Int = 5
#修改元素内容抛出异常
scala> lst(3) = 10
<console>:13: error: value update is not a member of List[Int]
lst(3) = 10
#修改集合长度抛出异常
scala> lst += 16
<console>:13: error: value += is not a member of List[Int]
Expression does not convert to assignment because receiver is not assignable.
lst += 16
^
#引入可变集合包
scala> import scala.collection.mutable.ListBuffer
import scala.collection.mutable.ListBuffer
#可以看到这是可变的集合,但是一定要先引入 import scala.collection.mutable.ListBuffer包后才能使用
scala> val lb = new ListBuffer[Int]()
lb: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
#追加一条数据
scala> lb.append(1)
scala> lb
res10: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1)
#可以修改里面的值
scala> lb(0) = 10
scala> lb
res3: scala.collection.mutable.ListBuffer[Int] = ListBuffer(10)
#也是可以这样追加
scala> lb += 20
res4: lb.type = ListBuffer(10, 20)
#也可以追加一个元组
scala> lb += (30,40,50)
res5: lb.type = ListBuffer(10, 20, 30, 40, 50)
#也可以追加一个List, 但是需要++=
scala> lb ++= List(60,70,80)
res7: lb.type = ListBuffer(10, 20, 30, 40, 50, 60, 70, 80)
#也可以追加一个Array
scala> lb ++= Array(90, 100)
res8: lb.type = ListBuffer(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
#删除一个元素
scala> lb -= 90
res9: lb.type = ListBuffer(10, 20, 30, 40, 50, 60, 70, 80, 100)
#根据下标删除
scala> lb.remove(0)
res10: Int = 10
#删除一个元组
scala> lb -= (20,30)
res12: lb.type = ListBuffer(40, 50, 60, 70, 80, 100)
#匹配上进行删除,无匹配的则忽略
scala> lb -= (20, 40)
res13: lb.type = ListBuffer(50, 60, 70, 80, 100)
2)Map
#不可变集合Map
scala> val mp = Map("a"->1, "b"->2)
mp: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2)
#导入所有的可变集合包,这里使用的下滑线,
#下划线可以代表通配符
scala> import scala.collection.mutable._
import scala.collection.mutable._
#可变集合Map
scala> val mp = Map("a"->1, "b"->2)
mp: scala.collection.mutable.Map[String,Int] = Map(b -> 2, a -> 1)
#增加keyvalue,长度是可变的
scala> mp.put("c", 3)
res14: Option[Int] = None
scala> mp
res15: scala.collection.mutable.Map[String,Int] = Map(b -> 2, a -> 1, c -> 3)
#修改mp的内容,内容也是可变的
scala> mp("c") = 4
#加一个对偶元组,可以("d"->5)这样写
scala> mp += ("d"->5)
res19: mp.type = Map(b -> 2, d -> 5, a -> 1, c -> 4)
#删除一个key,或者写成mp.remove("d")
scala> mp -= "d"
res21: mp.type = Map(b -> 2, a -> 1, c -> 4)
3)Set
#定义一个不可变的set
scala> val lmst = Set(1,2,3,4,5)
lmst: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)
scala> lmst += 12
<console>:13: error: value += is not a member of scala.collection.immutable.Set[Int]
Expression does not convert to assignment because receiver is not assignable.
lmst += 12
^
#定义一个可变的Set,不能重复,会把重复的给过滤掉,保留一份
#Set添加的数据是无序的
scala> val imst = Set(1,2,3,4,2)
imst: scala.collection.mutable.Set[Int] = Set(1, 2, 3, 4)
#添加一个数据
scala> imst +=7
res2: imst.type = Set(1, 2, 3, 7, 4)
#添加一个元组
scala> imst += (10,14,54)
res3: imst.type = Set(1, 2, 3, 54, 10, 7, 4, 14)
scala> imst ++= st
res5: imst.type = Set(12, 1, 100, 2, 3, 54, 10, 7, 4, 14)
#删除一个数据
scala> imst -= 12
res6: imst.type = Set(1, 100, 2, 3, 54, 10, 7, 4, 14)
4)scala的集合总结
数组、集合
数组的特点:
可变数组->长度和内容都可以改变
不可变数组->长度不能变,内容可以变
集合的特点:
可变集合:长度和内容都可以改变
不可变集合:长度内容都不能改变,适用场景:多线程
追加一个元素: +=
移除一个元素:-=
追加一个类型一样集合:++=
(*)集合的常用方法:
1)reduce介绍
scala> val arr = Array(1,2,5,7,10)
arr: Array[Int] = Array(1, 2, 5, 7, 10)
scala> arr.reduce((x, y) => x+y)
res7: Int = 25
#简单写法
scala> arr.reduce(_+_)
res8: Int = 25
scala> arr.reduce(_*_)
res9: Int = 700
scala> arr.reduce(_-_)
res10: Int = -23
#注意:从左到右的顺序依次计算
scala> arr.reduce(_-_) //默认使用reduceLeft,从左到右计算
res10: Int = -23
scala> arr.reduceLeft(_+_)
res11: Int = 25
scala> arr.reduceRight(_+_) //表示从列表尾部开始,对两两元素进行求和操作
res12: Int = 25
scala> arr.reduceLeft(_-_)
res13: Int = -23
scala> arr.reduceRight(_-_)
res14: Int = 7
举例:
scala> val list = List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)
scala> list.reduce(_ - _)
res29: Int = -13 //可以看出,得到的结果和reduceLeft的结果是一样的
#list.reduceLeft(_ - _), reduceLeft步骤解析
((((1-2)-3)-4)-5)
((((-1)-3)-4)-5)
(((-4)-4)-5)
((-8)-5)
(-13)
#list.reduceRight(_ - _), reduceRight步骤解析
val list = List(1,2,3,4,5)
(1-(2-(3-(4-5)))
(1-(2-(3-(-1)))
(1-(2-(4)))))
(1-(-2))
(3)
(*)fold方法
意思是叠加(中文折叠),reduce是聚合的意思,从本质上说,fold函数是将一种格式的输入数据转换成另外一个格式数据返回
scala> val list = List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)
scala> list.reduce(_+_)
res0: Int = 15
#这里0初始值,也是后面高阶函数的柯里化
scala> list.fold(0)((x, y) => x+y)
res1: Int = 15
#默认值是100
scala> list.fold(100)((x, y) => x+y)
res2: Int = 115
#步骤解析
(((((100+1)+2)+3)+4)+5)
((((101+2)+3)+4)+5)
(((103)+3)+4)+5)
((106+4)+5)
(110+5)
115
foldLeft(从左到右计算)
foldRight(从右到左计算)
#默认值是字符串类型,List是数字集合的时候,不能使用fold,使用foldLeft或者foldRight
scala> list.fold("Hello")(_+_)
<console>:13: error: type mismatch;
found : Any
required: String
list.fold("Hello")(_+_)
^
scala> list.foldLeft("Hello")(_+_)
res5: String = Hello12345
#从右到左进行计算
scala> list.foldRight("Hello")(_+_)
res6: String = 12345Hello
#字符串与数字进行数学运算则报错,而+除外,+可以表示字符串拼接转换
^
(*)sortBy(排序仅仅是改变了数据的顺序,而无法改变数据的类型)
scala> val list = List(1,2,4,-7,12,90)
list: List[Int] = List(1, 2, 4, -7, 12, 90)
scala> list.sortBy(x=>x)
res8: List[Int] = List(-7, 1, 2, 4, 12, 90)
scala> list.sortBy(x=> -x)
res9: List[Int] = List(90, 12, 4, 2, 1, -7)
(*)sorted
scala> list.sorted
res10: List[Int] = List(-7, 1, 2, 4, 12, 90)
scala> list.sorted.reverse
res11: List[Int] = List(90, 12, 4, 2, 1, -7)
(*)sortWith(两两比较,类似于冒泡排序)
scala> list.sortWith(_>_)
res12: List[Int] = List(90, 12, 4, 2, 1, -7)
scala> list.sortWith(_<_)
res13: List[Int] = List(-7, 1, 2, 4, 12, 90)
(*)flatten(压平,将大的list中的小list集合进行压平)
#list中没有小的list,报错
#每四个元素进行一次分组
scala> var it = list.grouped(4)
it: Iterator[List[Int]] = non-empty iterator
scala> var lst = it.toList
lst: List[List[Int]] = List(List(1, 2, 4, -7), List(12, 90))
#压平
scala> lst.flatten
res16: List[Int] = List(1, 2, 4, -7, 12, 90)
(*)flatMap = flatten+map
scala> var lines = Array("spark hadoop hive", "hive hbase redis hive spark", "scala java java")
lines: Array[String] = Array(spark hadoop hive, hive hbase redis hive spark, scala java java)
[第一种写法]
scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).map(x => (x._1, x._2.map(_._2).sum))
res21: scala.collection.immutable.Map[String,Int] = Map(java -> 2, hadoop -> 1, spark -> 2, hive -> 3, scala -> 1, redis -> 1, hbase -> 1)
[第二种写法] map返回的是key+value
scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).map(x => (x._1, x._2.foldLeft(0)(_+_._2)))
scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).map(x => (x._1, x._2.foldLeft(0)((x, y) => x + y._2)))
res22: scala.collection.immutable.Map[String,Int] = Map(java -> 2, hadoop -> 1, spark -> 2, hive -> 3, scala -> 1, redis -> 1, hbase -> 1)
[第三种写法] mapvalues返回的是value _.foldLeft(0)(_+_._2)
scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).mapValues(_.foldLeft(0)(_+_._2))
res28: scala.collection.immutable.Map[String,Int] = Map(java -> 2, hadoop -> 1, spark -> 2, hive -> 3, scala -> 1, redis -> 1, hbase -> 1)
[第四种写法]
val arr5 = arr4.map(x => (x._1, x._2.length))
步骤解析
scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1)
res23: scala.collection.immutable.Map[String,Array[(String, Int)]] =
Map(
java -> Array((java,1), (java,1)),
hadoop -> Array((hadoop,1)),
spark -> Array((spark,1), (spark,1)),
hive -> Array((hive,1), (hive,1), (hive,1)),
scala -> Array((scala,1)),
redis -> Array((redis,1)),
hbase -> Array((hbase,1))
)
1:foldLeft解析
scala> val arr = Array(("java",1), ("java",1), ("java", 2))
arr: Array[(String, Int)] = Array((java,1), (java,1), (java,2))
scala> arr.foldLeft(0)(_+_)
<console>:13: error: overloaded method value + with alternatives:
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to ((String, Int))
arr.foldLeft(0)(_+_)
^
scala> arr.foldLeft(0)((x, y) => x + y._2)
res26: Int = 4
(*)模式匹配
可以匹配的类型:
1)匹配内容
//这里的=>不是函数,在这里表示模式匹配,如果匹配上则执行这里的业务逻辑
//类比法 JAVA : switch case
object CaseDemo01 extends App {
val arr = Array("tom", "mike", "hello")
val i = Random.nextInt(arr.length)
println(i)
val name = arr(i)
println(name)
//这里的=>不是函数,在这里表示模式匹配,如果匹配上则执行这里的业务逻辑
//类比法 JAVA : switch case
name match {
case "tom" => println("我是tom...")
case "mike" => {
println("我是mike'...")
}
case _ => println("真不知道你们在说什么...")
}
}
2)匹配类型
object CaseDemo02 extends App {
//定义一个数组
val arr: Array[Any] = Array("hello123", 1, -2.0, 2.0, CaseDemo02, 2L)
//取出一个元素
val elem = arr(5)
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 w: Long => println("long " + w)
case CaseDemo02 => {
val c = CaseDemo02
println(c)
println("case demo 2") //throw new Exception("not match exception")
}
case _ => { //表示什么都没匹配上,相当于switch中的default
println("no")
println("default")
}
}
}
3)匹配Array
println("******************匹配数组****************************")
val arr = Array(1, 1, 7, 0)
arr match {
case Array(0, 2, x, y) => println(x + " " + y)
case Array(1, 1, 7, 0) => println("0 ...")
case Array(1, 1, 7, y) => println("only 0 " + y)
case _ => println("something else")
}
4)匹配元组
println("******************匹配元组****************************")
val tup = (1, 3, 8)
tup match {
case (1, x, y) => println(s"hello 123 $x , $y")
case (_, w, 5) => println(s"w:${w}")
case _ => println("else")
}
(*)样例类
1)简单来说,scala的class,就是在普通类定义前加个case关键字,然后你就可以对这些类进行模式匹配
case class带来的最大的好处就是支持模式匹配
//样例类,模式匹配,封装数据(多例),不用new即可创建实例
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
//样例对象,模式匹配(单例)
case object CheckTimeOutTask
/**
* 样例类demo
* 简单来说,scala的class,就是在普通类定义前加个case关键字,然后你就可以对这些类进行模式匹配
* case class带来的最大的好处就是支持模式匹配
*/
object CaseDemo04 extends App {
val arr = Array(CheckTimeOutTask, new HeartBeat(123), HeartBeat(88888), new HeartBeat(666), SubmitTask("0001", "task-0001"))
val a = arr(1)
println(a)
a match {
case SubmitTask(id, name) => {
println(s"$id, $name")
}
case HeartBeat(time) => {
println(time)
}
case CheckTimeOutTask => {
println("check")
}
}
}
五:Scala的高级内容:泛型
(*)泛型类
泛型类(类声明时类名后面括号中即为类型参数),顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field、method,就可以使用这些泛型类型
使用泛型类,通常需要对类中某些成员,比如某些field或者method的参数或变量,进行统一的类型限制,这样可以保证程序更好健壮性和稳定性
如果不适用泛型进行统一的类型限制,那么在后面程序运行中,难免会出现问题,比如传入了不希望出现的类型,导致程序崩溃
class GenericClass1 {
private var content:Int = 100
//定义set和get方法
def setContent(value:Int): Unit ={
content = value
}
def getContent():Int= {
content
}
}
class GenericClass2{
private var content:String = "abc"
//定义set和get方法
def setContent(value:String): Unit ={
content = value
}
def getContent():String= {
content
}
}
/**
* 定义一个泛型类
* @tparam T
*/
class GenericClass[T] {
private var content: T = _
//定义set和get方法
def setContent(value: T): Unit = {
content = value
}
def getContent(): T = {
content
}
}
object GenericClassTest{
def main(args: Array[String]): Unit = {
val intObj = new GenericClass1
intObj.setContent(1234)
intObj.getContent()
val strObj = new GenericClass2
strObj.setContent("abc")
strObj.getContent()
val genObj = new GenericClass[String]
genObj.setContent("abc")
println(genObj.getContent())
val genObj2 = new GenericClass[Int]
genObj2.setContent(123)
println(genObj2.getContent())
val genObj3 = new GenericClass[Double]
genObj3.setContent(12.3)
println(genObj3.getContent())
}
}
(*)泛型函数
泛型函数(方法声明时方法名后面括号中的类型参数),与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值
引用反射包 import scala.reflect.ClassTag
ClassTag:表示scala在运行时的状态信息,这里表示调用时的数据类型
/**
* 泛型函数
* Created by zhangjingcun on 2018/9/14 14:47.
*/
object GenericFunc {
/**
* 定义函数,创建一个int类型的数组
* @param elem
* @return
*/
def mkIntArray(elem: Int*) = Array[Int](elem: _*)
/**
* 定义函数:创建一个String类型的函数
* @param elem
* @return
*/
def mkStringArray(elem:String*) =Array[String](elem:_*)
/**
* 在函数中使用泛型,取代上面的两个函数
* @param elem
* @tparam T
* @return
*/
def mkArray[T:ClassTag](elem:T*) = Array[T](elem:_*)
/**
* 定义泛型函数
* @param name
* @param age
* @tparam T1
* @tparam T2
* @tparam T3
* @return
*/
def say[T1, T2, T3](name:T1, age:T2):T3 = {
(s"name:${name}, age:${age}").asInstanceOf[T3]
}
def main(args: Array[String]): Unit = {
println(mkIntArray(1, 2, 3, 4, 5).toBuffer)
println(mkStringArray("a", "b", "c").toBuffer)
println("*****************调用泛型函数**************************************")
println(mkArray(1, 2, 3, 4, 5).toBuffer)
println(mkArray("a", "b", "c").toBuffer)
println("*****************调用泛型函数2**************************************")
println(say[String,Int,String]("Tom", 23))
println(say("Mike", 23)) //scala会自动推断泛型的实际类型
}
}
(*)泛型的上界、泛型的下界
核心意思:泛型的取值范围
1:以普通的数据类型为例
10 < i:Int < 100 ->规定了i的取值范围(10-100)
2:规定:类型的取值范围-> 上界、下界
定义几个类:(具有继承关系) Class A -> Class B -> Class C -> Class D
定义泛型:
D <: T 泛型类型 <: B --------> 泛型T的取值范围:B、C、D
3:概念:
上界: 定义 S <: T 这是类型上界的定义,也就是S必须是类型T的子类(或者本身,自己也可以认为自己是自己的子类)
(upper bound )等同于 java中的<T extends Comparable<T>>
下界: 定义 U >: T 这是类型下界的定义,也就是U必须是类型T的父类(或者本身,自己也可以认为自己是自己的父类)
(lower bound) 等同于 java中的<T super Comparable<T>>
4:举例:
上界:参考UpperBound.scala
5:举例:
拼接字符串的例子,接收类型必须是String或者String的子类
scala> def addTwoString[T <: String](x:T, y:T) = println(x + "********" + y)
addTwoString: [T <: String](x: T, y: T)Unit
scala> addTwoString(1233, 1234)
<console>:13: error: inferred type arguments [Any] do not conform to method addTwoString's type parameter bounds [T <: String]
addTwoString("adc", 1234)
(*)视图界定:核心扩展了上界和下界的范围,表达方式,上界为例 <%
1)可以接收以下类型
(1)上界和下界的类型
(2)允许通过隐式转换过去的类型(定义一个隐式转换函数)
实际上我们希望 addTwoString(1233, 1234)
1.首先将1233转换成字符串的1233
2.再拼加,得到我们想要的结果(由此引出新的概念,视图界定)
2)改写上面的例子
(1)定义一个转换规则:即隐式转换函数
implicit def int2String(x:Int):String = { x.toString() }
(2)使用视图界定改写函数addTwoString
def addTwoString[T <% String](x:T, y:T) = println(x + "********" + y)
表示:T可以是String和String的子类,也可以是能够转换成String的其他类
调用:报错,没有找到隐式转换规则吧int转换成String类型
scala> def addTwoString[T <% String](x:T, y:T) = println(x + "********" + y)
addTwoString: [T](x: T, y: T)(implicit evidence$1: T => String)Unit
scala> addTwoString(100, 200)
<console>:13: error: No implicit view available from Int => String.
addTwoString(100, 200)
^
调用:正常了
scala> implicit def int2String(x:Int):String = { x.toString() }
warning: there was one feature warning; re-run with -feature for details
int2String: (x: Int)String
scala> addTwoString(100, 200)
100********200
(3)分析执行过程:addTwoString(100, 200)
(1)检查参数的类型,Int类型,接收的是String类型
(2)在当前的会话中查找有没有一个隐式转换函数,满足Int可以转换成String类型
(3)如果找到了,先调用这个隐式转换函数
如果没有找到,出错
(4)在调用函数
(4)在测试一下
#还是报错,这里提示了我们找不到any类型转换成String的隐式转换函数
scala> addTwoString(1234, "dd")
<console>:14: error: No implicit view available from Any => String.
addTwoString(1234, "dd")
^
#定义一个any类型转换为String类型的隐式转换函数
scala> implicit def any2String(x:Any):String = (x.toString())
warning: there was one feature warning; re-run with -feature for details
any2String: (x: Any)String
scala> addTwoString(1234, "dd")
1234********dd
(*)隐式转换函数
/**
* 定义一个水果类
* @param name
*/
class Fruit(name:String){
def getName() ={
name
}
}
/**
* 猴子类,喜欢吃水果
* @param f
*/
class Monkey(f:Fruit){
def say() = println(s"Monkey like ${f.getName()}")
}
object ImplicitDemo {
/**
* 定义一个转换规则(隐式转换函数),把Fruit转换成Monkey
* @param f
* @return
*/
implicit def Fruit2Monkey(f:Fruit):Monkey = {
new Monkey(f)
}
def main(args: Array[String]): Unit = {
//定义一个水果对象
val f: Fruit = new Fruit("桃子") //调用这个函数
f.say()
}
}
(*)斜变和逆变
(1)scala的协变和逆变是非常特色的功能,完全解决了java泛型的一大缺憾
//举例来说:
Java中,如果有Bird类是Animal的子类,那么EatSomething[Bird]是不是EatSomething[Animal]的子类?答案是:不行,因此对于开发程序造成了很多的麻烦
//在scala中,只要灵活的使用协变和逆变,就可以解决Java泛型的问题
1:协变的概念:(泛型变量的值可以是本身或者其子类的类型)scala的类或者特征的泛型定义中,如果在类型参数前面加入+符号,就可以使类或者特征变成协变了
参考CovarianceDamo代码
2:逆变的概念:(泛型变量的值可以是本身或者其父类的类型)在类或者特征的定义中,在类型参数之前加上一个-符号,就可以定义逆变泛型类和特征了
参考ContravanceDemo代码
(*)隐式参数
核心:隐式转换
参考ImplicitParam代码
(*)隐式类
在类前面加个implicit关键字,变成了隐式转换类
参考ImplicitClassDemo代码
(*)使用柯里化实现隐式转换
(actor编程,两年前已经被废弃)