Scala面试题 看过1

本文介绍了Scala编程语言的一些核心概念,包括循环语句的类型如while,do-while,for,以及变量的var,val和def的区别。深入讨论了特质和抽象类的差异,对象和类的特性,特别是伴生对象的概念。此外,还涵盖了Option类型、尾递归、闭包、模式匹配以及函数柯里化等高级主题,展示了Scala在函数式编程中的独特之处。
摘要由CSDN通过智能技术生成

6.循环语句哪三种,分别语法是什么?怎样退出循环?

while(condition){   statement(s);}

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。condition 可以是任意的表达式,当为任意非零值时都为 true。当条件为 true 时执行循环。 当条件为 false 时,退出循环,程序流将继续执行紧接着循环的下一条语句。

do {   statement(s);} while( condition );

for( var x <- Range ){   statement(s);}

以上语法中,Range 可以是一个数字区间表示 i to j ,或者 i until j。左箭头 <- 用于为变量 x 赋值。

for( var x <- List ){   statement(s);}

以上语法中, List 变量是一个集合,for 循环会迭代所有集合的元素。

Q1 var,val和def三个关键字之间的区别?

  • 答:var是变量声明关键字,类似于Java中的变量,变量值可以更改,但是变量类型不能更改。 val常量声明关键字。 def 关键字用于创建方法(注意方法和函数的区别) 还有一个lazy val(惰性val)声明,意思是当需要计算时才使用,避免重复计算

 

Q2 trait(特质)和abstract class(抽象类)的区别?

  • 答:(1)一个类只能集成一个抽象类,但是可以通过with关键字继承多个特质; (2)抽象类有带参数的构造函数,特质不行(如 trait t(i:Int){} ,这种声明是错误的)

 

Q3 object和class的区别?

  • 答:object是类的单例对象,开发人员无需用new关键字实例化。如果对象的名称和类名相同,这个对象就是伴生对象(深入了解请参考问题Q7)

 

Q4 case class (样本类)是什么?

  • 答:样本类是一种不可变且可分解类的语法糖,这个语法糖的意思大概是在构建时,自动实现一些功能。样本类具有以下特性: (1)自动添加与类名一致的构造函数(这个就是前面提到的伴生对象,通过apply方法实现),即构造对象时,不需要new; (2)样本类中的参数默认添加val关键字,即参数不能修改; (3)默认实现了toString,equals,hashcode,copy等方法; (4)样本类可以通过==比较两个对象,并且不在构造方法中定义的属性不会用在比较上。

 

 

Q6 unapply 和apply方法的区别, 以及各自使用场景?

  • 答:先讲一个概念——提取器,它实现了构造器相反的效果,构造器从给定的参数创建一个对象,然而提取器却从对象中提取出构造该对象的参数,scala标准库预定义了一些提取器,如上面提到的样本类中,会自动创建一个伴生对象(包含apply和unapply方法)。 为了成为一个提取器,unapply方法需要被伴生对象。 apply方法是为了自动实现样本类的对象,无需new关键字。

 

Q7 伴生对象是什么?

  • 答:前面已经提到过,伴生对象就是与类名相同的对象,伴生对象可以访问类中的私有量,类也可以访问伴生对象中的私有方法,类似于Java类中的静态方法。伴生对象必须和其对应的类定义在相同的源文件。

Q8 Scala类型系统中Nil, Null, None, Nothing四个类型的区别?

  • 答:Null是一个trait(特质),是所以引用类型AnyRef的一个子类型null是Null唯一的实例 Nothing也是一个trait(特质),是所有类型Any(包括值类型和引用类型)的子类型,它不在有子类型,它也没有实例,实际上为了一个方法抛出异常,通常会设置一个默认返回类型。 Nil代表一个List空类型,等同List[Nothing] None是Option monad的空标识(深入了解请参考问题Q11)

 

Q9 Unit类型是什么?

  • 答:Unit代表没有任何意义的值类型,类似于java中的void类型,他是anyval的子类型,仅有一个实例对象"( )"

 

Q10 call-by-value和call-by-name求值策略的区别?

  • 答:(1)call-by-value是在调用函数之前计算; (2) call-by-name是在需要时计算

 

Q11 Option类型的定义和使用场景?

  • 答:在Java中,null是一个关键字,不是一个对象,当开发者希望返回一个空对象时,却返回了一个关键字,为了解决这个问题,Scala建议开发者返回值是空值时,使用Option类型,在Scala中null是Null的唯一对象,会引起异常,Option则可以避免Option有两个子类型,Some和None(空值)

 

