Spark学习笔记[1]-scala环境安装与基本语法

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)

上一篇:vue tabBar导航栏设计实现3-进一步抽取tab-item


下一篇:前端图片上传逻辑整理