写点什么

大数据培训 | Scala 语言知识分享,直击面试

作者:@零度
  • 2022 年 7 月 01 日
  • 本文字数:3739 字

    阅读完需:约 12 分钟

前言

Scala 作为一门面向对象的函数式编程语言,把面向对象编程与函数式编程结合起来,使得代码更简洁高效易于理解。这就是 Scala 得到青睐的初衷。

Scala 作为一门 JVM 的语言,大数据生态的大部分组件都是 Java 语言开发的,而 Scala 可以与 Java 无缝混编,因此可以很好地融合到大数据生态圈。

主要内容

一些基础东西不再罗列,比如开发环境,循环,异常,泛型等等,本篇只介绍独到,特殊的精华地方,注重概念理解与用法。

  1. 变量和数据类型

  2. 函数式编程

(a)高阶函数

(b)匿名函数

(c)闭包

(d)函数柯里化

  1. 面向对象

(a)类与对象

(b)伴生对象

(c)特质

  1. 模式匹配

  2. 隐式转换

变量和数据类型

变量(var 声明变量,val 声明常量)

var 修饰的变量可改变

val 修饰的变量不可改变

但真的如此吗?

对于以下的定义

class A(a: Int) {

var value = a

}

class B(b: Int) {

val value = new A(b)

}

效果测试

val x = new B(1)

x = new B(1) // 错误,因为 x 为 val 修饰的,引用不可改变

x.value = new A(1) // 错误,因为 x.value 为 val 修饰的,引用不可改变

x.value.value = 1 // 正确,x.value.value 为 var 修饰的,可以重新赋值

事实上,var 修饰的对象引用可以改变,val 修饰的则不可改变,但对象的状态却是可以改变的。

可变与不可变的理解

我们知道 scala 中的 List 是不可变的,Map 是可变和不可变的。观察下面的例子

var 可变和 List 不可变的组合

var list = List("左","右")

list += "手"

理解就是

var list 指向的对象是 List("左","右")

后面修改 list 的指向,因为是可变的 var 修饰,list 又可以指向新的 List("左","右","手")

如果是以下(会报错的)

val list = List("左","右")

list += "手"

val var 与 Map 可变和不可变

var map = Map(

"左" -> 1,

"右" ->1,

)

map+=("手"->1)

val map=scala.collection.mutable.Map(

"左" -> 1,

"右" ->1,

)

map+=("手"->1)

理解

不可变的 Map 在添加元素的时候,原来的 Map 不变,生成一个新的 Map 来保存原来的 map+添加的元素。

可变的 Map 在添加元素的时候,并不用新生成一个 Map,而是直接将元素添加到原来的 Map 中。

val 不可变的只是指针,跟对象 map 没有关系。

数据类型




函数式编程


高阶函数

高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数。在 Scala 中函数是"一等公民"。

简单例子

val list=List(1,2,3,4)

val function= (x:Int) => x*2

val value=list.map(function)

方法为函数

def main(args: Array[String]): Unit = {

val list=List(1,2,3,4)

val value=list.map(function)

}

def function (x:Int)=x*2

返回函数的函数

def calculate(symbol:String): (String,String)=>String ={

symbol match {

case "拼接方式 1" => (a:String,b:String)=> s"拼接方式 1:$a , $b"

case "拼接方式 2" => (a:String,b:String)=> s"拼接方式 2: $b , $a"

}

}

val function: (String, String) => String = calculate("拼接方式 2")

println(function("大数据", "左右手"))

匿名函数

Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体。

使用匿名函数后,我们的代码变得更简洁了。

var inc = (x:Int) => x+1

var x = inc(7)-1

也可无参数

var user = () => println("大数据左右手")

闭包

闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。

闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。

简单理解就是:函数内部的变量不在其作用域时,仍然可以从外部进行访问。

val function= (x:Int) => x*2

闭包的实质就是代码与用到的非局部变量的混合

闭包 = 代码 + 用到的非局部变量

val fact=2

val function= (x:Int) => x*fact

函数柯里化

柯里化指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。

先定义一个简单的

def add(x:Int,y:Int)=x+y

使用

add(1,2)

函数变形(这种方式就叫柯里化)

def add(x:Int)(y:Int) = x + y

使用

add(1)(2)

实现过程

add(1)(2) 实际上是依次调用两个普通函数(非柯里化函数)

第一次调用使用一个参数 x,返回一个函数类型的值。

第二次使用参数 y 调用这个函数类型的值。

接收一个 x 为参数,返回一个匿名函数

接收一个 Int 型参数 y,函数体为 x+y。

def add(x:Int)=(y:Int)=>x+y

(1)

val result = add(1) // result= (y:Int)=>1+y

(2)

val sum = result(2)

(3)

sum=3


面向对象

类和对象

类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。

类可以带有类参数

类参数可以直接在类的主体中使用。类参数同样可以使用 var 作前缀,还可以使用 private、protected、override 修饰。scala 编译器会收集类参数并创造出带同样的参数的类的主构造器。,并将类内部任何既不是字段也不是方法定义的代码编译至主构造器中_大数据培训

