23.大数据学习之旅——scala进阶

版权声明:版权归零零天所有 https://blog.csdn.net/qq_39188039/article/details/86466720

函数式编程的概念


编程范式
函数式编程是一种编程范式,我们常见的编程范式有:
1)命令式编程(Imperative programming),常见的面向对象编程是也是一种命令式编
程。比如java,c等。
命令式编程是完全依托于冯诺依曼体系机来实现的,即代码最后会转变为一条条的指令去执
行,所以指令式编程的时间复杂度是和指令数相关的。根据摩尔定律,冯诺依曼体系机的性能
可能本世纪30年代就不再提高,即当冯诺依曼体系机被淘汰时,指令式编程可能也会被淘汰。
现在有一个名字就是:冯诺依曼瓶颈,即内存的速度跟不上cpu处理速度。IBM TrueNorth。
2)函数式编程,从理论上来说,函数编程是不依托于指令架构的,因为函数式编程是建立主
体和主体之间的映射关系。但是目前从实际情况来开,函数式编程还是需要转变为指令去运行
的。
函数式编程语言:HasKell,K#,Lisp
3)逻辑式编程,最常用的逻辑编程语言是Prolog,另外有较适用于大型方案的Mercury。
函数式编程
在函数式语言中,函数作为一等公民,可以在任何地方定义,在函数内或函数外,可以作为函
数的参数和返回值,可以对函数进行组合。这个概念就是——高阶函数,高阶函数就是参数
为函数或返回值为函数的函数。有了高阶函数,就可以将复用的粒度降低到函数级别,相对于
面向对象语言,复用的粒度更低。
此外,函数式语言通常提供非常强大的集合类(Collection),提供很多高阶函数,因此使用
非常方便。
函数式编程语言还提供惰性求值(Lazy evaluation,也称作call-by-need),是在将表达式
赋值给变量(或称作绑定)时并不计算表达式的值,而在变量第一次被使用时才进行计算。这
样就可以通过避免不必要的求值提升性能。
纯函数式编程语言中的变量的值是不可变的(immutable),也就是说不允许像命令式编程
语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写“x = x + 1”,这依赖可
变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。

懒值 Lazy
在这里插入图片描述

柯里化 Currying
scala的柯里化的作用是结合scala的高阶函数,从而允许用户自建立控制结构。
柯里化(Currying)技术Christopher Strachey 以逻辑学家 Haskell Curry 命名的(尽管它是Moses
Schnfinkel 和 Gottlob Frege 发明的)。它是把接受多个参数的函数变换成接受一个单一参数的函数,
并且返回接受余下的参数且返回结果的新函数的技术。
案例1:
object Demo08 {
def main(args: Array[String]): Unit = {
//首先我们定义一个函数:
def f1(a:Int,b:Int):Int={a+b}
//现在我们把这个函数变一下形:
def f2(a:Int)(b:Int)={a+b}
//那么我们应用的时候,应该是这样用:f2(2)(3),最后结果都一样是5,这种方式(过程)就叫柯里
化。
val r1=f2(2)(3)
//柯里化实质上会演变成这样一个函数:
//接收一个参数a,返回一个匿名函数,
//该匿名函数又接收一个参数b,函数体为a+b
def f3(a:Int)=(b:Int)=>a+b
val f4=f3(2)
//请思考f4(3)的值是多少?答案是:5
f4(3)
}
}
示例2:
object Demo09 {
def f1(a:Int,b:Int,c:Int):Int={a+b+c}
def f2(a:Int)(b:Int)(c:Int):Int={a+b+c}
def f3(a:Int)(b:Int,c:Int):Int={a+b+c}
def f4(a:Int,b:Int)(c:Int):Int={a+b+c}
def main(args: Array[String]): Unit = {
//f1和f2的函数的体现的是传入三个数,马上得到结果
f1(1,2,3)
f2(1)(2)(3)
//f3函数则可以体现:延迟执行的思想以及固定易变因素的思想
val r1=f3(1)(2,3)
}
}
示例3:
object Demo10 {
def f1(a:Int,b:Int,f:(Int,Int)=>Int):Int={f(a,b)}
def f2(a:Int)(b:Int)(f:(Int,Int)=>Int):Int={f(a,b)}
def f3(a:Int,b:Int)(f:(Int,Int)=>Int):Int={f(a,b)}
def f4(a:Int)(b:Int,f:(Int,Int)=>Int):Int={f(a,b)}
def main(args: Array[String]): Unit = {
//调用f3
f3(2,3){(a:Int,b:Int)=>a+b}
//可简化为下面的形式,我们发现这和scala中很多函数的形式很相近,比如:for(x<-1 to 10){print()}
//所以,柯里化的另外一个作用是让用户灵活的自义定自建控制结构
f3(2,3){
+}
f3(2,3){
*_}
// 延迟处理思想
def f3(a:Int)=(b:Int)=>a+b
val f4=f3(2)
f4(3)
}
}
总结柯里化的作用
柯里化技术在提高适用性、延迟执行或者固定易变因素等方面有着重要重要的作用,加上scala语言本
身就是推崇简洁编码,使得同样功能的函数在定义与转换的时候会更加灵活多样。另外在Spark的源
码中有大量运用scala柯里化技术的情况,需要掌握好该技术才能看得懂相关的源代码。
在scala柯里化中,闭包也发挥着重要的作用。所谓的闭包就是变量出了函数的定义域外在其他代码块
还能其作用,这样的情况称之为闭包。就上述讨论的案例而言,如果没有闭包作用,那么转换后函数
其实返回的匿名函数是无法在与第一个参数a相关结合的,自然也就无法保证其所实现的功能是跟原
来一致的。

