Scala入门
in Scala with 0 comment

Scala入门

in Scala with 0 comment

前言

在我个人看来,Scala是一门非常优雅的语言,但优雅的背后要付出的辛苦也很多,比如学习Scala的人都会说,Scala语法非常简洁,但这也意味着抽象级别比较高,对初学者而言不好理解。也会有人说,Scala语法非常灵活,一个功能可以有非常多的实现方法,可以说条条大路通罗马,那么代价就是对于初学者来说,路多了反而不好选择。

所以在这里我对初学者的忠告是:在学习Scala前期,先走通一条路,屏蔽掉多余的干扰项,可能我们第一次使用Scala实现的项目看起来是非常笨拙的,但是没关系,任何的学习都是循序渐进的,不要追求一蹴而就。走通之后再回头,去寻找更好的替代方案。这样才能享受学习。

综上,本课程的设计是比较特殊的,我会带着大家选择其中的一条路直接走向罗马,中间尽可能避开其他选项,当整体的知识架构搭建完成后,再一起以胜利者的姿态去讨论其他的方案和技巧。

提示:Scala语法简洁多变,源码中更是体现得淋漓尽致,所以不建议初学者过多翻看源码。如确有需求请在医师指导下服用。避免出现不良反应。


Scala简介

img

Scala初印象

Scala用一种简洁的高级语言将面向对象和函数式编程结合在一起。Scala的静态类型有助于避免复杂应用程序中的错误,其JVM和JavaScript运行时使您可以轻松访问庞大的库生态系统来构建高性能系统。

为什么要学习Scala?

分布式高并发语言Go、R、Erlang等等为何选择Scala?

Spark是大数据处理的核心方式,用scala语言编写!

Kafka分布式发布订阅消息系统,由LinkedIn捐给Apache,以极高的吞吐量著称,是目前最火爆的MQ,用scala语言编写!

Flink最新一代分布式海量数据计算框架,Alibaba收购并开源,自己开发Blink分支,Scala语言编写!

函数式编程

C:面向过程编程

Java:面向对象编程

Scala:面向函数编程

函数式编程:将所有复杂的问题的解决拆分为若干函数的处理,每一个函数可以去实现一部分功能,利用很多次函数的处理最终解决问题。函数式编程相对于面向对象编程更加的抽象,好处是代码可以非常的简洁,更多的采用常量而不是变量来解决问题,这样额外带来的好处是在线程并发时可以减少甚至杜绝多线程并发安全问题,特别适合于应用在处理高并发场景、分布式场景下的问题。函数式编程可以使用高阶函数,函数在scala中是一等公民,可以更加灵活的进行程序的编写。

函数式编程并不是面向对象编程的发展,而是另外一种解决问题的思路,两者之间也并没有绝对的好坏之分,在不同的场景中各有各的优缺点。

当然Scala是一种函数是编程的语言 同时具有面向对象编程的特点。

Scala和Java的关系

java是由C语言开发,scala基于java,现在看来大部分情况下,青出于蓝而胜于蓝!

分布式开发中会面对很多棘手的问题,如面临服务器之间的通信、远程调用、序列化、反序列化等等。但有了scala这一切变得轻松,它全部都做了内部实现。让我们访问分布式集群环境就和访问单机一样简单。

同时Scala无缝集成Java,Scala编译器会先把源文件编译成class文件再运行。Scala可以调用所有的Java类库,也可以从Java应用程序中调用Scala的代码。它也可以访问现存的数之不尽的Java类库,这让用户(潜在地)迁移到Scala更加容易。

Java => 编译 => *.class => JVM

Scala => 编译 => *.class => JVM

扩展:groovy、clojure(storm)都可以编译成.class,利用JVM。

Scala的编译器的作者就是Java编译器的作者。

Scala入门

官网:https://www.scala-lang.org/

课程版本:2.13.1

HelloWorld

创建一个scala工程

创建一个包

创建HelloWorld.scala

img

img

编写代码

object HelloWorld {
  def main(args: Array[String]): Unit = {//入口函数
    println("HelloWorld")
  }
}

数据类型

像java分为基本数据类型和引用类型一样,scala也将数据分为两种类型,分别是值类型和引用类型。不同的是值类型(相当于java中的基本数据类型)有9个,并且都以对象形式存在,可以调用方法。

img

