scala介绍一下
- scala运行在java虚拟机上
- scala同时支持面向对象与函数式编程
- scala的代码比java的代码更加精简
- scala目前主要应用在spark开发
关于scala,你有什么想说的吗?scala可以用在哪些方面呢?
- 同上
scala懒加载问题怎么处理?
使用Lazy关键字进行懒加载操作
在一些情况中我们经常希望某些变量的初始化要延迟,并且表达式不会被重复计算。就像我们用Java实现一个懒汉式的单例。如:
打开一个数据库连接。这对于程序来说,执行该操作,代价式昂贵的,所以我们一般希望只有在使用其的引用时才初始化。(当然实际开发中用的是连接池技术)
为了缩短模块启动时间,可以将当前不需要的某些工作推迟执行。 保证对象中其他字段的初始化能优先执行。
object Test extends App {
lazy val x: Unit = {
println("computing x")
}
val y: Unit = {
println("computing y")
}
}
执行一下,结果控制台会输出
computing y
为什么呢?因为传值,会把值先计算处理,lazy
的就不会这样了
Scala有break吗,Case class了解吗,哪里用到过?
Scala没有break操作,但是可以实现break原理,需要创建Breaks对象实现内部的break方法就可以像java一样跳出语句,但是在模式匹配过程中不需要跳出匹配模式,因为模式匹配只能匹配其中一个结果值。
case class代表样例类,它和class类比较来说,可以不需要序列化,而class需要序列化操作,和object很类似,但是不同的是object不能传入参数,而case class可以带入参数,一般在做转换操作传参使用,比如DataSet操作的时候,转换RDD或者DataFream操作时候,可以使用case class进行参数的传递。
元组
def tupleDemo(): Unit = {
val tuple: (Int, Int, Int, Int, Int) = (1, 2, 3, 4, 9)
//访问tuple元素
println(tuple._4)
//tuple的遍历
for (x <- tuple.productIterator) {
println(x)
}
//转成迭代器类型后可做的操作就多了
println(tuple.productIterator.toList) //List(1, 2, 3, 4, 9)
}
隐式转换
隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型。
object Test extends App {
//本来这块是要报错的,但是调用了一个隐式转换,就是我们自己写的那个
val x:Int=3.6
implicit def double2Int(x:Double): Int ={
x.toInt
}
}
隐式转换应用场景
在scala语言中,隐式转换一般用于类型的隐式调用,亦或者是某个方法内的局部变量,想要让另一个方法进行直接调用,那么需要导入implicit关键字,进行隐式的转换操作,同时,在Spark Sql中,这种隐式转换大量的应用到了我们的DSL风格语法中,并且在Spark2.0版本以后,DataSet里面如果进行转换RDD或者DF的时候,那么都需要导入必要的隐式转换操作。
什么叫闭包
一个函数把外部的那些不属于自己的对象也包含(闭合)进来。
通俗的来说就是局部变量当全局变量来使用!!!
案例1:
def minusxy(x: Int) = (y: Int) => x - y
这就是一个闭包:
1) 匿名函数(y: Int) => x -y嵌套在minusxy函数中。
2) 匿名函数(y: Int) => x -y使用了该匿名函数之外的变量x
3) 函数minusxy返回了引用了局部变量的匿名函数
案例2
def minusxy(x: Int) = (y: Int) => x - y
val f1 = minusxy(10)
val f2 = minusxy(10)
println(f1(3) + f2(3))
此处f1,f2这两个函数就叫闭包。
解释一下Scala内的Option类型
- Option类型可以理解为是一个集合,这个集合只有一个元素
- Some和None是Option类型的子类
- Option类型主要为了避免java语言中null的问题
object Test extends App {
val map=Map("a"->1,"b"->7)
println(map.get("a"))//Some(1)
println(map.get("d"))//None
println(map.getOrElse("d", 0))//0
}
在Scala语言中,Option类型是一个特殊的类型,它是代表有值和无值的体现,内部有两个对象,一个是Some一个是None,Some代表有返回值,内部有值,而None恰恰相反,表示无值,比如,我们使用Map集合进行取值操作的时候,当我们通过get取值,返回的类型就是Option类型,而不是具体的值。
17.10 解释一下什么叫偏函数
偏函数表示用{}包含用case进行类型匹配的操作,这种操作一般用于匹配唯一的属性值,在Spark中的算子内经常会遇到,例
val rdd = sc.textFile(路径)
rdd.map{
case (参数)=>{返回结果}
}
手写Scala单例模式
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。
object Test extends App {
//证明的确是个单例,因为创建2个是相同的
private val singletone1: Singletone.type = Singletone.getInstance()
private val singletone2: Singletone.type = Singletone.getInstance()
println((singletone1 == singletone2))//true
}
//obejct 本身就是个单例,把自己返回下就行了
object Singletone{
val singletone: Singletone.type =Singletone
def getInstance(): Singletone.type ={
singletone
}
}
解释一下柯里化
多个参数变成一个参数.比如
举个例子,顺便看下隐式
object Test extends App {
//隐式参数的位置要在调用隐式之前
implicit val s:Int=5
add1(3,4)//7
add2(3)(4)//7
add3(3)//8
def add1(x:Int,y:Int): Unit ={
println(x+y)
}
//对上面的函数柯里化大概就是下面这样
def add2(x:Int)(y:Int): Unit ={
println(x+y)
}
//隐式与柯里化
def add3(x:Int)(implicit y:Int): Unit ={
println(x+y)
}
}
Scala中的模式匹配和Java的匹配模式的区别
- java匹配的类型有限(整型,枚举),scala则可以匹配很多类型.String、Array、List、Class
- java需要break跳出匹配模式,否则会进入下一个判断,scala不需要
举个例子
object Test extends App {
val name=StdIn.readLine()
name match {
case "libai" =>println("this is libai")
case "dufu"=> println("this is dufu")
case _=> println("this is nothing")
}
}
Scala中的伴生类和伴生对象是怎么一回事
- 名字一样则互为伴生
- 伴生类和伴生对象要处在同一个源文件中
- 伴生对象和伴生类可以互相访问其私有成员
17.15 谈谈Scala的尾递归
正常得递归,每一次递归步骤,需要保存信息到堆栈中去,当递归步骤很多的时候,就会导致内存溢出
而尾递归,就是为了解决上述的问题,在尾递归中所有的计算都是在递归之前调用,编译器可以利用这个属性避免堆栈错误,尾递归的调用可以使信息不插入堆栈,从而优化尾递归
例如:
5 + sum(4) // 暂停计算 => 需要添加信息到堆栈
5 + (4 + sum(3))
5 + (4 + (3 + sum(2)))
5 + (4 + (3 + (2 + sum(1))))
5 + (4 + (3 + (2 + 1)))
15
tailSum(4, 5) // 不需要暂停计算
tailSum(3, 9)
tailSum(2, 12)
tailSum(1, 14)
tailSum(0, 15)
15
函数中 Unit是什么意思?
- unit就等同于java的void
- 一般来说每一个返回void的java方法对应一个返回Unit的Scala方法。
Scala中的to和until 有什么区别?
例如1to10,它会返回Range(1,2,3,4,5,6,7,8,9,10),而1until 10 ,它会返回Range(1,2,3,4,5,6,7,8,9)
也就是说to包头包尾,而until 包头不包尾!
object Test extends App {
var list=mutable.ListBuffer[Int]()
for (x:Int <- 1 to 10) {
list+=x
}
println(list.toList)//List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
}
object Test extends App {
var list=mutable.ListBuffer[Int]()
for (x:Int <- 1 until 10) {
list+=x
}
println(list.toList)//List(1, 2, 3, 4, 5, 6, 7, 8, 9)
}
var,val和def三个关键字之间的区别?
- var声明变量
- val声明常量
- def声明方法
- 还有一个lazy val(惰性val)声明,意思是当需要计算时才使用,避免重复计算
object Test extends App {
lazy val x: Unit = {
println("computing x")
}
val y: Unit = {
println("computing y")
}
}
执行一下,结果控制台会输出
computing y
为什么呢?因为传值,会把值先计算处理,lazy
的就不会这样了
trait(特质)和abstract class(抽象类)的区别?
(1)只能继承一个抽象类,但是可以寄出多个trait
class Test() extends Man with A with B{}
abstract class Man(){}
abstract class Woman(){}
trait A{}
trait B{}
(2)抽象类有带参数的构造函数,特质不行(如 trait t(i:Int){} ,这种声明是错误的) 3.0以后的版本可以,所以万事不绝对
unapply 和apply方法的区别, 以及各自使用场景?
先讲一个概念——提取器,它实现了构造器相反的效果,构造器从给定的参数创建一个对象,然而提取器却从对象中提取出构造该对象的参数,scala标准库预定义了一些提取器,如上面提到的样本类中,会自动创建一个伴生对象(包含apply和unapply方法)。 为了成为一个提取器,unapply方法需要被伴生对象。
apply
方法是为了自动实现样本类的对象,无需new
关键字。- 不是样例类的自己写个
apply
方法,创建对象时也可以不写new
.
object Test extends App {
val man=Man("xiaoming")
man.run()//xiaoming is running
}
object Man{
def apply(name: String): Man = new Man(name)
}
class Man(name:String){
def run(): Unit ={
println(s"$name is running")
}
}
17.21 Scala类型系统中Nil, Null, None, Nothing四个类型的区别?
Null是一个trait(特质),是所以引用类型AnyRef的一个子类型,null是Null唯一的实例。
Nothing也是一个trait(特质),是所有类型Any(包括值类型和引用类型)的子类型,它不在有子类型,它也没有实例,实际上为了一个方法抛出异常,通常会设置一个默认返回类型。
Nil代表一个List空类型,等同List[Nothing]
None是Option monad的空标识
17.22 call-by-value和call-by-name求值策略的区别?
(1)call-by-value是在调用函数之前计算;
(2) call-by-name是在需要时计算
示例代码
//声明第一个函数
def func(): Int = {
println("computing stuff....")
42 // return something
}
//声明第二个函数,scala默认的求值就是call-by-value
def callByValue(x: Int) = {
println("1st x: " + x)
println("2nd x: " + x)
}
//声明第三个函数,用=>表示call-by-name求值
def callByName(x: => Int) = {
println("1st x: " + x)
println("2nd x: " + x)
}
//开始调用
//call-by-value求值
callByValue(func())
//输出结果
//computing stuff....
//1st x: 42
//2nd x: 42
//call-by-name求值
callByName(func())
//输出结果
//computing stuff....
//1st x: 42
//computing stuff....
//2nd x: 42
yield如何工作?comprehension(推导式)的语法糖是什么操作?
- yield是comprehensions的一部分,是多个操作(foreach, map, flatMap, filter or withFilter)的composition语法糖
val x=for(x<- 1 to 10) yield x*2
println(x)//Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
//等价于
//yield就是个语法糖,可以被其他的算子所替代
private val ints: immutable.IndexedSeq[Int] = (1 to 10).map((_ * 2))
println(ints)//Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
什么是高阶函数?
高阶函数指能接受或者返回其他函数的函数,scala中的filter map flatMap函数都能接受其他函数作为参数。
17.25 scala全排序过滤字段,求 1 to 4 的全排序, 2不能在第一位, 3,4不能在一起
import util.control.Breaks._
- 1 to 4 的全排序
- 2不能在第一位
- 3,4不能在一起
object LocalSpark extends App{
override def main(args: Array[String]): Unit = {
List(1,2,3,4).permutations.filter(list=>list(0) != 2).map(list=>{
var num =0
breakable{
for(x<- 0 to (list.size-1)){
if(list(x)==3 && x<3 && list(x+1)==4) break
if(list(x)==3 && x>0 && list(x-1)==4) break
num +=1
}
}
if(num <4){
List()
}else{
list
}
}).filter(list=>list.size>3).foreach(println(_))
}
}
结果
List(1, 3, 2, 4)
List(1, 4, 2, 3)
List(3, 1, 2, 4)
List(3, 1, 4, 2)
List(3, 2, 1, 4)
List(3, 2, 4, 1)
List(4, 1, 2, 3)
List(4, 1, 3, 2)
List(4, 2, 1, 3)
List(4, 2, 3, 1)