Scala Collection
Scala提供了一套很好的集合实现,提供了一些集合类型的抽象。
Scala 集合分为不可变(immutable)的集合和可变(mutable)的集合。
数组Array
对于数组的使用,若想调用数组提供的方法,我们需要使用import Array._ 引入包
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Set
Scala Set(集合)是没有重复的对象集合,所有的元素都是唯一的。
Scala 集合分为可变的和不可变的集合。
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用scala.collection.mutable.Set 包。
默认引用 scala.collection.immutable.Set
在这里插入图片描述
在这里插入图片描述
Map映射
Map(映射)是一种可迭代的键值对(key/value)结构。
所有的值都可以通过键来获取。
Map 中的键都是唯一的。
Map 也叫哈希表(Hash tables)。
Map 有两种类型,可变与不可变,区别在于可变对象可以修改它,而不可变对象不可以。
默认情况下 Scala 使用不可变 Map。如果你需要使用可变集合,你需要显式的引入import scala.collection.mutable.Map 类
在 Scala 中 你可以同时使用可变与不可变Map,不可变的直接使用Map,可变的使用mutable.Map。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Tuple 元组
与列表一样,元组也是不可变的,但与列表不同的是元组可以包含不同类型的元素。
元组的值是通过将单个的值包含在圆括号中构成的。例如:
在这里插入图片描述
非常重要的高阶函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

案例
案例,单词统计
写法1:
val val1=List(“hadoop”,“hello”,“hello”,“hadoop”,“world”,“hadoop”)
val1.groupBy { x => x } .mapValues { x => x.size }.foreach(println())
打印的结果:
(hadoop,3)
(world,1)
(hello,2)
写法2:
val1.map { x => (x,1) }.groupBy(x=>x.1).mapValues(x=>x.map(i=>i.2).reduce{(n,m)=>
n+m}).foreach(println(
))
打印的结果:
(hadoop,3)
(world,1)
(hello,2)
写法3:
l4.groupBy { x => x }.map{case(k,v)=>(k,v.size)}.foreach(println)
写法4:
l4.map { x => (x,1) }.groupBy{case(word,count)=>word}.mapValues{x=>x.map{case(word,count)=>
count}.reduce(
+
)}.foreach(println)

