scala面试题简要总结

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的时候,那么都需要导入必要的隐式转换操作。

什么叫闭包

​ 一个函数把外部的那些不属于自己的对象也包含(闭合)进来。

​ 通俗的来说就是局部变量当全局变量来使用!!!

案例1def 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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值