Any是所有类型的超类型,也称为顶级类型。它定义了一些通用的方法如equals、hashCode和toString。Any有两个直接子类:AnyVal和AnyRef。

AnyVal代表值类型。有9个预定义的非空的值类型分别是:Double、Float、Long、Int、Short、Byte、Char、Unit和Boolean。Unit是不带任何意义的值类型,它仅有一个实例可以像这样声明:()。所有的函数必须有返回值,所以Unit经常用在没有返回值时的返回值类型的声明(相当于void)。

AnyRef代表引用类型。所有非值类型都被定义为引用类型。在Scala中,每个用户自定义的类型都是AnyRef的子类型。如果Scala被应用在Java的运行环境中,AnyRef相当于java.lang.Object。

img

AnyVal类型之间的转换

各个类型所占字节长度与java相同。

但是这里数据类型的转换只能单向,比如将Int类型赋值给Long类型,如果逆向转换不能通过编译,比如将Long类型赋值给Int类型。可以转换的方向见上图。

Scala也支持数据类型的强制转换(解决逆向转换需求)例如:

    val a:Long = 12345556
    val b:Int = a.toInt

变量和常量的声明

Scala中常量用val来定义,表示不可变的量。

用var来定义变量,表示可变,在scala中推荐更多的使用val(有利于回收)。

分支和循环

if…else…

Scala中的if…else…与java中没有不同

    var age = 10
    if(age>=18){
      println("成年人")
    }else if(age<=10){
      println("儿童")
    }else{
      println("少年")
    }

他可以简写为:

    var age = 10
    if (age >= 18) println("成年人") else if (age <= 10) println("儿童") else println("少年")

to、until、Range

to:表示左闭右闭的一个范围,eg:1 to 10 => [1,10]

until: 表示左闭右开的一个范围,eg:1 to 10 => [1,9]

Range:从源码来看,to和until底层都是有Range类实现的。

for循环

    for(i <- 1 to 10){
      println(i)
    }

其中to为.to()函数的运算符形式

    var a = 1 to (10,2) //第二个参数为步长(每隔几个选定一次) 1,3,5,7,9
    var b = 1 until 10 //to 为包含结尾  until 不包含结尾
    println(a)
    println(b)

for循环练习

多层不同变量循环可以定义在一起,中间用分号隔开

九九乘法表:

     for(i<-1 until 10){
      for(j<-1 until 10){
        if(i>=j){
          print(s"$j * $i = "+i*j+"\t")
          if(i==j){
            println()
          }
        }
      }
    }

等价于

    for (i <- 1 until 10; j <- 1 until 10) {
      if (i >= j) {
        print(s"$j * $i = " + i * j + "\t")
        if (i == j) {
          println()
        }
      }
    }

除了上边说的循环条件可以定义在for之后的()里,还可以定义判断条件,用分号隔开,也可以省略

比如:打印出1到100之间大于50的偶数

    for (i <- 1 to 100; if (i > 50); if (i % 2 == 0)) {
      println(i)
    }    

同时for循环也可以得到一个返回值,这里需要通过yield关键字实现

比如:

    var vector = for (i <- 1 to 100 if (i > 50) if (i % 2 == 0)) yield i
    println(vector)

while和do…while

这个部分也与java相同,我们通过两个简单案例来看一下

    var i = 0
    while(i<10){
      println(i)
      i+=1//scala中没有++ --
    }
    var j = 0
    do{
      println(j)
      j+=1
    }while(j<10)

方法和函数

说明:在Scala中既有方法也有函数,大多数情况下,他们没有区别,作为初学者暂时不用明确区分。一般情况下方法的类型为具体一个返回值类型,可以省略不写。而函数的类型为:参数类型 => 返回值类型 ,也可以省略不写,在后边的代码中会体现这个特点。

方法的定义

    def max(a:Int,b:Int):Int={
      if(a>b) return a else return b
    }
    println(max(1, 3))

img

  1. 方法用def来定义
  2. 方法体中最后的返回值可以使用return,如果使用了return,那么方法体的返回值类型一定要定义
  3. 如果方法体中没有return,默认将方法体中的最后一行计算的结果当做返回值返回,方法体的返回值类型可以省略(自动推断)
  4. 定义方法传入的参数一定要指定类型
  5. 如果方法体为一行代码,方法的定义也可以简写

比如:

    def max(a: Int, b: Int): Int = if (a > b) return a else return b