Q12 yield如何工作?

  • 答:yield用于循环迭代中生成新值,yield是comprehensions的一部分,是多个操作(foreach, map, flatMap, filter or withFilter)的composition语法糖。(深入了解请参考问题Q14)

Q13 解释隐示参数的优先权

  • 答:在Scala中implicit的功能很强大。当编译器寻找implicits时,如果不注意隐式参数的优先权,可能会引起意外的错误。因此编译器会按顺序查找隐式关键字。顺序如下: (1)当前类声明的implicits ; (2)导入包中的 implicits; (3)外部域(声明在外部域的implicts); (4)inheritance (5)package object (6)implicit scope like companion objects

 

Q14 comprehension(推导式)的语法糖是什么操作?

  • 答:comprehension(推导式)是若干个操作组成的替代语法。如果不用yield关键字,comprehension(推导式)可以被forech操作替代,或者被map/flatMap,filter代替。

 

 

Q16 什么是vaule class?

  • 答:开发时经常遇到这个的问题,当你使用integer时,希望它代表一些东西,而不是全部东西,例如,一个integer代表年龄,另一个代表高度。由于上述原因,我们考虑包裹原始类型生成一个新的有意义的类型(如年龄类型和高度类型)。 Value classes 允许开发者安全的增加一个新类型,避免运行时对象分配。有一些 必须进行分配的情况 and 限制,但是基本的思想是:在编译时,通过使用原始类型替换值类实例,删除对象分配。

 

Q17 Option ,Try 和 Either 三者的区别?

  • 答:这三种monads允许我们显示函数没有按预期执行的计算结果。 Option表示可选值,它的返回类型是Some(代表返回有效数据)或None(代表返回空值)。 Try类似于Java中的try/catch,如果计算成功,返回Success的实例,如果抛出异常,返回Failure。 Either可以提供一些计算失败的信息,Either有两种可能返回类型:预期/正确/成功的 和 错误的信息。

 

Q18 什么是函数柯里化?

  • 答:柯里化技术是一个接受多个参数的函数转化为接受其中几个参数的函数。经常被用来处理高阶函数。

Q19 什么是尾递归?

正常递归,每一次递归步骤,需要保存信息到堆栈里面,当递归步骤很多时,导致堆栈溢出。 尾递归就是为了解决上述问题,在尾递归中所有的计算都是在递归之前调用, 编译器可以利用这个属性避免堆栈错误,尾递归的调用可以使信息不插入堆栈,从而优化尾递归 使用 @tailrec 标签可使编译器强制使用尾递归。

Q20 什么是高阶函数?

  • 答:高阶函数指能接受或者返回其他函数的函数,scala中的filter map flatMap函数都能接受其他函数作为参数。

一、scala语言有什么特点?什么是函数式编程?有什么优点?

  1、scala语言集成面向对象和函数式编程

  2、函数式编程是一种典范,将电脑的运算视作是函数的运算。

  3、与过程化编程相比,函数式编程里的函数计算可以随时调用。

  4、函数式编程中,函数是一等功明。

二、scala中的闭包   

  1、定义:你可以在任何作用域内定义函数:包,类甚至是另一个函数或方法。在函数体内,可以访问到相应作用域内地任何变量。(重点)函数可以在变量不再处于作用域内时被调用。

   例如:

      def mulBy(factor:Double) = (x:Double) => factor * x

      //开始调用

      val tripe = mulBy(3)

      val half = mulBy(0.5)

      println(tripe(14) + " " + half(14))

    这就是一个闭包

三、scala中的柯里化

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

   例如:

      def mul(x:Int,y:Int) = x * y  //该函数接受两个参数

      def mulOneAtTime(x:Int) = (y:Int) => x * y  //该函数接受一个参数生成另外一个接受单个参数的函数

      这样的话,如果需要计算两个数的乘积的话只需要调用:

      mulOneAtTime(5)(4)

    这就是函数的柯里化

四、scala中的模式匹配

    scala的模式匹配包括了一系列的备选项,每个替代项以关键字大小写为单位,每个替代方案包括一个模式或多个表达式,如果匹配将会进行计算,箭头符号=>将模式与表达式分离

    例如:

      obj match{

        case 1 => "one"

        case 2 => "two"

        case 3 => "three"

        case _ => default

      }

