Scala基础
1.Scala基础数据类型
Scala中所有的数据都是对象,也就是说scala没有java中的原生类型。在scala是可以对数字等基础类型调用方法的。
举例:数字 1是一个对象,就有方法(函数)
scala> 1.toString
res0: String = 1
可以不用指定数据的类型,Scala会自动进行类型的推荐
举例:下面的两条语句是一样的
val a:Int = 10
val b = 10 ----> b的类型就是Int
(1)数值类型:Byte、Short、Int、Long、Float、Double
Byte表示:8位有符号补码整数。数值区间为 -128 到 127
Short表示:16位有符号补码整数。数值区间为 -32768 到 32767
Int: 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
Long:64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807,数值后带L/l
Float:32 位, IEEE 754标准的单精度浮点数,浮点数后带F/f的就是Float
Double:32 位 IEEE 754标准的单精度浮点数,浮点数后不带F/f的就是Double
(2)字符类型Char和字符串类型String
字符串的操作
//定义一个字符,字符是要用单引号来定义的
scala> var c1 = 'x'
c1: Char = x
//定义一个字符串,注意这里使用了双引号
scala> val s1 = "Hello World"
s1: String = Hello World
//字符串的插值
scala> s"My name is Tom and ${s1}"
res2: String = My name is Tom and Hello World
(3) 布尔类型
取值只有true和false
(4)Unit的类型
相当于Java的void
scala> def printname() {
| println("name")
| }
printname: ()Unit
scala> printname()
name
(5)Nothing类型
Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。代码中如果出现Nothing类型的时候,在load到JVM运行的时候将会把Nothing替换为Nothing 类 型 。 也 即 在 J V M 之 外 以 N o t h i n g 的 身 份 进 行 显 示 , 在 J V M 中 以 N o t h i n g 类型。也即在JVM之外以Nothing的身份进行显示,在JVM中以Nothing 类型。也即在JVM之外以Nothing的身份进行显示,在JVM中以Nothing的身份进行显示,两者表示同一个含义。这样解释也满足了Nothing可以当做 throw new XXXXXException(“head of empty list”)的类型使用的原因,即: Nothing$ extends Throwable.
(6)Any类型
Any是所有其他类的超类
(7)AnyRef类型
AnyRef类是Scala里所有引用类(reference class)的基类
2.变量与常量
在Scala中,常量用val关键字定义,变量用var关键字定义,注意在Scala里这两个关键字要用小写,否则会报错
scala> Var x:Long =1
<console>:1: error: ';' expected but '=' found.
Var x:Long =1
^
scala> var x:Long =1
x: Long = 1
scala> Val y:Long = 1L
<console>:1: error: ';' expected but '=' found.
Val y:Long = 1L
^
scala> val y:Long = 1L
y: Long = 1
3.函数与方法
Scala 中使用 val 语句可以定义函数,def 语句定义方法,但本质上两者没什么区别。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。
Scala 中的方法跟 Java 的类似,方法是组成类的一部分。
Scala 中的函数则是一个完整的对象,Scala 中的函数其实就是继承了 Trait 的类的对象。
方法实例:递归实现斐波那契
scala> def Fibonacci(n:Int):Int={
| if (n<=2 && n>0) 1
| else Fibonacci(n-1)+Fibonacci(n-2)
| }
Fibonacci: (n: Int)Int
scala> for(i<-1 to 10)
| print(Fibonacci(i)+" ")
1 1 2 3 5 8 13 21 34 55
4.条件表达式: if…else语句
scala> def myFactor(x:Int):Int = {
| if(x<=1)
| 1 注意:Scala的函数中,函数的最后一句话就是函数的返回值
| else
| myFactor(x-1)*x
| }
5.循环语句: for、while、do…while、foreach迭代
for表达式
通过使用被称为发生器(generator)的语法“files<-filesHere”,遍历了list里的元素,每一次枚举,名为s的新的val就被元素值初始化,函数体println(s)也将被执行一次
在写for循环的时候,可以在for表达式的括号里添加过滤器,如第三种写法;第二种写法等价于第三种。for表达式里可以加入超过一个的过滤器,if子句必须用分号分隔。同样的,生成器也是可以加入超过一个的,这样就等同于其他语言的for循环嵌套。
之前的例子都是对枚举值进行操作,然后就释放。在for表达式之后加上关键字yield,就可以创建一个值去记住每一次的迭代。
while循环
while循环分两种:while和do-while
之所以把while称为循环,而不是表达式,是因为它们不能产生有意义的结果,结果的类型是Unit,下面代码可以看见,方法体前面没有等号。
package practice
object test1 {
def main(args: Array[String]): Unit = {
var list = List("Mary","Mike","Tom")
//while循环
var i = 0
while(i < list.length){
println(list(i))
i += 1
}
//do-while循环
var j = 0
do{
println(list(j))
j += 1
}while(j<list.length)
}
}
还有一个值得注意的是,对var再赋值等式本身也是unit值,假设使用以下的例子,编译的时候,Scala会警告comparing values of types Unit and String using `!=’ will always yield true,所以循环会变成死循环
var line=""
while ((line = readLine()) != "")
println("Read: "+ line)
foreach迭代
这里的例子使用集合的foreach方法进行迭代,输出list里的数据。
第一行代码对list调用了foreach方法,并把函数字面量作为参数传入,符号=>表示这个函数将左边的东西转换成了符号右边的东西,在这里就是把element变成了打印element,可以把函数字面量就此理解为匿名函数。
Scala解释器可以推断element的类型是String,因为String是调用foreach的那个list的元素类型。如果想要表达的更明确一些,可以像第二行代码一样指明类型,并用括号括起来。
而函数字面量在这只有一行语句,并且只带一个参数,那么甚至连指代参数都不需要,可以写成第三行代码这样的形式
package practice
object test1
def main(args: Array[String]): Unit = {
var list = List("Mary","Mike","Tom")
list.foreach(element => println(element))
list.foreach((element:String) => println(element))
list.foreach {println}
}
}
6.Scala的函数参数
(1)函数参数的求值策略
call by value:对函数实参进行求值,并且只求一次
call by name:函数实参在函数内部调用的时候,才会求值,每次都会求值
举例:Call by value 使用冒号
def func1(x:Int,y:Int):Int = x + x
Call by name 使用 : =>
def func2(x: => Int,y: => Int):Int = x + x
调用:
func1(3+4,8)
执行过程 1:fun1(7,8)
2:7+7
3:4
func2(3+4,8)
执行过程 1:(3+4)+(3+4)
2:7 +(3+4)
3:7 + 7
4:14
复杂一点的例子
def bar(x:Int,y: => Int):Int = 1
def loop():Int = loop //死循环
调用 bar(1,loop) 输出:1
bar(loop,1) 可以看到worksheet里这条语句上有个不断转动的杠,进入了死循环
原因还是一样的,bar(1,loop)调用的时候,x是call by value,y是call by name,所以这里会先对x做一个求值,x=1,而y在没有调用到它的情况下,是不会进行求值的;同样的bar(loop,1)也是一样的道理,对x做求值,这个时候程序就会进入一个死循环的情况。
(2)函数参数的类型
默认参数
可以看到,当指定了默认参数值的时,即便没有参数值传入,也会按默认值来进行运算。
代名参数
func2中每个参数都有默认值,我们可以通过指定参数名以及参数值去将默认值给替换掉
可变参数
在参数类型的后面加上星号(*),就说明参数的个数不确定,且均为这个类型
7.Scala的Lazy特性(懒执行特性)
当一个变量被申明是lazy,他的初始化会被推迟,直到第一次使用他的时候,才会被初始化
另一个例子,没有添加Lazy关键字的时候,会抛出异常,因为b.txt根本不存在
而加上Lazy关键字后,因为没有调用words,所以没有抛出异常
8.异常处理
还是上面的例子,除了直接打印异常,还可以像Java一样用try…catch…finally来进行异常捕获
9.数组
Scala里使用new实例化对象,实例化过程中,可以用值和类型使对象参数化,参数化的意思是指在创建实例的同时完成对它的“设置”,数组同样可以如此。
(1)定长数组:Array
数组的索引从0开始,最大值为数组的长度-1,整数类型的数组初始值全部为0,而String类型的为Null
限定数组长度
val a = new ArrayInt
val b = new ArrayString
对数组直接进行初始化
val c = Array(“Tom”,“Mary”,“Mike”)
val a = new ArrayInt
注意,尽管使用的是val进行定义,但是数组内部的元素是可以被修改的,只是数组对象不能被重新赋值成新的数组
对数组进行排序并输出,这里是降序排列
a.sortwith(>).foreach(print)
(2)变长数组:ArrayBuffer
import scala.collection.mutable._
val d = ArrayBuffer[Int]()
往变长数组中添加元素
d += 1
d += 2
d += 3
一次添加多个元素
d += (10,20,30)
去掉数组中的元素
d.trimEnd(2)
(3)多维数组:通过数组的数组来实现
数组中的每一个值都可以是一个数组
import Array._
object Test {
def main(args: Array[String]) {
//定义二维数组,设定长宽
var myMatrix = ofDim[Int](3,3)
//初始化数组
for (i <- 0 to 2) {
for ( j <- 0 to 2) {
myMatrix(i)(j) = j;
}
}
//打印二维数组
for (i <- 0 to 2) {
for ( j <- 0 to 2) {
print(" " + myMatrix(i)(j));
}
println();
}
}
}
(4)区间数组
使用range方法生成数组
import Array._
object Test {
def main(args: Array[String]) {
//使用range方法生成数组,注意,是左闭右开区间,第三个参数为增长量
var myList1 = range(10, 20, 2)
var myList2 = range(10,20)
// 输出所有数组元素
for ( x <- myList1 ) {
print( " " + x )
}
println()
for ( x <- myList2 ) {
print( " " + x )
}
}
}
10.映射与元组
映射(Map)
Map是一种可迭代的键值对(key/value)结构,所有的值都可以通过Key来获取,Map 中的Key都是唯一的。
Map 也叫哈希表(Hash tables)。
Map 有两种类型,可变与不可变,区别在于可变对象可以修改它,而不可变对象不可以。
默认情况下 Scala 使用不可变 Map。如果需要使用可变集合,要引入 import scala.collection.mutable.Map 类
当key不存在的时候,就会报错
避免异常的方法当然是使用条件控制语句,或者getOrElse方法
Map的增删改、遍历
元组(Tuple)
与List一样,元组也是不可变的,但与列表不同的是元组可以包含不同类型的元素。
可以利用Scala的类型推断来隐式定义一个元组,也可以显式的new,但注意显式的情况下,Tuple后面要跟元组的长度,目前Scala Tuple的最大长度为22
Tuple不能直接用foreach来遍历,所以要使用迭代器再foreach进行遍历
Tuple还可以使用tostring方法来将元组转换成字符
元组(Tuple)
与List一样,元组也是不可变的,但与列表不同的是元组可以包含不同类型的元素。
可以利用Scala的类型推断来隐式定义一个元组,也可以显式的new,但注意显式的情况下,Tuple后面要跟元组的长度,目前Scala Tuple的最大长度为22
Tuple不能直接用foreach来遍历,所以要使用迭代器再foreach进行遍历
1.var,val和def三个关键字之间的区别?
var是变量声明关键字,类似于Java中的变量,变量值可以更改,但是变量类型不能更改。
val常量声明关键字。
def 关键字用于创建方法
还有一个lazy val(惰性val)声明,意思是当需要计算时才使用,避免重复计算
2.trait(特质)和abstract class(抽象类)的区别?
1)一个类只能集成一个抽象类,但是可以通过with关键字继承多个特质;
(2)抽象类有带参数的构造函数,特质不行(如 trait t(i:Int){} ,这种声明是错误的)
3.object和class的区别?
object是类的单例对象,开发人员无需用new关键字实例化。如果对象的名称和类名相同,这个对象就是伴生对象
//声明一个类
class MyClass(number: Int, text: String) {
def classMethod() = println(text)
}
//声明一个对象
object MyObject{
def objectMethod()=println("object")
}
new MyClass(3,"text").classMethod() //打印结果test,需要实例化类
Myclass.classMethod() //无法直接调用类的方法
MyObject.objectMethod() //打印结果object,对象可以直接调用方法
4.伴生对象是什么?
伴生对象就是与类名相同的对象,伴生对象可以访问类中的私有量,类也可以访问伴生对象中的私有方法,类似于Java类中的静态方法。伴生对象必须和其对应的类定义在相同的源文件。
/定义一个类
class MyClass(number: Int, text: String) {
private val classSecret = 42
def x = MyClass.objectSecret + "?" // MyClass.objectSecret->在类中可以访问伴生对象的方法,在类的外部则无法访问
}
//定义一个伴生对象
object MyClass { // 和类名称相同
private val objectSecret = "42"
def y(arg: MyClass) = arg.classSecret -1 // arg.classSecret -> 在伴生对象中可以访问类的常量
}
MyClass.objectSecret // 无法访问
MyClass.classSecret // 无法访问
new MyClass(-1, "random").objectSecret // 无法访问
new MyClass(-1, "random").classSecret // 无法访问
5.Scala类型系统中Nil, Null, None, Nothing四个类型的区别?
Null是一个trait(特质),是所以引用类型AnyRef的一个子类型,null是Null唯一的实例。
Nothing也是一个trait(特质),是所有类型Any(包括值类型和引用类型)的子类型,它不在有子类型,它也没有实例,实际上为了一个方法抛出异常,通常会设置一个默认返回类型。
Nil代表一个List空类型,等同List[Nothing]
None是Option monad的空标识
6.Unit类型是什么?
Unit代表没有任何意义的值类型,类似于java中的void类型,他是anyval的子类型,仅有一个实例对象"( )"
7.Option类型的定义和使用场景?
在Java中,null是一个关键字,不是一个对象,当开发者希望返回一个空对象时,却返回了一个关键字,为了解决这个问题,Scala建议开发者返回值是空值时,使用Option类型,在Scala中null是Null的唯一对象,会引起异常,Option则可以避免。Option有两个子类型,Some和None(空值)
8.解释隐示参数的优先权
在Scala中implicit的功能很强大。当编译器寻找implicits时,如果不注意隐式参数的优先权,可能会引起意外的错误。因此编译器会按顺序查找隐式关键字。顺序如下:
(1)当前类声明的implicits ;
(2)导入包中的 implicits;
(3)外部域(声明在外部域的implicts);
(4)inheritance
(5)package object
(6)implicit scope like companion objects
9.什么是函数柯里化?
柯里化技术是一个接受多个参数的函数转化为接受其中几个参数的函数。经常被用来处理高阶函数。
10.什么是高阶函数?
高阶函数指能接受或者返回其他函数的函数,scala中的filter map flatMap函数都能接受其他函数作为参数。
11.yield如何工作?
yield用于循环迭代中生成新值,yield是comprehensions的一部分,是多个操作(foreach, map, flatMap, filter or withFilter)的composition语法糖
12.偏应用函数和偏函数的区别
偏函数是一个单参数函数,且并未对该类型的所有值都实现相应处理逻辑。偏函数的字面量语法由包围在花括号中的一个或多个case语句构成。这与普通函数字面量不同,普通函数字面量可以使用花括号,也可以使用圆括号。
偏函数之所以“偏”,在就在于其只能处理那些能与至少一个case语句匹配的输入,而不能处理所有可能的输入。
如果偏函数被调用,而函数的输入却与所有case语句都不匹配,系统就会抛出一个MatchError的运行时错误。可以使用isDefinedAt方法测试特定输入是否与偏函数匹配,这样偏函数就可以避免抛出MatchError错误了。
偏应用函数是一个表达式,包含函数的部分而非全部参数列表。返回值是一个新函数,此函数携带剩下的参数列表。
对于拥有多个参数列表的函数而言,如果你希望忽略其中一个或多个参数列表,可以通过定义一个新函数来实现。也就是说,你给出了部分所需要的参数。为了避免潜在的表达式歧义,Scala要求在后面加上下划线,用来告诉编译器你的真实目的。注意,这个特性只对函数的多个参数列表有效,对一个参数列表中的多个参数的情况并不适用。
13.case class (样本类)是什么?
样本类是一种不可变且可分解类的语法糖,这个语法糖的意思大概是在构建时,自动实现一些功能。样本类具有以下特性:
(1)自动添加与类名一致的构造函数(这个就是前面提到的伴生对象,通过apply方法实现),即构造对象时,不需要new;
(2)样本类中的参数默认添加val关键字,即参数不能修改;
(3)默认实现了toString,equals,hashcode,copy等方法;
(4)样本类可以通过==比较两个对象,并且不在构造方法中定义的属性不会用在比较上。
//声明一个样本类
case class MyCaseClass(number: Int, text: String, others: List[Int]){
println(number)
}
//不需要new关键字,创建一个对象
val dto = MyCaseClass(3, "text", List.empty) //打印结果3
//利用样本类默认实现的copy方法
dto.copy(number = 5) //打印结果5
val dto2 = MyCaseClass(3, "text", List.empty)
pringln(dto == dto2) // 返回true,两个不同的引用对象
class MyClass(number: Int, text: String, others: List[Int]) {}
val c1 = new MyClass(1, "txt", List.empty)
val c2 = new MyClass(1, "txt", List.empty)
println(c1 == c2 )// 返回false,两个不同的引用对象