类—上篇
概述
1)scala中的类和java中基本类似。
2)scala中的类同样通过class来进行声明
3)scala中的类同样可以具有成员变量和成员方法
4)scala中的类同样可以通过new关键字来创建出对象
创建类
示例1:
//创建一个类,并定义类里的两个成员变量name和age。以及一个成员方法eat()
//需要注意的是:scala中变量(var)声明时需要指定初始值,
class Person {
var name:String="";
var age:Int=0;
def eat(){
println(“吃饭”)
}
}
注:成员属性和成员方法默认都是public的 需要私有可以写private 需要保护可以写protected
示例2:
//当成员变量或成员方法是私有属性时,外部将不能直接访问,这个同java一样
class Person {
private var name:String="";
private var age:Int=0;
private def eat(){
println(“吃饭”)
}
}
def main(args: Array[String]): Unit = {
val p=new Person
p.name=“tom”;
p.age=1;
p.eat();
}
类的构造
和java不同,scala中的类不需要明确声明一个构造器,而是直接将构造参数通过构造参数列表
声明为类的一部分。
而直接写在类的体中的既不是类的成员变量也不是成员函数的部分,会自动收集为构造函数的
体。
示例3:
//scala中的类不需要明确声明一个构造器,而是直接将构造参数通过构造参数列表声明为类的
一部分
class Person(var1:String,var2:Int){
var name=var1;
var age=var2;
//而直接写在类的体中的既不是类的成员变量也不是成员函数的部分,会自动收集为构造函
数的体。
println("……")
println("***")
}
def main(args: Array[String]): Unit = {
//当调用构造方法时,会打印 …… 和***
val p=new Person(“tom”,23)
}
辅助构造器
有些时候 一个类里需要多个构造器。scala里主构造器之外的构造器被称为辅助构造器。
1)Scala的辅助构造器定义开始于def this()
2)Scala里每个辅助构造器的第一个动作都是调用同类的构造器。
示例4:
//scala支持辅助构造器,
class Person(var1:String,var2:Int){
var name=var1;
var age=var2;
//Scala的辅助构造器定义开始于def this()
//Scala里每个辅助构造器的第一个动作都是调用同类的构造器。
def this(var1:String){
this(var1:String,0)
}
//Scala的辅助构造器定义开始于def this()
def this(var2:Int){
this("",var2:Int)
}
}
def main(args: Array[String]): Unit = {
val p1=new Person(“tom”)
val p2=new Person(23)
}
一个完整的示例:
class Person(v1:String,v2:Int){
private var name=v1
private var age=v2
def this(v1:String){
this(v1:String,0)
}
def this(v2:Int){
this("",v2)
}
def setName(name:String)={this.name=name}
def getName()={name}
def setAge(age:Int)={this.age=age}
def getAge()={age}
def eat()={println(“eat”)}
def say()={println(“say”)}
println("&&&&&&")
}
单例对象(object)
1)scala中的类(class)不能定义静态成员(或静态方法),而代之以定义单例对象来替代。
2)单例对象需要通过object关键字来声明
3)一个单例对象可以单独存在,也可以绑定到一个类上
4)单例对象当中的所有方法,都可以不需要创建对象而直接通过object单例对象的名字直接来
调用,用起来感觉就像一个静态方法一样。
5)当一个单例对象和某个类写在同一个源文件中且共享同一个名字时,他们就产生了一个绑
定的关系
6)此时单例对象称为该类的伴生对象。类称为该对象的伴生类。
7)类和他的伴生对象可以互相访问其私有成员。
8)以伴生的方式使为类增加静态成员称为了可能
9)单例对象不能new,因此也没有构造参数。
10)可以把单例对象当作是java中可能会用到的静态方法工具类。
11)比如作为程序的入口main方法必须是静态的,所以main方法必须处在一个单例对象中,
而不能写在一个类中。
12)单例对象在第一次被访问时才会被初始化。
示例5:
//类Person
class Person{
//此私有变量,伴生类可以访问
private val namespace=“1706”
}
//Person的单利对象,也即伴生类Person,
object Person{
def showTime(){
println(System.currentTimeMillis())
}
def shownamespace(){
val p=new Person
//可以访问类的私有变量
println(p.namespace)
}
}
def main(args: Array[String]): Unit = {
Person.showTime();
Person.shownamespace()
}
重写和重载
1)重写是指覆盖父类中的方法来在子类中做其他事项
override def 父类方法名 参数列表 返回值 方法体
2)重载是指在同一个类中提供方法名相同但是参数不同的方法和java中基本一致
在这里插入图片描述
final的使用
可以用在成员变量、成员方法、类本身上
作用和java中相同

