一.Scala概述以及安装
1. 什么是Scala
Scala 是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala 运行于 Java 平台(Java 虚拟机),并兼容现有的Java 程序。http://www.scala-lang.org
2. 为什么要学Scala
- 优雅
- 速度快
- 能融合到Hadoop生态圈
3. Scala的安装
Scala是运行在Java平台上的,所以安装Scala之前需安装JDK。
(1) Windows上安装Scala
在官网http://www.scala-lang.org/上下载scala-2.11.8.zip压缩包,然后解压,再照JDK方式配置环境变量
(2) Linux上安装Scala
- 下载 Scala 地址 https://www.scala-lang.org/download/2.11.8.html
- 解压 Scala 到指定目录:tar -zxvf scala-2.11.8.tgz -C /usr/java
- 配置环境变量,将 scala 加入到 PATH 中
vi /etc/profile export JAVA_HOME=/usr/java/jdk1.8 export PATH=$PATH:$JAVA_HOME/bin:/usr/java/scala-2.11.8/bin |
二.Scala基础
- 声明变量:
类型在后 |
- 常用类型:
Scala和Java一样,有7种数值类型Byte、Char、Short、Int、Long、Float、Double和1个Boolean类型。
- 条件表达式:
Scala 的条件表达式比较简洁,定义变量时加上 if else 判断条件。
- //判断 x 的值,将结果赋给 y val y = if (x > 0) 1 else -1
- //支持混合类型表达式 val z = if (x > 1) 1 else "error"
- //如果缺失 else,相当于 if (x > 2) 1 else () val m = if (x > 2) 1
- //在 scala 中每个表达式都有值,scala 中有个 Unit 类,用作不返回任何结果的方法的结果类型,相当于 Java 中的 void,Unit 只有一个实例值,写成()。 val n = if (x > 2) 1 else ()
- 块表达式:
定义变量时用 {} 包含一系列表达式,其中块的最后一个表达式的值就是块的值。
- 循环:
在 scala 中有 for 循环和 while 循环,用 for 循环比较多
for 循环语法结构: for (i <- 表达式/数组/集合)
循环一: //for(i <- 表达式),表达式 1 to 10 返回一个 Range(区间);每次循环将区间中的一个值赋给 i for (i <- 1 to 10) println (i) |
循环二: //将数组或集合遍历循环 for(i <- arr) println(i) |
循环三: //高级 for 循环;每个生成器都可以带一个条件,注意:if 前面没有分号 for(i <- 1 to 3; j <- 1 to 3 if i != j) println(i + "=====" + j) |
循环四: //for 推导式:如果 for 循环的循环体以 yield 开始,则该循环会构建出一个集合;每次迭代生成集合中的一个值 val v = for (i <- 1 to 10) yield i * 10 println(v) |
- 方法的定义和调用:
定义方法用def关键字:方法名:参数列表:返回值类型
def ffm (x:Int, y:Int): Int = {
x*y //方法体
}
方法的返回值类型可以不写,编译器可以自动推断出来,但是对于递归函数,必须指定返回类型;
在Scala中调用方法时,可以直接.出来,跟Java中类似。
- 函数的定义和调用:
函数名:参数列表:函数体
val f1 = (x:Int, y: Int) => {x+y}
要调用函数时,可以直接通过f1调用
- 方法和函数的区别:
- 在函数式编程语言中,函数是“头等公民”,它可以像任何其他数据类型一样被传递和操作,函数是一个对象,继承自 FuctionN。
- 函数对象有 apply、curried、toString、tupled 这些方法。而方法不具有这些特性。
- 如果想把方法转换成一个函数,可以用方法名跟上下划线的方式。
- 可以在方法后面加下划线将方法转为函数: val ff2 = ff1 _
- Scala中的超类Any
在scala中,Any类是所有类的超类;Any有两个子类:AnyVal和AnyRef
- AnyVal:所有值类型的基类, 它描述的是值,而不是代表一个对象,AnyVal有9个子类:
- scala.Double - scala.Float - scala.Long - scala.Int - scala.Char - scala.Short - scala.Byte
还有scala.Unit 和 scala.Boolean这2个非数字类型
- AnyRef:是所有引用类型的基类。除了值类型,所有类型都继承自AnyRef 。
三.数组、映射、元组、集合
1. 数组
(1) 定义数组(定长数组和变长数组)
n 定长数组:
val arr=new Array[T](数组长度); 或val arr2 = Array [Int](10);或val arr3 = Array ( "hadoop", "storm", "spark");如果使用new,相当于调用了数组的 apply 方法,直接为数组赋值
//可以使用()来访问元素:println (arr3(2))
//将数组转换成数组缓冲,就可以看到原数组中的内容了toBuffer 会将数组转换长数组缓冲
println (arr1.toBuffer)
n 变长数组:
定义格式: val arr = ArrayBuffer[T]()
+=尾部追加元素: ab += 1
追加多个元素: ab += (2, 3, 4, 5)
追加一个数组++=: ab ++= Array (6, 7)
追加一个数组缓冲++=: ab ++= ArrayBuffer(8,9)
在数组某个位置插入元素用 insert,从某下标插入: ab.insert(0, -1, 0)
删除数组某个位置的元素用 remove 按照下标删除: ab.remove(0)
//打印数组缓冲 ab: println (ab)
(2) 遍历数组:
n 直接使用for循环进行遍历
n 使用 until 会生成一个 Range;reverse 是将前面生成的 Range 反转(until会获取数组指定的角标,然后根据角标获取元素)
for(i <- (0 until arr.length).reverse) println (arr(i))
(3) 数组转换:
yield 关键字将原始的数组进行转换会产生一个新的数组,原始的数组不变
n 定义一个数组: val arr = Array (1, 2, 3, 4, 5, 6, 7, 8, 9)
n 将偶数取出乘以 10 后再生成一个新的数组: val res = for (e <- arr if e % 2 == 0) yield e * 10
(4) 数组的常用算法:
求和: println(arr1.sum)
求最大值: println(arr1.max)
排序: println(arr1.sorted)
2.映射
在 Scala 中,把哈希表这种数据结构叫做映射。
(1) 构建映射:
- 使用箭头构建:val map=Map(键 -> 值,键 -> 值....)
- 利用元组构建:val map=Map((键,值),(键,值),(键,值)....)
- 注意:在 Scala 中,有两种 Map,一个是 immutable 包下的 Map,该 Map 中的内容不可变;另一个是 mutable 包下的 Map,该 Map 中的内容可变
(2) 获取和修改映射中的值:
- 值 = map名(键) 获取值; map名(键名) = 值 给map中的键赋值;
- map名.getOrElse(键名,0) //如果映射中有值,返回映射中的值,如果没有就返回默认值
- 还可以使用+=向map中追加值
3. 元组
(1) 创建元组:
- 元组是不同类型的值的聚集;对偶是最简单的元组。通过将不同的值用小括号括起来,即表示元组。
- 创建元组格式:val tuple=(元素,元素...)
- 定义元组时用小括号将多个元素包起来,元素之间用逗号分隔元素的类型可以不一样,元素个数可以任意多个。
(2) 获取元组中的值:
使用下划线加脚标 ,例如 t._1 t._2 t._3;注意:元组中的元素脚标是从 1 开始的。
(3) 将对偶的集合转换成映射:
可以使用toMap方法,将Array对偶转为映射; 例:arr. toMap
4. 集合
Scala 的集合有三大类:List,Set和Map;所有的集合都扩展自 Iterable特质,在Scala中集合有可变(mutable)和不可变(immutable)两种类型,immutable类型的集合初始化后就不能改变了(注意与 val 修饰的变量进行区别)。
(1) List:
- 不可变的序列 import scala.collection.immutable._
//创建一个不可变的集合:val lst1 = List(1,2,3) 或 val other_lst=2::Nil
//获取集合的第一个元素:val first=lst1.head
//获取集合中除第一个元素外的其他元素集合:val tail=lst1.tail
//将一个元素添加到 lst1 的后面产生一个新的集合:val lst6 = lst1 :+ 3
//将 2 个 list 合并成一个新的 List:val lst7 = lst1 ++ lst0
//将 lst0 插入到 lst1 前面生成一个新的集合:val lst8 = lst1 ++: lst0
//将 lst0 插入到 lst1 前面生成一个新的集合:val lst9 = lst1.:::(lst0)
- 可变的序列 import scala.collection.mutable._
//构建一个可变列表,初始有 3 个元素 1,2,3:val lst0 = ListBuffer[Int](1,2,3)
//创建一个空的可变列表:val lst1 = new ListBuffer[Int]
//向 lst1 中追加元素,注意:没有生成新的集合:lst1 += 4 或 lst1.append(5)
//将 lst1 中的元素最近到 lst0 中, 注意:没有生成新的集合:lst0 ++= lst1
//将 lst0 和 lst1 合并成一个新的 ListBuffer 注意:生成了一个集合:val lst2= lst0 ++ lst1
//将元素追加到 lst0 的后面生成一个新的集合:val lst3 = lst0 :+ 5
//删除元素,注意:没有生成新的集合:lst4 -= 5
//删除一个集合列表,生成了一个新的集合:val lst5=lst4--List(1,2)
//把可变 list 转换成不可变的 list 直接加上 toList:val lst6=lst5.toList
//把可变 list 转变数组用 toArray:val lst7=lst5.toArray
(2) Set:
Set 代表一个没有重复元素的集合;将重复元素加入 Set 是没有用的,而且 Set 是不保证插入顺序的,即 Set 中的元素是乱序的。
- 不可变的 Set import scala.collection.immutable._
//定义一个不可变的 Set 集合: val set =Set(1,2,3,4,5,6,7)
//元素个数: set.size
//取集合最小值:set.min
//取集合最大值:set.max
//将元素和 set1 合并生成一个新的 set,原有 set 不变:set + 8
//两个集合的交集: set & set1
//两个集合的并集: set ++ set1
- 可变的 Set import scala.collection.mutable._
//定义一个可变的 Set:scala> val set1=new HashSet[Int]()
//添加元素 add 等价于+=:set1.add(2)
//向集合中添加元素集合:set1 ++=Set(1,4,5)
//删除一个元素:set1 -=5
//删除一个元素:set1.remove(1)
(3) Map:
定义 Map 集合:val map=Map(键 -> 值 , 键 -> 值...) 或 利用元组构建 val map=Map((键,值), (键,值) ,....)
展现形式:val map = Map(“zhangsan”->30,”lisi”->40) 或 val map = Map((“zhangsan”,30),(“lisi”,40))
- 不可变的 Map import scala.collection.immutable._
操作 map 集合: 获取值: 值=map(键) 原则:通过先获取键,在获取键对应值。
遍历 map 集合: 显示所有的 key: imap.keys 通过 key 获取 value: imap("lisi")
通过 key 获取 value 有 key 对应的值则返回,没有就返回默认值 0: imap.getOrElse("zhangsan",0)
- 可变的 Map import scala.collection.mutable._
声明一个可变集合: val user =mutable.HashMap("zhangsan"->50,"lisi" -> 100)
添加键值对: scala> user +=("wangwu" -> 30)
添加多个键值对: scala> user += ("zhangsan0" -> 30,"lisi0" -> 20)
更新键值对: scala> user("zhangsan") = 55
四.类、对象、继承、特质
1. 类
(1) 类的定义:
- 在Scala中,类并不用声明为public 类型的。Scala源文件中可以包含多个类,所有这些类都具有共有可见性。
- 类私有字段一,只能在类的内部使用或者伴生对象中访问:private var name : String = "唐伯虎"
- 类私有字段二,访问权限更加严格的,该字段在当前类中被访问,在伴生对象里面也不可以访问:private[this] var pet = "小强"
- 以Object修饰,名字和类名相同,叫伴生对象
(2) 构造器:
- Scala 中的每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起。
- 每个辅助构造器执行必须以主构造器或者其他辅助构造器的调用开始
- 注意:主构造器会执行类定义中的所有语句。
2. Scala面向对象编程之对象
(1) Scala中的object:
- object 相当于 class 的单个实例,通常在里面放一些静态的 field 或者 method;在 Scala 中没有静态方法和静态字段,但是可以使用 object 这个语法结构来达到同样的目的。
- object 作用:
存放工具方法和常量;
高效共享单个不可变的实例;
单例模式:不需要 new,用【单例对象名称.方法】调用对象中的方法;
(2) Scala中的伴生对象:
- 如果有一个 class 文件,还有一个与 class 同名的 object 文件,那么就称这个 object是 class 的伴生对象,class 是 object 的伴生类;
- 伴生类和伴生对象必须存放在一个.scala 文件中;
- 伴生类和伴生对象的最大特点是,可以相互访问;
(3) Scala中的 apply方法:
- object 中非常重要的一个特殊方法,就是 apply 方法;
- apply 方法通常是在伴生对象中实现的,其目的是,通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
- 通常我们会在类的伴生对象中定义 apply 方法,当遇到类名(参数 1,...参数 n)时 apply 方法会被调用;
- 在创建伴生对象或伴生类的对象时,通常不会使用 new class/class() 的方式,而是直接使用 class(),隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;
(4) Scala中的main方法:
- 同 Java 一样,如果要运行一个程序,必须要编写一个包含 main 方法的类;
- 在 Scala 中,也必须要有一个 main 方法,作为入口;
- Scala 中的 main 方法定义为 def main(args: Array[String]),而且必须定义在 object 中;
- 除了自己实现 main 方法之外,还可以继承 App Trait,然后,将需要写在 main 方法中运行的代码,直接作为 object 的 constructor 代码即可,而且还可以使用 args 接收传入的参数;
3. Scala面向对象编程之继承
(1) Scala中继承(extends) 的概念:跟Java一样;
(2) Scala 中 override 和 super 关键字:跟Java一样;
(3) Scala 中 中 isInstanceOf 和 asInstanceOf:
如果实例化了子类的对象,但是将其赋予了父类类型的变量,在后续的过程中,又需要将父类类型的变量转换为子类类型的变量,应该如何做?
- 首先,需要使用 isInstanceOf 判断对象是否为指定类的对象,如果是的话,则可以、使用 asInstanceOf 将对象转换为指定类型;
- p.isInstanceOf[XX] 判断 p 是否为 XX 对象的实例;p.asInstanceOf[XX] 把 p转换成 XX 对象的实例
- 如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常;
- 如果对象是 null,则 isInstanceOf 一定返回 false, asInstanceOf 一定返回null;
Scala |
Java |
|
|
[c]obj |
|
classOf[c] |
C.class |
(4) Scala 中 中 getClass 和 classOf:
- isInstanceOf只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类对象;
- 如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf了;
- p.getClass 可以精确地获取对象的类,classOf[XX] 可以精确的获取类,然后使用 ==操作符即可判断;
(5) Scala中使用模式匹配进行类型判断:
- 在实际的开发中,比如 spark 源码中,大量的地方使用了模式匹配的语法进行类型的判断,这种方式更加地简洁明了,而且代码的可维护性和可扩展性也非常高;
- 使用模式匹配,功能性上来说,与 isInstanceOf 的作用一样,主要判断是否为该类或其子类的对象即可,不是精准判断。
- 等同于 Java 中的 switch case 语法;
例:
p match {
// 匹配是否为 Person 类或其子类对象
case per:Person5 => println("This is a Person5's Object!")
// 匹配所有剩余情况
case _ =>println("Unknown type!") }
(6) Scala 中 中 protected:
- 跟 Java 一样,Scala 中同样可使用 protected 关键字来修饰 field 和 method。在子类中,可直接访问父类的 field 和 method,而不需要使用 super 关键字;
- 还可以使用 protected[this] 关键字, 访问权限的保护范围:只允许在当前子类中访问父类的 field 和 method,不允许通过其他子类对象访问父类的 field 和 method。
(7) Scala中调用父类的 constructor:
- Scala 中,每个类都可以有一个主 constructor 和任意多个辅助 constructor,而且每个辅助 constructor 的第一行都必须调用其他辅助 constructor 或者主 constructor代码;因此子类的辅助 constructor 是一定不可能直接调用父类的 constructor 的;
- 只能在子类的主 constructor 中调用父类的 constructor。
- 如果父类的构造函数已经定义过的 field,比如 name 和 age,子类再使用时,就不要用 val 或 var 来修饰了,否则会被认为,子类要覆盖父类的 field,且要求一定要使用 override 关键字。
(8) Scala中的抽象类和抽象Field:
- 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现不同的方法。此时,可以将父类中的这些方法编写成只含有方法签名,不含方法体的形式,这种形式就叫做抽象方法;
- 一个类中,如果含有一个抽象方法或抽象 field,就必须使用 abstract 将类声明为抽象类,该类是不可以被实例化的;
- 在子类中覆盖抽象类的抽象方法时,可以不加 override 关键字;
- 如果在父类中,定义了 field,但是没有给出初始值,则此 field 为抽象 field;
4. Scala中面向对象编程之trait
(1) 将trait作为接口使用:
- Scala 中的 trait 是一种特殊的概念;
- 首先先将 trait 作为接口使用,此时的 trait 就与 Java 中的接口 (interface)非常类似;在 trait 中可以定义抽象方法,就像抽象类中的抽象方法一样,只要不给出方法的方法体即可;
- 类可以使用 extends 关键字继承 trait,注意,这里不是 implement,而是 extends ,在Scala 中没有 implement 的概念,无论继承类还是 trait,统一都是 extends;
- 类继承后,必须实现其中的抽象方法,实现时,不需要使用 override 关键字;
- Scala 不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可。
(2) 在trait中定义具体的方法:
Scala中的trait不仅可以定义抽象方法,还可以定义具体的方法,此时 trait 更像是包含了通用方法的工具,可以认为 trait 还包含了类的功能。
(3) 在trait中定义具体 field:
- Scala 中的 trait 可以定义具体的 field,此时继承 trait 的子类就自动获得了trait 中定义的 field;
- 但是这种获取 field 的方式与继承 class 的是不同的。 如果是继承 class 获取的 field ,实际上还是定义在父类中的;而继承 trait 获取的 field,就直接被添加到子类中了。
(4) 在trait中定义抽象 field:
- Scala 中的 trait 也能定义抽象 field, 而 trait 中的具体方法也能基于抽象 field 编写;
- 继承 trait 的类,则必须覆盖抽象 field,提供具体的值;
(5) 在实例对象指定混入某个 trait:
- 可在创建类的对象时,为该对象指定混入某个 trait,且只有混入了 trait 的对象才具有 trait 中的方法,而其他该类的对象则没有;
- 在创建对象时,使用 with 关键字指定混入某个 trait;
(6) trait调用链:
- Scala 中支持让类继承多个 trait 后,可依次调用多个 trait 中的同一个方法,只要让多个 trait 中的同一个方法,在最后都依次执行 super 关键字即可;
- 类中调用多个 trait 中都有的这个方法时,首先会从最右边的 trait 的方法开始执行,然后依次往左执行,形成一个调用链条;
- 这种特性非常强大,其实就是设计模式中责任链模式的一种具体实现;
(7) 混合使用 trait的具体方法和抽象方法:
- 在 trait 中,可以混合使用具体方法和抽象方法;
- 可以让具体方法依赖于抽象方法,而抽象方法则可放到继承 trait 的子类中去实现;
- 这种 trait 特性,其实就是设计模式中的模板设计模式的体现;
(8) trait的构造机制:
- 在 Scala 中,trait 也是有构造代码的,即在 trait 中,不包含在任何方法中的代码;
- 继承了 trait 的子类,其构造机制如下:
父类的构造函数先执行, class 类必须放在最左边;多个 trait 从左向右依次执行;
构造 trait 时,先构造父 trait,如果多个 trait 继承同一个父 trait,则父 trait 只会构造一次;所有 trait 构造完毕之后,子类的构造函数最后执行。
(9) trait 继承 class:
在 Scala 中 trait 也可以继承 class,此时这个 class 就会成为所有继承该 trait 的子类的超级父类。