五、case class和class的区别

    case class:

      是一个样本类,样本类是一种不可变切可分解类的语法糖,也就是说在构建的时候会自动生成一些语法糖,具有以下几个特点:

      1、自动添加与类名一致的构造函数(也就是半生对象,通过apply方法实现),也就是说在构造对象的时候不需要使用new关键字

      2、样本类中的参数默认是val关键字,不可以修改

      3、默认实现了toString,equals,hashcode,copy方法

      4、样本类可以通过==来比较两个对象,不在构造方法内地二属性不会用在比较上

    class:

      class是一个类

      1、class在构造对象的时候需要使用new关键字才可以。

六、谈谈scala中的隐式转换

    所谓的隐式转换函数(implicit conversion function)指的事那种以implicit关键字申明的带有单个参数的函数,这样的函数会被自动的应用,将值从一种类型转换为另一种类型

    比如:需要把整数n转换成分数n/1

       implicit def int2Fraction(n:Int) = Fraction(n,1)

       这样就可以做如下表达式求职:

       val result = 3 * Fraction(4,5)

    此时,隐士转换函数将整数3转换成一个Fraction对象,这个对象接着又被乘以Fraction(4,5)

七、scala中的伴生类和伴生对象是怎么一回事

    在scala中,单例对象与类同名时,该对象被称为该类的伴生对象,该类被称为该对象的伴生类。

    伴生类和伴生对象要处在同一个源文件中

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

    不与伴生类同名的对象称之为孤立对象

八、scala和java 的区别

    1、变量申明:

        scala:只需要申明是val或是var,具体的类型(比如String,Int,Double等等),由编译器自行推断

        java:  需要在变量前面先注明变量的类型

    2、返回值:

        scala:申明返回值是在后面,并且不需要return语句,非要用,也不是不可以

        java:  如果有返回值,需要return语句

    3、结束符

        scala:不需要使用分号作为结束符

        java:  每个语句结束后需要分号作为结束符

    4、循环

        scala:循环语句可以用于守卫

        java:  不可以这么写

    5、通配符:

        scala:_

        java:   *

    6、构造器

        scala:构造器名称为this,scala的辅助构造器之前需要有一个主构造器或者其他辅助构造器,并且scala的构造器参数可以直接放在类的后面

        java:  构造器名称需要与类名称一样

    7、内部类

        scala:scala实例化的内部类是不同的,可以使用类型投影,例如 Network#Person表示Network的Person类

        java:内部类从属于外部类

    8、接口

        scala:scala中接口称为特质(trait),特质中是可以写抽象方法,也可以写具体的方法体以及状态。且类是可以实现多个特质的。

            特质中未被实现的方法默认就是抽象的

            子类的实现或继承统一使用的事extends关键字,如果需要实现或继承多个使用with关键字

            特质中可以有构造器

           特质可以继承普通的类,并且这个类称为所有继承trait的父类

        java:  java中的接口(interface),接口中的方法只能是抽象方法,不可以写具体包含方法体的方法

           接口中不能有抽象的属性,且属性的修饰符都是public static final

           类实现接口需要使用implements关键字,实现多个接口,需要用逗号隔开

             接口中不可以有构造器

             接口不可以继承普通的类 

    9、赋值

        scala:scala中的赋值语句返回结果是unit的不可以串联,例如x=y=1,这样是有问题的,x并没有被赋值为1

        java:  x=y=1,这样是没问题的

九、谈谈scala的尾递归

    正常得递归,每一次递归步骤,需要保存信息到堆栈中去,当递归步骤很多的时候,就会导致内存溢出

    而尾递归,就是为了解决上述的问题,在尾递归中所有的计算都是在递归之前调用,编译器可以利用这个属性避免堆栈错误,尾递归的调用可以使信息不插入堆栈,从而优化尾递归

    例如:

1
2
3
4
5
6
7
正常递归:def sum(n:Int):Int = {
    if (n == 0){
      n
    }else{
      n + sum(n - 1)
    }
  }//执行结果sum(5)
5 + sum(4) // 暂停计算 => 需要添加信息到堆栈 5 + (4 + sum(3)) 5 + (4 + (3 + sum(2))) 5 + (4 + (3 + (2 + sum(1)))) 5 + (4 + (3 + (2 + 1))) 15
1
2
3
4
5
6
7
8
9
尾递归
@tailrec  //告诉编译器,强制使用尾递归
  def tailSum(n:Int,acc:Int = 0):Int = {
    if (n ==0 ){
      acc
    }else{
      tailSum(n - 1,acc + n)
    }
  }//执行结果tailSum(5) // tailSum(5, 0) 默认值是0
