Spark也支持Java和Python,为啥要学Scala?因为Spark的原生语言是Scala,对Scala的支持最好,我觉得,Scala像是Java和Python的结合体,学着还挺好玩的
一、基本语法
1.声明值和变量:
val: (变量指向的内容)不可变,声明必须初始化,不能再赋值
var:(变量指向的内容)可变,声明需要初始化,可以再赋值
例子:
// import java.lang._ // 默认会导入java.lang包里所有的东西,scala里不用*,用_
val myStr // 会报错,val变量指向的内容声明必须初始化
val myStr : String = "Hello World"
val myStr = "Hello World" // 类型写不写都行,不写冒号也不要有,他会自己推断
println(myStr)
myStr = "Error" // 会报错,val变量指向的内容是不可变的
var myStr1 = "Hello World"
myStr1 = "Success" // 不会报错,var变量指向的内容是可变的
<=> Java
java.lang.String myStr = "Hello World"
System.out.println(myStr)
2.基本数据类型和操作
说明:下面用到符号“<=>”代表“等价于”
(1)Byte,Char,Short,Int,Long,Float,Double,Boolean 都是类
(2)操作符都是方法:
5 + 3 <=> (5).+(3)
var i = 1
i ++ // 没有 ++,--操作
i += 1
i -= 1
(3)循环:Range
for(i <- 1 to 5) <=> for(i <- 1.to(5)) <=> for(i <- Range(1,2,3,4,5))
for(i <- 1 until 5) <=> for(i <- Range(1, 2, 3, 4))
for(i <- 1 to 10 by 2) <=> for(i <- Range(1, 3, 5, 7, 9))
for(i <- 0.5f to 5.9f by 0.8f) <=> for(i <- Range(0.5, 1.3, 2.1, 2.8999,
3.699, 4.5, 5.3))
for(i<- 1 to 5 if i%2==0) println(i)
for(i<- 1 to 5; j <- 1 to 3) print(i*j)
//遍历完把结果保存下来,生成一个新变量
val r = for (i <- 1 to 5 if i % 2 == 0) yield {println(i); i}
=> r = {2, 4}
3.读写文件
(1)读文件
import scala.io.Source
val inputFile = Source.fromFile("output.txt")
var lines = inputFile.getLines // 返回一个迭代器
for (line <- lines) println(line)
(2)写文件
import java.io.PrintWriter
val out = new PrintWriter(‘output.txt’)
for(i <- 1 to 5) out.println(i)
out.close()
4.异常处理
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try{
val f = new FileReader(‘’)
}catch{
case ex: FileNotFoundException =>
//
case ex: IOException =>
//
}finally{
file.close()
}
二、数据结构
scala中常用的集合Collection包括 List, Array, Set, Map
集合分为“可变集合”和“不可变集合”两种:
可变集合在包scala.collection.mutable
中
不可变集合在包scala.collection.immutable
中
默认的集合是不可变的
1.List
构造:
val intList = 1::2::3::Nil <=> val intList = List(1, 2, 3) // Nil代表空列表对象
val strList = ("BigData", "Hadoop", "Spark")
val otherList = "Apache"::strList //::代表在已有列表前端增加元素,右结合
=> otherList = ("Apache", "BigData", "Hadoop", "Spark")
操作
strList.head() //返回第一个值
strList.tail() //返回去掉第一个之后的列表
2.Map
构造
val university = Map("XMU" -> "Xiamen University")
添加元素
university("FZU") = "Fuzhou"
university += ("TJU" -> "Tianjin University", …)
3.迭代器
val iter = Iterator(“a”, “b”, “c”)
while(iter.hasNext){}
for(ele <- iter){}
Iterable有两个方法返回迭代器:grouped和sliding
lst = List(1, 2, 3, 4, 5)
lst grouped 3 // 分块grouped,将列表以长度3进行分块
=> .next=List(1, 2, 3), .next()=List(4, 5)
lst sliding 3 // 滑动sliding,窗口长度为3,每次向后滑动1个单位长度
=> .next=List(1, 2, 3), .next()=List(2, 3, 4), .next()=List(3, 4, 5)
4.数组:可变,可索引,相同类型
长度确定用Array,不确定用ArrayBuffer,元素类型可不同,有点像python里的list
val intArr = new Array[int](3, 4, …) // 一维
val intValueArr = Array(Array(12, 34, 45), …) // 二维
intValueArr(0)(1) = 12 // 下标用圆括号
intValueArr += 10 //相当于append
intValueArr -= 40 //把第一个40删掉
5.元组:不同类型
val t = ("a", 23, 5.2)
print(t._1) // 输出第一个值
三、类
1.方法
def increment(): Unit = {} //Unit <=> void,标准写法
def a(value: int): Unit = value += 1 <=> def a(value: int){value += 1} // 方法体只有一句时可以省略{}或者 : Unit =
def a() = {
a // 返回值,不需要写return
}
2.编译和执行
(1)先定义个类
class Counter{
def a(): Int = {}
}
val myC = new Counter // 没参数,可以不写括号
myC.a() // 这个括号也可以不写
编译(在scala解释器中):
第一种:scala Test.scala
// 定义的是类可以这么编译,后面定义Object就不能这么干了
第二种:进入到解释器下面执行:load ./Test.scala
退出:quit
(2)加上单例对象
class Counter{
def a(): Int = {}
}
object MyCounter{
def main(args:Array[String]){
val myC = new Counter
myC.a()
}
}
有class必须:
scalac Test.scala
scala -classpath . MyCounter // scala -classpath 路径 类名
没有class可以:
scala Test.scala
3. getter/setter方法
class Counter{
private var privateVal = 0
def value = privateVal // getter
def value_ = (newValue: Int){ // setter
if (newValue>0) privateVal = newValue
}
def a(): Int = {}
}
object MyCounter{
def main(args:Array[String]){
val myC = new Counter
println(myC.value) // 调用getter
myC.value = 3 // 调用setter
}
}
4. 构造器
辅构造器必须调用之前已经定义的主构造器或辅构造器
class Counter(val id: Int){ // 主构造器
private var name = ""
private var mode = 1
def a(): Int = {}
def this(id: Int, name: String){
this(id) // 调用主构造器
this.name = name
}
def this(id: Int, name: String, mode: Int){
this(id, name) // 调用辅构造器
this.mode = mode
}
}
5. 单例对象:不定义类也行,类似于java里的static
object Person{}
val a = Person() // 不需要new
伴生对象:同一个文件中,class后面的名称和object后面的名称完全一致的时候,class的是伴生类,object是伴生对象,伴生类可以直接调用伴生对象里的方法,伴生对象里面的所有方法都是静态方法
编译之后,伴生对象里的内容都变成伴生类里的静态属性/方法
有实例之后再用实例调用方法才是调用伴生类
6. apply 和 update
apply:用括号传递给变量(对象)一个或多个参数时,Scala会把它转换成对apply的调用
update:当对带有括号并包括一到若干参数的对象进行赋值时,编译器将调用对象的update方法,调用时,是把括号里的参数和等号右边的对象一起作为update方法的输入参数来执行调用的
7. 继承
(1)重写一个非抽象方法必须使用override修饰符,抽象方法不需要
(2)只有主构造器可以调用超类的主构造器
抽象类:
abstract class Car{ //不能实例化
val carBrand: String // 无初始值,抽象字段
def info() // 抽象方法,无需abstract
def greeting(){print(…)}
}
子类
class BMWCar extends Car {
override val carbrand="BMW" // 重写超类字段,需要使用override关键字
def info(){print(…)} // 重写超类的抽象方法时,不需要override关键字
override def greeting(){print(…)} // 重写超类的非抽象方法,必须使用override关键字
}
8. 特质 trait
(1)类似于java里的接口,但又不仅实现了接口的功能,还具备很多其他特性
(2)是代码重用的基本单元,可以同时拥有抽象方法和具体方法
(3)一个类只能继承自一个超类,却可以实现多个特质,实现了多重继承
trait CarId{
var id: Int
def curentId(): Int
}
trait CarGreeting{
def greeting(msg: String){println(..)} // 可以具体实现
}
可以使用extends或with混入类中
class BMWCar extends CarId with CarGreeting{
override var id = 1000
def curentId(): Int = {id += 1; id}
}
9. 模式匹配
(1)例一
val colorStr = colorNum match {
case 1 => "red"
case 2 => "green"
case _ => "not allowed" // other
| case unexpected => unexpected + " is not allowed"
}
(2)例二
for (elem <- List(9, 12.3, "Spark", "Hadoop", "Hello")){
val str = elem match{
case i: Int => i + " is an int value."
case d: Double => d + " is a double value."
case "Spark" => "Spark is found"
case _ => "This is an unexpected value."
}
}
(3)例三
for (elem <- List(9, 1, 2)){
elem match{
case _ if (elem % 2 == 0) => println(elem + " is even.")
case _ => println(elem + " is odd.")
}
}
(4)case 类的匹配
case class Car(brand: String, price: Int)
val myBYDCar = new Car("BYD", 89000)
val myBMWCar = new Car("BMW", 120000)
val myBenzCar = new Car("Benz", 150000)
for (car <- Lisr(myBYDCar, myBMWCar, myBenzCar)) {
car match {
case Car(("BYD", 89000)) => println("")
case Car((brand, price)) => println("Brand: " + brand + ", Price:" + price)
}
}
(5)Option类型
用case类来表示那种可能存在、也可能不存在的值
当预计到变量或者函数返回值可能不会引用任何值的时候,建议使用Option类型
当存在可以被引用的值的时候,返回结果会用Some(Option的子类)来包含这个值,不存在,则返回None
Option类型有个getOrElse("")
方法,使被引用的值不存在时不再显示None,而是显示括号里的提示信息
Option[T]实际上是个容器,可以看作只包含一个元素或者不包含元素的集合
val books = Map("Hadoop" -> 1, "Spark" -> 2)
books.get("Hadoop")
=> Option[Int] = Some(1)
val sales = books.get("hive").foreach(println)
=> Option[Int] = None
sales.getOrElse("No Such Book")
=> No Such Book
10. 函数的类型和值
函数字面量:可以体现函数式编程的核心理念
定义函数
def counter(val: Int): Int = {val += 1}
函数类型 (Int) => Int <=> Int => Int
函数值:类型声明部分去掉,剩下的就是函数的“值” (val) => {val += 1}
定义函数和定义变量的拆解类比
名称 | 类型 | 值 | |
val counter: Int => Int = (val) => {val += 1} | counter | Int => Int | (val) => {val += 1} |
val num: Int = 5 | num | Int | 5 |
11. 匿名函数: lambda表达式
(num: Int) => num * 2
闭包(还不懂):函数内部可以调用函数外部的一些变量值,每次调用都会创建一个新闭包
var more = 1
val addMore = (x: Int) => x + more
12. 占位符语法:下划线 _
13. 集合的操作
(1)遍历
university foreach {case(k, v) => println(k + "+" + v)}
<=> university.foreach({case(k, v) => println(k + "+" + v)})
<=> university foreach {kv => println(kv._1 + "+" + kv._2)}
(2)Map
val books = List("Hadoop", "Hive", "HDFS")
books.map(s => s.toUpperCase) // map操作
=> List("HADOOP", "HIVE", "HDFS")
books.flatMap(s => s.toList) // flatMap操作
=> List("H", "a", …, "H", "I", …, "H", "D", …)
(3)filter
val uni = university filter {kv => kv._2 contains "scala"}
(4)reduce and fold
val list = List(1, 2, 3, 4, 5)
// reduce 归约:reduceLeft / reduceRight
list.reduce(_ + _) = 15 // 从左往右加 => 1 + 2 + ... + 5
// fold:类似于reduce,只是需要个种子值
list.fold(10)(_ * _) = 1200 // 种子值 * 从左往右乘 => 10 * 1 * 2 * ... * 5