class Test(val a: Int, val b: Int) {

//

}

样例类

case class 一般被翻译成样例类,它是一种特殊的类,能够被优化以用于模式匹配。

当一个类被声名为 case class 的时候。具有以下功能:

  1. 构造器中的参数如果不被声明为 var 的话,它默认的是 val 类型的。

  2. 自动创建伴生对象,同时在里面给我们实现子 apply 方法,使我们在使用的时候可以不直接使用 new 创建对象。

  3. 伴生对象中同样会帮我们实现 unapply 方法,从而可以将 case class 应用于模式匹配。

  4. 实现自己的 toString、hashCode、copy、equals 方法

case class person(

name:String,

age:Int

)

对象与伴生对象

Scala 单例对象是十分重要的,没有像在 Java 一样,有静态类、静态成员、静态方法,但是 Scala 提供了 object 对象,这个 object 对象类似于 Java 的静态类,它的成员、它的方法都默认是静态的。

定义单例对象并不代表定义了类,因此你不可以使用它来 new 对象。当单例对象与某个类共享同一个名称时,它就被称为这个类的伴生对象。

类和它的伴生对象必须定义在同一个源文件里。类被称为这个单例对象的伴生类。

类和它的伴生对象可以互相访问其私有成员。

object Test {

private var name="大数据"

def main(args: Array[String]): Unit = {

val test = new Test()

println(test.update_name())

}

}

class Test{

def update_name(): String ={

Test.name="左右手"

Test.name

}

}

特质(trait)

scala trait 相当于 java 的接口,实际上它比接口还功能强大。与接口不同的是,它还可以定义属性和方法的实现。

一般情况下 scala 的类只能够继承单一父类,但是如果是 trait 的话就可以继承多个,从结果来看就是实现了多重继承(关键字 with)。其实 scala trait 更像 java 的抽象类。

object Test extends UserImp with AddressImp {

override def getUserName(): String = ???

override def getAddress(): String = ???

}

trait UserImp{

def getUserName():String

}

trait AddressImp{

def getAddress():String

}

模式匹配

以 java 的 switch 为例,java 的 switch 仅仅会做一些基本类型的匹配,然后执行一些动作,并且是没有返回值的。

而 scala 的 pattern matching match 则要强大得多,除了可以匹配数值,同时它还能匹配类型。

def calculate(symbol:String): (String,String)=>String ={

symbol match {

case "拼接方式 1" => (a:String,b:String)=> s"拼接方式 1:$a , $b"

case "拼接方式 2" => (a:String,b:String)=> s"拼接方式 2: $b , $a"

}

}

让我吃惊的是(就短短几行)

快排

def quickSort(list: List[Int]): List[Int] = list match {

case Nil => Nil

case List() => List()

case head :: tail =>

val (left, right) = tail.partition(_ < head)

quickSort(left) ::: head :: quickSort(right)

}


归并

def merge(left: List[Int], right: List[Int]): List[Int] = (left, right) match {

case (Nil, _) => right

case (_, Nil) => left

case (x :: xTail, y :: yTail) =>

if (x <= y) x :: merge(xTail, right)

else y :: merge(left, yTail)

}


隐式转换

Scala 提供的隐式转换和隐式参数功能,是非常有特色的功能。是 Java 等编程语言所没有的功能。它可以允许你手动指定,将某种类型的对象转换成其他类型的对象。通过这些功能,可以实现非常强大,而且特殊的功能。

规则

(1)在使用隐式转换之前,需要用 import 把隐式转换引用到当前的作用域里或者就在作用域里定义隐式转换。

(2)隐式转换只能在无其他可用转换的前提下才能操作。如果在同一作用域里,对同一源类型定义一个以上的隐式转换函数,如果多种隐式转换函数都可以匹配,那么编译器将报错,所以在使用时请移除不必要的隐式定义。

数据类型的隐式转换

String 类型是不能自动转换为 Int 类型的,所以当给一个 Int 类型的变量或常量赋予 String 类型的值时编译器将报错。但是.....

implicit def strToInt(str: String) = str.toInt

def main(args: Array[String]): Unit = {

val a:Int="100"


print(a)

}

参数的隐式转换

所谓的隐式参数,指的是在函数或者方法中,定义一个用 implicit 修饰的参数,此时 Scala 会尝试找到一个指定类型的,用 implicit 修饰的对象,即隐式值,并注入参数。

object Test {private var name="大数据"implicit val test = new Testdef getName(implicit test:Test): Unit ={println(test.update_name())}def main(args: Array[String]): Unit = {getName}}class Test{def update_name(): String ={Test.name="左右手"Test.name}}

文章来源于大数据左右手

用户头像

@零度

关注

关注尚硅谷,轻松学IT 2021.11.23 加入

IT培训 www.atguigu.com

评论

发布
暂无评论
大数据培训 | Scala语言知识分享,直击面试_scala_@零度_InfoQ写作社区