1.什么是scala语言?
Scala 是一门多范式(multi-paradigm)的编程语言,但它并不是一门新的编程语言。设计初衷是要集成面向对象编程和函数式编程的各种特性。
Scala 运行在Java虚拟机上,并兼容现有的Java程序。
Scala 源代码被编译成Java字节码,所以它可以运行于JVM之上,并可以调用现有的Java类库。
2.Scala 特性?
面向对象特性
Scala是一种纯面向对象的语言,每个值都是对象。对象的数据类型以及行为由类和特质描述。
类抽象机制的扩展有两种途径:一种途径是子类继承,另一种途径是灵活的混入机制。这两种途径能避免多重继承的种种问题。
函数式编程
Scala也是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型。
更进一步,程序员可以利用Scala的模式匹配,编写类似正则表达式的代码处理XML数据。
3.函数式编程特点?
- 泛型类 T
- 协变和逆变 : 向上转型 向下转型
- 标注
- 类型参数的上下限约束 : 上边界和下边界
- -把类别和抽象类型作为对象成员
- 复合类型
- 引用自己时显式指定类型
- 视图:视图界定 int --> richInt – > comparable Ordered
- 多态方法
4.scala 数据类型?
数据类型 | 描述 |
---|---|
Byte | 8位有符号补码整数。数值区间为 -128 到 127 |
Short | 16位有符号补码整数。数值区间为 -32768 到 32767 |
Int | 32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long | 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
Float | 32 位, IEEE 754 标准的单精度浮点数 |
Double | 64 位 IEEE 754 标准的双精度浮点数 |
Char | 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF |
String | 字符序列 |
Boolean | true或false |
Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null | null 或空引用 |
Nothing | Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。 |
Any | Any是所有其他类的超类 |
AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 |
注:Unit占不占用底层存储,仁者见仁智者见智
5.scala 变量声明,使用?区别?
通常用var、val表示
var:通常表示变量
val:通常表示常量
声明变量实例如下:
var myVar : String = "Foo"
var myVar : String = "Too"
声明常量实例如下:
val myVal : String = "Foo"
6.块语句有什么特点?
{} 把最后一个值或者表达式 当做返回值
7.scala break怎么使用?
先导包,再创建对象
不完善版本
def test01{
//在何处导包并无大碍,但是,最起码得在调用之前
import scala.util.control.Breaks
//创建Breaks
val breaks = new Breaks
var num = 0
while (num < 5) {
println("num:" + num)
// num ++ ,scala不存在++
num += 1
if(num == 4){
//调用break方法
break
//不能向java一样,直接break
//需要先导包,再创建Breaks对象,最后调用break方法
}
}
//当是以上调用时,结果会输出,但是会出现报错
//Exception in thread "main" scala.util.control.BreakControl
)
}
完善版本
import scala.util.control.Breaks
//创建Breaks
val breaks = new Breaks
var num = 0
//要想不报错,当如下
breaks.breakable(
while (num < 5) {
println("num" + num)
num += 1
if(num == 3){
breaks.break()
}
}
//breakable,实际上是将异常给捕捉了
8.方法和函数有什么区别?
核心:函数是头等公民。
Scala 有方法与函数,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。
Scala 中的方法跟 Java 的类似,方法是组成类的一部分。
Scala 中的函数则是一个完整的对象,Scala 中的函数其实就是继承了 Trait 的类的对象。
Scala 中使用 val 语句可以定义函数,def 语句定义方法。
例如
var fun1 = (a:Int,b:Int) => {a + b}
def fun2(a:Int,b:Int) :Int = {a + b}
//两种方法执行效果一致
9.lazy 特点?
懒加载,延迟操作, 只有被调用的时候才会被执行
def test02{
val age = 18
var name = "unknow"
// println("name:" + name)//unknow
lazy val res = if(age > 18){
name = "秦孝公"
}else{
name = "秦昭襄王"
}
println(name)//unknow
res
//lazy懒执行,只有当调用它时,才会执行
println(name)//"秦昭襄王"
10.数组可变不可变?
可变不可变指的是数组的长度
Array不可变
ArrayBuffer可变
11.list 可变不可变?
可变不可变指的是里面的元素
list不可变
listBuffer可变
附:
list 添加元素的方式:
方式 | 描述 |
---|---|
+: | 预添加 |
:: | 在列表开头添加元素 |
::: | 在列表开头添加指定列表的元素 |
:+ | 复制添加元素后列表。 |
++: | 添加到最前面 |
++ | 从列表的尾部添加另外一个列表 |
scala 的集合有: list set map seq
12.map 可变不可变?
K V 值能不能被改变
创建的方式: 映射 -> 元组(,)
例:
取出map的值,get(),getOrElse()
val map01= Map("科比"->"小飞侠","邓肯"->"石佛")
val v1 = map01.get("科比")
println("v1:" + v1)//Some(小飞侠)
println("v1.get:" + v1.get)//小飞侠
//当key不存在时,会把设置的默认值输出出来
val v2 = map01.getOrElse("as", "不存在")
println("v2" + v2)
val v3 = map01.get("ss")
println("v3:" + v3)//None
}
遍历map
//遍历1
for(t <- map02){
println(t._1 + ":" + t._2)
}
//遍历2
for((k,v) <- map02){
println(k + "--" + v)
}
//遍历3
map02.foreach(f=>{
println(f._1 + "===" + f._2)
})
对不可变Map进行更改
map01("科比")="kobe"
//会出现:
//value update is not a member of scala.collection.immutable.Map[String,String]
对可变map操作
//要相对map进行更改,必须导如下的包
import scala.collection.mutable.Map
val map03 = Map("卡特"->"UFO","保罗"->"cp3")
map03("卡特")="半神"
println(map03("卡特"))
//map的API操作
val res01 = map03.contains("卡特")
println("该map包含kate" + res01)
map03.remove("卡特")
val res02 = map03.contains("卡特")
println("该map包含kate" + res02)
13.zip 操作?
zip把两个列表的元素合成一个由元素对组成的列表里。把多个list合并成包含元组的list里面,也戏称拉链操作。
注:如果两个集合个数不对应,会造成数据丢失。
集合不限于 List, 也可以是其它集合比如 Array
如果要取出合并后的各个对偶元组的数据,可以遍历(按照元组的方式)
例:
val name = Array("姚明", "韦德", "波什")
val nickname = Array("小巨人", "闪电侠", "龙王")
//无论是哪个集合的元素少了,不对等,zip只会输出配对的
val newName = name.zip(nickname)
newName.foreach(println)
14.tuple 元组?
- Map:是一个集合,集合中的每一个元素都有key和value(共两个);
- 元组也是一个集合,集合中的每一个元素都有22值
- 在 Scala 中,元组是一个可以容纳不同类型元素的类。 元组是不可变的。
- 当我们需要从函数返回多个值时,元组会派上用场。
- Scala 中的元组包含一系列类:Tuple2,Tuple3等,直到 Tuple22。 因此,当我们创建一个包含 n 个元素(n 位于
2 和 22 之间)的元组时, - Scala 基本上就是从上述的一组类中实例化 一个相对应的类,使用组成元素的类型进行参数化。
例:
val tuple = ("秦惠文王", 23, 170.0f, 150.0)
//遍历1
tuple.productIterator.foreach(println)
//遍历2
for(i <- tuple.productIterator){
println(i)
}
val stu = (("shanshan", 18, 170.0f, 100.00), ("老毕", 28, 180.0f, 180.00))
println("谁是天下第一:" + stu._2._1)
//对偶元组
val pairs = ("明成祖", "朱棣")
15.hashSet hashMap
hashSet:根据下标添加元素,不允许元素重复
HashMap:1.HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.
2.HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
3.HashMap的实现是借助于哈希表HashTable实现的
4.HashMap是数组和链表相结合,其中key的hash值是数组的下标,同样hash值的key以链表方式存储。
例:
immutable:不变的
mutable:可变的
import scala.collection.immutable
immutable.HashMap("奥尼尔"->"大鲨鱼","伊塞亚托马斯"->"微笑刺客")
import scala.collection.mutable
mutable.HashMap("拜纳姆"->"小鲨鱼","巴蒂尔"->"蝙蝠侠")
//set 不可变指的是 一旦发生改变(无论是长度还是元素)就会生成一个新的集合
import scala.collection.immutable
val set01 = immutable.HashSet(1,2,3,4,5)
val set02 = set01.+(6)
set02 += 1//,会报错,set02是immutable,不变的,不可更改的
//value += is not a member of
scala.collection.immutable.HashSet[Int]
import scala.collection.mutable
val set03 = mutable.HashSet(1,2,3,4,5)
set03.add(6)
set03.+=(7)
set03 += 8
16.Option 有两个子类?
some、none
为了让所有东西都是对象的目标更加一致,也为了遵循函数式编程的习惯,Scala鼓励你在变量和函数返回值可能不会引用任何值的时候使用Option类型。
- Option可以看作是一个容器,容器的size是1或0;
- size为1的时候,即有值可以引用,就是一个Some[A](x: A),如Some[String];
- size为0的时候,即没有值的情况下就是一个None,它是Option的一个子类。None被声明为一个对象,而不是一个类,因为我们只需要它的一个实例。这样,它多少有点像null关键字,但它却是一个实实在在的,有方法的对象。
两个取值的方式:get(),getorelse()
get(): 当程序回传Some的时候,代表这个函式成功地给了你一个String,而你可以透过get()函数拿到那个String,如果程序返回的是None,则代表没有字符串可以给你。
在返回None,也就是没有String给你的时候,如果你还硬要调用get()来取得 String 的话,Scala一样是会抛出一个NoSuchElementException异常给你的。
getOrElse():这个方法在这个Option是Some的实例时返回对应的值,而在是None的实例时返回传入的参数。换句话说,传入getOrElse的参数实际上是默认返回值。
17.scala 访问修饰符? 作用域?
请点击下面的链接
scala 访问修饰符及 作用域
18.scala 如何访问内部类?
1.外部类暴露内部类对象
例
class Cloth {
//内部类
class Jean {
val name = "Jean"
def getInfo: Unit = {
println("牛仔裤")
}
}
//在内部类里面暴露 外部类对象
def getJean:Jean={
new Jean
}
}
调用
object DemoInnerClass {
def main(args: Array[String]): Unit = {
//调用内部类的getInfo 1
var clo = new Cloth
var jean = clo.getJean
jean.getInfo
}
2.new 外部类对象.内部类
例:
//调用内部类的getInfo
//注意要用val,不是var,否则调用不出来
val cloth = new Cloth
var jean1 = new cloth.Jean
jean1.getInfo
注意:注意要用val,不是var,否则cloth.Jean是点不出来后面的Jean的
19.伴生类和伴生对象?
3个要点
- 当一个单例对象(object)和某个类(class)共享一个名称时(名字一样),这个单例对象称为 伴生对象。
同理,这个类被称为是这个单例对象的伴生类。类和它的伴生对象可以互相访问其私有成员。使用伴生对象来定义那些在伴生类中不依赖于实例化对象而存在的成员变量或者方法。 - 在 Java 中 static 成员对应于 Scala 中的伴生对象的普通成员。
- 在 Java 代码中调用伴生对象时,伴生对象的成员会被定义成伴生类中的 static 成员。这称为静态转发。这种行为发生在当你自己没有定义一个伴生类时。
20.App特质?
scala的一个特质 可以代替main方法 作为程序的入口
例:
package test
/**
* 不用写main方法就可以输出结果
*/
object DemoAPP extends App{
println("App特质")
}
21.scala 构造器?
主构造器,辅助构造器 辅助构造器第一句必须要调用主构造器
22.scala object类型?
scala 没有静态类型 static
object 里面的属性和方法都是静态的
object 修饰的 都是单例的
23.apply应用?
apply 可以自己使用,不需要new
在伴生对象里面 定义apply方法
提取器对象是一个包含有 unapply 方法的单例对象。apply 方法就像一个构造器,接受参数然后创建一个实例对象,反之 unapply 方法接受一个实例对象然后返回最初创建它所用的参数。提取器常用在模式匹配和偏函数中。
例如Array(1,2,3,4) ,虽然数组不用new,但是它的源码是new Array[Int]
实际上还是new。
24.子类继承父类?
一个类实现一个接口使用extends;
如果这个类有父类(使用extends);特质要换成with
25.类型检查和类型转换?
- isInstanceOf:检查某个对象是否属于某个给定的类。
- asInstanceOf:将引用转换为子类的引用。
- classOf:如果想测试p指向的是一个Employee对象但又不是其子类,可以用if(p.getClas s ==
classOf[Employee]),classOf方法定义在scala.Predef对象中,因此会被自动引入。
例:
创建一个父类Animal,创建其两个子类Tiger,Tuna
class Animal{
val species = "Animal"
}
class Tiger extends Animal{
override val species = "mammal"
}
class Tuna extends Animal{
override val species = "fish"
}
测试:
object DemoInstance {
def main(args: Array[String]): Unit = {
val tiger = new Tiger
val tuna = new Tuna
test01(tiger)
}
def test01(is:Animal):Unit={
//需要做类型转换和类型检查
//isInstanceOf为类型检查,只能做粗略的检查,判断是否为其本类或者子类
if(is.isInstanceOf[Tiger]){
val ins1 = is.asInstanceOf[Tiger]
println(ins1.species)
}
//这等判断是精确的判断,直接对2个类进行比较
else if(is.getClass == classOf[Tuna]){
val ins2 = is.asInstanceOf[Tuna]
println(ins2.species)
}
}
}
26.scala 的特质?
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait,
27.Nil tail head?
list 集合 ,例如一个集合List(1,2,3,4)
Nil: 表示一个空的集合 ()
tail:表示一个除去第一个元素的集合 如:(2,3,4)
head:表示集合的第一个元素 如:1
28.样例类?
样例类:(Case Class)
- 样例类一般不用于可变的对象,并且可以做值比较(只有四类八种基本数据类型;==)
- 样例类可以不用new来创建对象
- 样例类:toString:默认就是构造方法,同时把值带上了
29.偏函数 柯里化 闭包 泛型
偏函数
偏函数是指仅定义了输入参数的子集的函数,如下图:
这个图显示的一个偏函数:f : X -> Y,该函数仅定义了输入参数X的子集1和3,没有包含2。
在Scala中的偏函数是通过特质PartialFunction[-A, +B]来定义的,查看PatialFunction特质的API,可看到PatialFunction定义如下:
trait PartialFunction[-A, +B] extends (A) ⇒ B
可以看出偏函数:
- 1)是一个将类型A转为类型B的特质。
- 2)接受A类型的输入参数,返回值为B类型。
- 3)是一个一元函数,“-”符号作用于类型表示逆变,-A表明输入参数为A类型或A类型的父类,也就是说输入的参数应为A的子集,具有“部分”的含义。
- 4)函数有可能“偏”离了A定义域(Type)类型,而成为值域B, +表明可以是B或B的子类,具有“全部”的含义。
偏函数和其它函数一样,也定义了apply方法,apply方法会从匹配到的模式计算函数值。也添加了另外一个方法“def isDefinedAt(a: A):Boolean”,isDefinedAt方法决定了该方法的参数是否在给定的偏函数的定义域内,如果返回结果为true,表示在,否则不在,最好在。见下例,signal引用了一个偏函数:
val signal: PartialFunction[Int, Int] = {
case x if x > 1 => 1
case x if x < -1 => -1
}
这个signal所引用的函数除了0值外,对所有整数都定义了相应的操作。 调用signal(0) 会抛出异常,因此使用前最好先signal.isDefinedAt(0)判断一下。
偏函数主要用于这样一种场景:对某些值现在还无法给出具体的操作(即需求还不明朗),也有可能存在几种处理方式(视乎具体的需求),我们可以先对需求明确的部分进行定义,比如上述除了0外的所有整数域,然后根据具体情况补充对其他域的定义,比如 :
val composed_signal: PartialFunction[Int,Int] = signal.orElse{
case 0 => 0
}
composed_signal(0) // 返回 0
或者对定义域进行一定的偏移(假如需求做了变更, 1 为无效的点)
val new_signal: Function1[Int, Int] = signal.compose{
case x => x - 1
}
<pre name="code" class="html">new_signal(1) // throw exception
new_signal(0) // 返回 -1
new_signal(2) // 返回 1
还可以用andThen将两个相关的偏函数串接起来
val another_signal: PartialFunction[Int, Int] = {
case 0 => 0
case x if x > 0 => x - 1
case x if x < 0 => x + 1
}
这里的then_signal 剔除了-1, 0, 1三个点的定义。
柯里化:
方法可以定义多个参数列表,当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。引入函数外部变量
例:
/* scala中是可以方法套用方法
* 方法定时的时候有两个参数列表;
*
* 类似于:方法的重载
* */
def fun3(a:Int)(c:Int,b:Int) :Unit =
{
println("---a--" + a);
println("---b--" + b);
print("---c--" + c);
}
/* 调用方法 */
fun3(2)(3,4);
/* 第一步 fun3(2)(3,4)
* fun4==fun3(30)
* 第二步
* fun4(40,50)
* 使用一个方法进行了替代(3,4)
* _表示省略后面的形参列表;
*
* 柯里化:
* */
def fun4 = fun3(30)_;
/* 调用方法 */
fun4(40,50);
闭包:
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。
泛型:
泛型类指可以接受类型参数的类。泛型类在集合类中被广泛使用。
注意:泛型类型的子类型是不可传导的。这表示如果我们有一个字母类型的栈 Stack[Char],那它不能被用作一个整型的栈 Stack[Int]。
结论就是,只有当类型 B = A 时, Stack[A] 是 Stack[B] 的子类型才成立。因为此处可能会有很大的限制,Scala 提供了一种 类型参数注释机制 用以控制泛型类型的子类型的行为。
30.上边界 下边界 视图界定
上边界:传入的参数只能本身或者是子类 <:
下边界:传入的参数只能本身或者是父类 <:
视图界定 : <% 将不是他的子类通过视图界定间接的转换成它的子类
31.隐式转换?
32.异常
- Scala 的异常处理和其它语言比如 Java 类似。
- Scala 的方法可以通过抛出异常的方法的方式来终止相关代码的运行,不必通过返回值。
抛出异常
Scala 抛出异常的方法和 Java一样,使用 throw 方法
捕获异常
异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。
捕捉异常的catch子句,语法与其他语言中不太一样。在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句,
try{
}
catch{
//偏函数
case e:Exception => {}
}
finally{
}
33.协变和逆变
协变
- 使用注释 +A,可以使一个泛型类的类型参数 A 成为协变。 对于某些类 class List[+A],使 A 成为协变意味着对于两种类型
A 和 B,如果 A 是 B 的子类型,那么 List[A] 就是 List[B] 的子类型。
逆变
- 通过使用注释 -A,可以使一个泛型类的类型参数 A 成为逆变。
与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 class Writer[-A]
,使 A 逆变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 Writer[B] 是 Writer[A] 的子类型。
34.call by name / call by value
call by name: 传递的是函数本身,函数没有进行计算
call by value:传递的是函数的返回值
35.Actor 接收消息的方式?
什么是Scala Actor
- Scala中的Actor能够实现并行编程的强大功能,它是基于事件模型的并发机制,Scala是运用消息的发送、接收来实现高并发的。
- Actor可以看做是一个个独立的实体,他们之间是毫无关联的。但是,他们可以通过消息来通信。一个Actor收到其他Actor的消息后,它可以根据需要作出各种响应。消息的类型可以是任意的,消息的内容也可以是任意的,整合到Akka 。
receive:需要给receive方法传入一个偏函数 { case 变量名1:消息类型1 => 业务处理1, case 变量名2:消息类型2 => 业务处理2, … }
react:性能要比receive高,会复用线程
36.actor 发送消息?
符号 | 描述 |
---|---|
! | 发送异步消息 没有返回值 |
!! | 发送异步消息,有返回值 Future |
!? | 发送同步消息,等待返回值 返回是Any 如果没有返回值,线程处于阻塞状态 |
注意:Future表示一个异步操作的结果状态,可能还没有实际完成的异步任务的结果;Any是所有类的超类,Future[Any]的泛型是异步操作结果的类型。