Spark学习笔记[1]-scala环境安装与基本语法
正所谓工欲善其事必先利其器,Spark的开发语言不是java而是scala,虽然都是运行于JVM,但是两门语言的基本特性还是有些不一样,这里说明一个概念,JVM不等于JAVA,任何语言只要能编译出符合JVM规范的class文件,都可以运行在JVM上
相比于java,scala语言更加简简洁,且其实函数式编程语言,函数式变成语言的含义就是任何函数都是变量,有点类似于C++中的函数指针,由于语法很简洁,所以带来的问题就是相比于Java,用scala写的代码的代码可读性会差那么一点点
变成语言说到底都只是一门工具,语言特性有差异,但是其能支持的功能基本上都大同小异,本文着重介绍一些scala语言的基本特性,详细的内容可以直接查看官网
1、准备工作(以windows系统为例)
-
1)、从官网下载scala的安装包,https://www.scala-lang.org/download/2.12.1.html , windows系统下载对应的msi文件,双击安装即可,安装过程中不能安装在默认的文件夹,默认文件夹是Program Files (x86),路径带空格,会报错
-
2)、添加环境变量,也可以在安装时由程序写入path
-
3)、打开命令行输入scala,如果能进入scala的编辑界面,则说明安装完成
-
4)、本文采用的集成开发工具是IDEA,scal可以集成到IDEA需要先安装scala的插件,具体可以参考博客:https://blog.csdn.net/zhujq_icode/article/details/82501559
2、基本语法及语言特性
2-1 main、class、 object和语句书写规则介绍
- Java的类定义关键字是class,scala除了class外还有object关键字,主方法(main)主方法只能写在object定义的类中,示例代码如下:
object Collection {
def main(args: Array[String]): Unit = {}
}
- Java的一个文件只能由一个主类,且主类名字和文件名必须一致,scala没有这个要求
- **object和class的区别:**scala中使用class定义类时不可有静态变量和静态方法(根本就没有static关键字),scala中static的功能可以用object类来实现(所以main必须在object中),但是该object类和class类必须定义在一个文件中,且必须同名,此时这个Object被称为"伴侣对象",例如同一个文件中的class A和Object A就是伴生对象,A中的变量就是class A的静态属性
- Object定义的类相当于静态的单例对象
- 在Java中以分号标志一行语句,scala中分号可有可无,但是同一行如果有多句,需要用逗号分隔
- 虽然scala允许文件名和类名不一致且一个文件可以有多个类,但是scala最终也需要编译成符合JVM规范的class文件,所以最终会由scala编译器生成类名和文件名一致的class类,所以同一个包下的不同文件不允许存在名称相同的类
- 在scala中类体中可以执行业务逻辑,在Java中业务逻辑只能在方法中执行,在scala中定义在类体中的业务逻辑【也就是裸露的代码】会被scala编译器编译到默认构造函数,比如如下定义在类中裸露代码
class test{
var a:Int = 3
val name = "bbb"
println(s"test.......$a")
println(s"test......${a+1}")
}
2-2 变量定义
var/val 变量名:类型 = 值 //var定义变量,val定义常量,var定义的变量可以改值,val类似于final
2-3 构造函数
scala中的可以写构造函数,如果不写,默认的构造函数就是由类体中的裸露代码构成,如果定义了个性化构造函数,定义方式如下
def this(参数名称:参数类型.....){
//必须调用默认构造函数
this()
}
2-4 类名构造器
除了常规的构造器,scala还有类名构造器,类名构造器的定义方式如下
class A(var/val name[名称]:String[类型]){
}
new A(name="aaa")
类名构造器的基本特点如下:
- 1)、var/val可以省略,默认是val且为private
- 2)、只有在类名构造器中的参数可以设置为var,其余方法中的参数都是val类型,且不允许设置成var类型
- 3)、如果有类名构造器,又定义了自定义构造器,则在自定义构造器中调用默认构造器需要显示对其进行初始化,例如将A改成
calss A(name:String){
def this(age:Int){
this("aaa") //显示初始化类名构造器中的参数
}
}
2-5 流程控制
2-5-1 if/else
和java一样
var i:Int = 0;
if(i == 0){
println("i=0")
}else{
println("aaaa")
}
2-5-2 while循环
和java一样,但是没有++这种自增的语法,直接用+=1替代
var i = 0
while(i<10){
println(i)
i +=1
}
2-5-3 for循环
scala不支持for(int i=0;i<10;i++)这种语法,只支持增强的for循环,类似于java 中就是for(a:迭代器, 使用scala实现for(int i=0;i<10;i++)的语法是:
for(i <- 0 to (9,1)){ //包含9
println(i)
}
for(j <- 0 until (10,1)){//不包含10
println(j)
}
循环表达式后面可以跟判断条件,例如满足某一个条件才执行循环体
for(i <- 0 to (9,1) if (i%2==0)){
println(i)
}
双层for循环直接使用更加简洁,例如打印乘法表
for(i <- 1 to 9 /*外层循环*/;j <- 1 to 9 if (i >= j) /*内层循环*/){
print(s"$j * $i = ${i*j} \t")
if(i==j){
println("")
}
2-6 函数
2-6-1 常规函数
函数定义
def 函数名(形参列表【都是形参名称:形参类型的格式】):返回值类型 = {
}
如果函数无返回值,则返回值类型是Unit,函数的返回值类型可以用return,也可以直接将变量写在最后一行即可,例如
def test1(): Int ={
var i = 3
//return i
i
}
如果函数无返回值,则返回值类型是Unit,函数的返回值类型可以用return,也可以直接将变量写在最后一行即可,例如
2-6-2 匿名函数
var y= (形参列表) =>{
函数体
}
//或者
var y:(Int,Int)=>Int = (a:Int,b:Int)=>{
a+b
}
y:(Int,Int)=>Int叫函数的签名,可以作为函数的形参存在,调用匿名函数和调用普通函数差不多,就是y(形参)
2-6-3 嵌套函数(函数中定义函数)
def test1(a:String):Unit = {
def test2():Unit = {
println(a)
}
test2()
}
test1("hello")
2-6-4 偏应用函数
def fun07(date:Date,tp:String,msg:String): Unit ={
println(s"$date\t$tp\t$msg")
}
var info = fun07(_:Date,"info","ok") //固定了后面两个参数,第一个参数"_"是占位
info(new Date())
2-6-5 可变参数函数
def fun08(a: Int*): Unit = {
for (elem <- a) {
println(elem)
}
//函数作为参数
a.foreach(println) //打印a的每一个原数,foreach接收一个形参的函数,返回值是泛型,println只有一个形参,返回值为Unit
}
fun08(8)
println("-------------")
fun08(1,2,3,4)
2-6-6 高阶函数
函数作为参数或者返回值
//函数作为参数,y:(Int,Int)=>Int就是所需函数格式,接收两个参数,返回一个Int数据
def compute(a:Int,b:Int,y:(Int,Int)=>Int):Int = {
y(a,b)
}
println(compute(3,4,(a:Int,b:Int)=>{a+b}))
println(compute(3,4,(a:Int,b:Int)=>{a*b}))
println(compute(3,4,_ % _))
println("--------------------------------")
//函数作为返回值
def factory(op:String):(Int,Int)=>Int ={
if(op.equals("+")){
(a:Int,b:Int)=>{a+b}
}else{
(a:Int,b:Int)=>{a*b}
}
}
var addFunc = factory("+")
var mulFunc = factory("*")
println(addFunc(3,4))
println(mulFunc(3,4))
2-6-7 柯理化
又称为多参数列表,感觉有点抽象,形式如下
def func09(a:Int)(b:String): Unit = {
println(s"$a\t$b")
}
func09(5)("hello")
是不是感觉很多余,直接定义def func09(a:Int,b:String)不就好了,主要用途
- 1)、用于接收可变参数列表类型不一致时使用
def func09(a:Int*)(b:String*): Unit = {
// println(s"$a\t$b")
a.foreach(println)
b.foreach(println)
}
func09(5,6)("hello","word")
// 当然可以用 def func09(a:Any*)实现,但是此时就无法控制传入的参数类型
- 2)、**隐式参数:**如果要指定参数列表中的某些参数为隐式(implicit),应该使用多参数列表
2-7 集合框架
-
1)、 使用java的集合框架,虽然是java写的,但是已经编译成字节码,都是运行在JVM上,所以scala可以使用java的类库
-
<font size=4.5 face='楷体’21)、 scala自己定义的集合类,有两大类,可变(mutable)和不可变(immutable),默认使用的是不可变包中的集合类,例子如下
//数组
var arr01 = Array(1,2,3,4)
println(arr01(0)) //用小括号取对应索引,[]在scala是泛型参数
var arr02 = Array[Int](1,2,3,4)
//链表
var list01 = List(1,2,3,4,5) //不可变List
list01.foreach(println)
//可变List
var list02 = new ListBuffer[Int]()
list02.+=(32) //添加元素,++ ++:等操作符的含义见https://blog.csdn.net/z1941563559/article/details/88751099
//集合
set //可变和不可变
//元组
var t2 = new Tuple2(11,"sssss") //2 代表可以有2个元素,最多可以Tuple22
println(t2._1) //取值
//迭代
val iterator = t2.productIterator //拿到迭代器
iterator.foreach(println)
//Tuple2在scala描述的就是键值对
map
import scala.collection.mutable.Map
val map01:Map[String,Int] = Map(("a", 33), "b" -> 22, ("c", "44"))
val keys:Iterable[String] = map01.keys
map01.put("d",444)
val value = map01.get("a").getOrElse("aa")
// get("a")返回的是Option类型,Option内部有两个值,none和some,有值就是返回some,没有值返回none,再通过一层取到值
//集合操作
//map方法,接收一个函数,一进一出
val list = List(1,2,3,4,5)
val list02 = list.map((x: Int) => (x * x))
list02.foreach(println)
//reduce方法,接收一个函数,多进一出
list.reduce((a:Int,b:Int)=>(a+b)) //内部调用的是reduceLeft,从左到右累加,初始值是0
//flatMap方法,集合展开
val list03 = List("hello word","hello jeje")
val strings = list03.flatMap((x: String) => {
x.split(" ")
})
strings.foreach(println)
2-8 迭代器
为了避免一次将大量数据直接加载到内存导致内存溢出,数据计算领域大量使用了迭代器模式,只需要保存指向真实数据的指针,通过对指针的迭代迭代数据,举个例子说明一下使用迭代器迭代scala的列表
val list03 = List("hello word","hello jeje","hehe hhhhhh")
val iter:Iterator[String] = list03.iterator
val strings = iter.flatMap((x: String) => {
x.split(" ")
}) //返回的也是迭代器,没有发生实际的计算
// strings.foreach(println) //迭代元素,发生计算,最终调用的是iter的next和hasnext方法,可以看下源码
val tuples = strings.map((_, 1))
//strings是迭代器,且已经在调用strings.foreach后指向末尾,再对其进行map迭代,已经无法输出元素
tuples.foreach(println)
2-9 高级特性
2-9-1 trait
类似于接口【编译后确实是接口】,用于多继承
trait A {
def say(): Unit={
println("1")
}
}
trait B {
def sayB():Unit={
println("2")
}
def sayB2():Unit
}
class Person(name:String) extends A with B {
def hello(): Unit = {
println(s"$name say hello")
}
override def sayB2(): Unit ={
println("3")
}
}
object traitTest {
def main(args: Array[String]): Unit = {
val p = new Person("ssss")
p.sayB2()
}
}
2-9-2 case class
样例类,主要用于模式匹配,和普通类不一样的是样例类的比较是比较值而不是引用,所以如下的a和a2是相等的
//类似于工厂,只要构造实例的值一样,出厂的产品就相同
case class Dog(name:String,age:Int){
}
object caseClassTest {
def main(args: Array[String]): Unit = {
val a = new Dog("hashiqi", 18)
val a2 = new Dog("hashiqi", 18)
println(a.equals(a2))
}
2-9-3 match 模式匹配
感觉上像是一个增强的switch,不仅可以对值进行匹配,还能对类型进行匹配
val tup:(Double,Int,String,Char) = (1.0, 2, "aaa", 'a')
val iter = tup.productIterator
val res = iter.map((x:Any)=>{
x match{
case 1.0 => println("1.0") //匹配值
// case 2 => println("2")
case o:Int => println(s"$o is Int") //匹配类型,o就是传入的x
case o:String => println(s"$o is String")
case _ => println("default") //默认情况,也就是switch的default规则
}
})
while(res.hasNext){
res.next()
}
2-10 偏函数
根据对应的规则处理数据返回对应值
//第一个位置是传入参数,第二个参数是返回值类型
def test:PartialFunction[Any,String]={
case "hello" => "val is Hello"
case x:Int => s"$x is Int"
case _ => "none"
}
println(test(44))
println(test("hello"))
println(test('a'))
2-11 隐式转换
隐式转换的作用是对现有的已经编译好的类进行增强,假设现在使用的是java的LinkList
val list01 = new util.LinkedList[Int]()
list01.add(1)
list01.add(2)
list01.add(3)
需要对其进行遍历,但是java的LinkList没有foreach方法,可以通过以下方法对其进行包装
- 1)、 封装方法
def foreach[T](linkedList: util.LinkedList[T],f:(T)=>Unit)={ //T是泛型参数
val iter = linkedList.iterator()
while(iter.hasNext){
f(iter.next())
}
}
foreach(list01,println)
- 2)、 封装类
class ListEx[T](linkedList: util.LinkedList[T]){
def foreach(f:(T)=>Unit)={
val iter = linkedList.iterator()
while(iter.hasNext){
f(iter.next())
}
}
}
// 用类封装
val listex = new ListEx(list01)
listex.foreach(println)
- 3)、 同样使用类对其封装,使用隐式转换方法对原有集合进行增强
//隐式转换方法,名称无所谓,类型要对
implicit def tran[T](linkedList: util.LinkedList[T]):Unit={
new ListEx(linkedList)
}
list01.forEach(println)
- 4)、 使用隐式转换类
implicit class tran[T](linkedList: util.LinkedList[T]) {
def foreach(f: (T) => Unit) = {
val iter = linkedList.iterator()
while (iter.hasNext) {
f(iter.next())
}
}
}
list01.forEach(println)
使用隐式转换可以在不修改源码的情况下对类功能进行增强,除了隐式转换函数和类,还有隐式转换参数,如下
implicit val aa:String = "aaa"
def aaaa(implicit aaa:String):Unit={ //代表参数可传可不传,不传的话,会从程序中定义的implicit变量寻找类型相匹配的填入,如寻找到多个则报错
println(aaa)
}
//调用方式
aaaa("bbb")
aaaa
//如果此时函数改成
def aaaa(implicit aaa:String,bbb:Int)
//虽然有一个String类型的隐式变量,但是调用aaaa时也不能只穿bbb参数,必须同时传入或者不传,若想实现只传入bbb的功能,需要用到柯理化(多参数列表),将函数定义成
def test01(bbb:Int)(implicit aaa:String):Unit ={
println(s"$aaa ----> $bbb")
}
test01(10)