递归方法

定义方法递归调用时必须显示的定义出函数返回值,否则其无法推断,不能通过编译

  def fun(num: Int): Int = {
    if (num == 1) 1 else num * fun(num - 1)
  }

方法的默认参数

Scala支持定义方法时对参数设置默认值。调用时输入参数则使用输入的参数,空参则使用默认值

  def sum(a: Int = 10, b: Int = 20) = {
    a + b
  }
  println(sum())//空参
  println(sum(100,200))//覆盖
  println(sum(100))//覆盖a
  println(sum(b=200))//覆盖b

可变参数的方法

与java相同,scala同样有参数可变的需求

  def sum(num: Int*): Int ={ // * 号代表此参数可以有多个,但是传入的值类型必须相同。
    var sum = 0
    for(i <- num){
      sum += i
    }
    sum
  }

嵌套方法

方法的嵌套类似if、for的嵌套,以下列子为:求5的阶乘。

  def fun1(num:Int): Int ={
    def fun2(a:Int,b:Int): Int ={
      if(a==1){
        b
      }else{
        fun2(a-1,a*b)
      }
    }
    fun2(num,1)
  }

匿名函数

匿名函数定义方法如下,一般在作为参数传入其他方法或函数中只使用一次的情况中使用,类似java中的匿名内部类。

 (a:String) => {
    println(a)
  }
当然匿名函数也可以有引用
  val stringToUnit: String => Unit = (a: String) => {
    println(a)
  }
stringToUnit("Dongcc")

偏应用函数

偏应用函数一般用来解决一个方法中的参数非常多,并且有部分参数总是不发生变化(比如下面第一段代码中,“现在是”这个参数每次都不需要变化)而每次调用这个方法总是需要传入这些不变的参数的问题。此时可以使用偏应用函数来简化这个过程(见第二段代码)。

  def showDate(s:String,date:Date): Unit ={
    println(s"$s : $date")
  }
  showDate("现在是",new Date())
  Thread.sleep(1000)
  showDate("现在是",new Date())
  Thread.sleep(1000)
  showDate("现在是",new Date())

为showDate方法定义偏应用函数showDate2

  val showDate2: Date => Unit = showDate("现在是", _: Date)
  showDate2(new Date())

高阶函数

在高阶函数中,有三种表现形式

方法的参数是函数

 def fun(f:(Int,Int)=>Int,s:String): Unit ={
    val i = f(100, 2)
    println(s"100 和 2 的 $s 是 $i")
  }
  //这里传入一个匿名函数作为参数
  fun((a:Int,b:Int)=>{a+b},"和")

方法的返回是函数

这里用一个场景说明以下代码,假设有这样一个场景,我们需要按照用户的不同情况来决定下一步应该使用什么样的函数,比如以下代码表示的是当输入年龄信息为18岁以下时,返回不可参加工作的函数,而18岁及以上时返回可以参加工作的函数。

返回的函数也可以是匿名函数,return可以省略不写,新版本中fun方法的返回值类型也可以不写。

   def fun(a: Int): (String) => Unit = {
    if (a >= 18) {
      def fun1: String => Unit = (s: String) => {
        println(s"$s 成年了,可以参加工作了")
      }
      return fun1
    } else {
      def fun2: String => Unit = (s: String) => {
        println(s"$s 未成年,不可以参加工作")
      }
      return fun2
    }
  }
  val fun1: String => Unit = fun(13)
  fun1("锤锤")
  val fun2: String => Unit = fun(22)
  fun2("铁蛋")
  //因为返回值是一个函数,所以也可以直接调用返回值
  fun(18)("Dongcc")

注意:在2.10版本之前,方法的返回值类型应该显示声明

img

方法的参数和返回都是函数

  def fun(f: (Int, Int) => Int): (String) => Unit = {
    val i = f(100, 200)
    (s: String) => {
      println(s + i)
    }
  }
  val result = fun((a: Int, b: Int) => {
    a + b
  })
  result("100+200=")
  //也可以简写成
  fun((a,b)=>{a+b})("100+200=")

柯里化函数

柯里化函数其实在高阶函数中我们已经见过他的形式

比如上边例子的最后一行:fun((a,b)=>{a+b})(“100+200=”)

  def fun(a:Int,b:Int)(c:Int,d:Int)={
    a+b+c+d
  }
  fun(1,2)(3,4)