tailSum(4, 5) // 不需要暂停计算 tailSum(3, 9) tailSum(2, 12) tailSum(1, 14) tailSum(0, 15) 15

for( var x <- List      if condition1; if condition2...   ){   statement(s);}

以上是在 for 循环中使用过滤器的语法。

var retVal = for{ var x <- List     if condition1; if condition2...}yield x

你可以将 for 循环的返回值作为一个变量存储。

大括号中用于保存变量和条件,retVal 是变量, 循环中的 yield 会把当前的元素记下来,保存在集合中,循环结束后将返回该集合。

当在循环中使用 break 语句,在执行到该语句时,就会中断循环并执行循环体之后的代码块。

// 导入以下包import scala.util.control._// 创建 Breaks 对象val loop = new Breaks;// 在 breakable 中循环loop.breakable{    // 循环    for(...){       ....       // 循环中断       loop.break;   }}

10.什么是闭包?(******************)

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

var factor = 3  val multiplier = (i:Int) => i * factor 

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

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

17.异常报错的语法?

import java.io.FileReaderimport java.io.FileNotFoundExceptionimport java.io.IOExceptionobject Test {   def main(args: Array[String]) {      try {         val f = new FileReader("input.txt")      } catch {         case ex: FileNotFoundException => {            println("Missing file exception")         }         case ex: IOException => {            println("IO Exception")         }      } finally {         println("Exiting finally...")      }   }}

18.Array、ArrayBuffer,谁是定长?谁是变长?

Array是定长、ArrayBuffer是变长

19.什么是隐式转换函数?什么场景下用?怎样定义?

我们经常引入第三方库,但当我们想要扩展新功能的时候通常是很不方便的,因为我们不能直接修改其代码。scala提供了隐式转换机制和隐式参数帮我们解决诸如这样的问题。

implicit def file2Array(file: File): Array[String] = file.lines

22.对象是什么?类是什么?怎样在IDEA创建文件?

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

new->scala class

 

 

17.3 scala懒加载问题怎么处理?

​使用Lazy关键字进行懒加载操作

​在一些情况中我们经常希望某些变量的初始化要延迟,并且表达式不会被重复计算。就像我们用Java实现一个懒汉式的单例。如:

打开一个数据库连接。这对于程序来说,执行该操作,代价式昂贵的,所以我们一般希望只有在使用其的引用时才初始化。(当然实际开发中用的是连接池技术)
为了缩短模块启动时间,可以将当前不需要的某些工作推迟执行。
保证对象中其他字段的初始化能优先执行。

17.4 Scala有break吗,Case class了解吗,哪里用到过? 

​Scala没有break操作,但是可以实现break原理,需要创建Breaks对象实现内部的break方法就可以像java一样跳出语句,但是在模式匹配过程中不需要跳出匹配模式,因为模式匹配只能匹配其中一个结果值。

​case class代表样例类,它和class类比较来说,可以不需要序列化,而class需要序列化操作,和object很类似,但是不同的是object不能传入参数,而case class可以带入参数,一般在做转换操作传参使用,比如DataSet操作的时候,转换RDD或者DataFream操作时候,可以使用case class进行参数的传递。

17.5 元组

  1. 元组的创建
val tuple1 = (1, 2, 3, "heiheihei")

println(tuple1)
  1. 元组数据的访问,注意元组元素的访问有下划线,并且访问下标从1开始,而不是0
val value1 = tuple1._4

println(value1)
  1. 元组的遍历
方式1:
for (elem <- tuple1.productIterator  ) {
   print(elem)
}
方式2:
tuple1.productIterator.foreach(i => println(i))
tuple1.produIterator.foreach(print(_))

17.6 隐式转换

​隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型。

implicit def a(d: Double) = d.toInt
// 当执行这句代码的时候,内部会自动调用我们自己编写好的隐式转换方法
val i1: Int = 3.5
println(i1)

17.7 隐式转换应用场景

​在scala语言中,隐式转换一般用于类型的隐式调用,亦或者是某个方法内的局部变量,想要让另一个方法进行直接调用,那么需要导入implicit关键字,进行隐式的转换操作,同时,在Spark Sql中,这种隐式转换大量的应用到了我们的DSL风格语法中,并且在Spark2.0版本以后,DataSet里面如果进行转换RDD或者DF的时候,那么都需要导入必要的隐式转换操作。

 