类—下篇
包的引用
类似于java中通过import关键字引入包/类scala也可以实现这种方式引入,但是更强大一些
1)scala中的import可以出现在代码任何地方
2)scala中的import时可以指的是对象和包
3)scala中的import可以重命名或隐藏一些被引用的成员
4)scala默认会自动引入如下三个包下的所有内容
java.lang //java中的通用类
scala._ //scala提供的通用类 主要是基本类型除了String
Predef._ //提供了单例对象 以及一些常用方法print println
示例2:
import cn.tedu._
import cn.tedu.{Apple,Orange}
抽象类
scala中同样支持抽象类的使用,抽象类的内部可以包含抽象方法和非抽象方法。
抽象类不允许被实例化,抽象类主要是用来被继承的
示例3:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
多态
示例5:
object Demo13{
def main(args: Array[String]): Unit = {
val t1:Teacher=new TeacherChen(“chen”,32)
val t2:Teacher=new TeacherLiu(“liu”,32)
}
}
特质 trait
1)可以类比java中的接口,但是又和接口非常不一样,特质相当于java中的接口,java中称为
类实现了接口,scala中称为混入了特质。
2)和java中的接口不同的是,scala中的特质可以包含具有方法体的方法。
和抽象类不同的地方在于,scala的类只能单继承,但是可以多混入,利用这种方式可以实现类
似c语言中多继承的特性。
3)在类中可以通过extends 或 with 关键字来让类混入特质,如果类没有明确继承父类,
extends关键字没有被占用就可以使用extends。
4)但是如已经使用了extends显示的继承了父类,再向混入特质就要用with关键字了。
示例6:
//trait类似于java的接口,但可以做具体方法的实现
trait Drive{
def piaoyi()
def feiche(){
}
}
trait Cook{
def tudousi()
def chaojidan()
}
//scala中,只能继承一个父类,但是可以混入多个特质(trait)
//需要实现特质中未实现的方法
//此外,需要注意的是,如果未继承任何类或抽象类,在混入特质时,比如有且仅有一
个特质需要用extends来混入,而其他特质用with混入
class XueSheng extends Person with Drive with Cook{
override def piaoyi(){
}
override def tudousi(){
}
override def chaojidan(){
}
}
9)泛型
基本和java中相同,不同的是,泛型是用方括号引起来的
val arr = ArrayString;
正常情况下通过val 和 var定义的量都会直接分配空间,即使这个量要在很久以后才使
用,这样就会造成内存空间白白被占用。

这种情况下可以加上lazy关键字,延后变量/常量赋值的位置。这样直到后续真正用到这
个量时才真正开辟空间赋值,减少了内存的浪费。

val name = “zhang”//直接分配空间 即使一时半会用不到 •
lazy val name = “zhang”//并不会立即分配空间 直到后续用到了才会分配
示例1:
object Demo01 {
val val1=“zhang” //> val1 : String = zhang
//lazy
lazy val val2=“zhang” //> val2: => String

caseclass - 样例类
case class - 样例类
1)只要在声明类时 在class关键字前加上case关键字 这个类就成为了样例类
样例类必须要显式的声明一个主构造器
2)当样例类声明一个主构造器后,会默认隐式的声明一个空构造器
3)样例类默认实现序列化接口
序列化的两个作用:①数据持久化 ②网络数据传输
4)样例类默认自动覆盖toString equals hashCode方法
5)样例类不需要new可以直接生成对象
示例3:
//需要声明一个主构造器,主构造器可以是一个空构造器
case class Item(var1:String,var2:Int){
var name=var1;
var age=var2;
}
object Demo03 {
def main(args: Array[String]): Unit = {
//样例类不需要new,并且隐式的含有空构造器
val i1=Item
val i2=Item(“tom”,23)
println(i2.name+i2.age)
}
}

option
option
在Scala中用来表示一个结果,它可能有值,也可能没有值,它有两个子Option
示例2:
object Demo02 {
def f1(a:Int,b:Int):Option[Int]={
if(b!=0){
Some(a/b)
}else{
None
}
}
def main(args: Array[String]): Unit = {
//表示如果有正确结果,返回正确结果,没有则返回指定的默认值
val Result=f1(4,2).getOrElse(0)
}
}

上一篇 22.大数据学习之旅——scala手把手带你入门

猜你喜欢

转载自blog.csdn.net/qq_39188039/article/details/86466720
今日推荐