17.9 解释一下Scala内的Option类型

​在Scala语言中,Option类型是一个特殊的类型,它是代表有值和无值的体现,内部有两个对象,一个是Some一个是None,Some代表有返回值,内部有值,而None恰恰相反,表示无值,比如,我们使用Map集合进行取值操作的时候,当我们通过get取值,返回的类型就是Option类型,而不是具体的值。

17.10 解释一下什么叫偏函数

偏函数表示用{}包含用case进行类型匹配的操作,这种操作一般用于匹配唯一的属性值,在Spark中的算子内经常会遇到,例

val rdd = sc.textFile(路径)
rdd.map{
    case (参数)=>{返回结果}
}

17.11 手写Scala单例模式  没看

​单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。

/**
  * scala中关于单例的模拟
  * object中的属性和方法都可以当做类似java中的静态成员,都可以通过
  * object.成员来进行调用
  */
object SingletonOps {
  def main(args: Array[String]): Unit = {
    val singleton1 = Singleton.getInstance
    val singleton2 = Singleton.getInstance
    println(singleton1 == singleton2)
    singleton1.index = 5
    println("singleton1.index: " + singleton1.index)
    println("singleton2.index: " + singleton2.index)
  }
}
 
object Singleton {
  private val singleton = Singleton;
  def getInstance = singleton
  var index = 1
} 

17.12 解释一下柯里化

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

例如:

def mul(x:Int,y:Int) = x * y  //该函数接受两个参数
def mulOneAtTime(x:Int) = (y:Int) => x * y  //该函数接受一个参数生成另外一个接受单个参数的函数
这样的话,如果需要计算两个数的乘积的话只需要调用:
mulOneAtTime(5)(4)
这就是函数的柯里化

17.13 Scala中的模式匹配和Java的匹配模式的区别

scala的模式匹配包括了一系列的备选项,每个替代项以关键字大小写为单位,每个替代方案包括一个模式或多个表达式,如果匹配将会进行计算,箭头符号=>将模式与表达式分离

    例如:

obj match{
      case 1 => "one"
      case 2 => "two"
      case 3 => "three"
      case _ => default
    }


​而Java的匹配模式是switch case匹配方式,它内部匹配的类型有局限性,并且需要用Break跳出匹配模式,而Scala中只会匹配其中一个结果,同时匹配类型居多,如String、Array、List、Class等..

17.19 trait(特质)和abstract class(抽象类)的区别?

(1)一个类只能集成一个抽象类,但是可以通过with关键字继承多个特质;

(2)抽象类有带参数的构造函数,特质不行(如 trait t(i:Int){} ,这种声明是错误的)

17.20 unapply 和apply方法的区别, 以及各自使用场景?

​先讲一个概念——提取器,它实现了构造器相反的效果,构造器从给定的参数创建一个对象,然而提取器却从对象中提取出构造该对象的参数,scala标准库预定义了一些提取器,如上面提到的样本类中,会自动创建一个伴生对象(包含apply和unapply方法)。 为了成为一个提取器,unapply方法需要被伴生对象。 apply方法是为了自动实现样本类的对象,无需new关键字。

17.21 Scala类型系统中Nil, Null, None, Nothing四个类型的区别?

​Null是一个trait(特质),是所以引用类型AnyRef的一个子类型,null是Null唯一的实例。

​Nothing也是一个trait(特质),是所有类型Any(包括值类型和引用类型)的子类型,它不在有子类型,它也没有实例,实际上为了一个方法抛出异常,通常会设置一个默认返回类型。

​Nil代表一个List空类型,等同List[Nothing]

​None是Option monad的空标识

 

17.23 yield如何工作?comprehension(推导式)的语法糖是什么操作?

yield用于循环迭代中生成新值,yield是comprehensions的一部分,是多个操作(foreach, map, flatMap, filter or withFilter)的composition语法糖。

​comprehension(推导式)是若干个操作组成的替代语法。如果不用yield关键字,comprehension(推导式)可以被forech操作替代,或者被map/flatMap,filter代替。

示例代码:
// 三层循环嵌套
for {
  x <- c1
  y <- c2
  z <- c3 if z > 0
} yield {...}
//上面的可转换为
c1.flatMap(x => c2.flatMap(y => c3.withFilter(z => z > 0).map(z => {...})))

17.24 什么是高阶函数?

​高阶函数指能接受或者返回其他函数的函数,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) 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值