scala_scalar - CSDN
精华内容
参与话题
  • Scala基础语法大全总结(一)

    千次阅读 2018-07-11 08:36:27
    Scala语言是一门基于JVM的编程语言,具有强大的功能,它即具有类似Java的面向对象的特性,而且也类似于C语言面向过程,函数也是一等公民,即不需要在隶属于哪一个类就可以执行。基于JVM的特性,使得scala和Java可以...

    Scala语言是一门基于JVM的编程语言,具有强大的功能,它即具有类似Java的面向对象的特性,而且也类似于C语言面向过程,函数也是一等公民,即不需要在隶属于哪一个类就可以执行。基于JVM的特性,使得scala和Java可以无缝互操作,scala可以任意操作Java的代码,两者的联系紧密。

    Scala解释器的使用:

    REPL: Read(读值)、Evaluation(计算)、Print(打印)、Loop(循环)。
    scala解释器也称REPL,可以快速编译scala代码为字节码交给JVM执行。
    在解释器中直接输入内容,将会返回一个变量,默认以res开头,然后依次res0、res1。
    在解释器中的变量可以在后续操作中继续使用,也可以读取其值。
    在scala>命令行中可以使用tab键自动补全。

    常量与变量

    在scala中定义常量使用val关键字来定义,类似于Java中使用final定义的常量,它定义常量之后不能修改,可以在后续操作中使用。如果想要修改它的值就会报错,如下所示:
    这里写图片描述
    常量的使用可以增加程序的健壮性,鲁棒性和安全性,在大型 系统中,一些固定的值通常定义为常量。

    scala中使用var关键字来定义变量,即可以修改它的值,这个定义方式类似于JavaScript中定义 方式。

    无论是常量还是变量,都可以为其添加数据类型,不过也可以不用添加,如上图所示,它会自动类型推荐,c常量就是Int类型。当然也可以定义其类型,和其他语言相反的,它的定义方式是变量名在前面,类型在后面,变量名和类型之间用冒号隔开(和GO语言类型,不过go语言不需要用冒号隔开)。

    scala> var str:String="hello world"
    str: String = hello world

    还可以多个变量或者常量一起定义(说实话和go语言挺像的):

    scala> val a,b,c=1
    a: Int = 1
    b: Int = 1
    c: Int = 1

    数据类型和操作符
    在scala中,和Java中的数据类型差不多,Byte,Char,Short,Long,Int,String,Float,Double,Boolean等等,不过需要注意的是scala中的数据类型和Java中的数据类型本质上有区别中,scala中的数据类型每一个都是一个类,scala会自己负责基本数据类型和引用类型的转换操作。因此使用上面的数据类型可以直接调用相关函数,如:

    1.toString()
    1.to(10)

    同时scala中使用了很多加强类为基本数据类型添加了上百种增强的功能和函数。例如String类通过StringOps类增强了大量的函数,如下面求两个字符串的交集字符:

    scala> "Hello".intersect("world")
    res0: String = lo

    scala也提供了RichInt、RichDouble、RichChar等类型,RichInt也会调用to函数,比如1.to(10)。此处Int会首先隐式转换为RichInt,然后再调用其to函数。(隐式转换后续文章中详细介绍,这时scala中的一个很重要的特性)

    在scala中,如+,-等各种操作符都是数据类型的函数,对于1+1这样的表达式也可以写成1.+(1)的形式。不过在scala中没有++,–这样的操作符,可以使用+=,-=来代替。

    函数调用

    和Java一样,如果调用scala的相关函数,直接导入相关包就可以调用。不过在scala中,在调用函数不传递参数的时候可以省略后面的括号,比如,“Hello World”.distinct()可以写为“Hello World”,distinct。

    在scala中,还有一种特殊的函数apply,在scala的Object中,它通常用来实例化一个类,在创建一个对象的时候,通常是 类名() 这样的形式,实际上它是调用了 类名.apply() 来实例对象。

    If条件语句与输入输出

    在scala中,与其他编程语言有很大的区别,if语句有返回值。if和else条件语句的最后一行就是if语句的返回值。

    scala> val aa=25
    aa: Int = 25
    
    scala> if (aa>20) "more than" else "less than"
    res1: String = more than

    如上所示,if语句返回了String类型的字符串,可是我们没有定义返回值的数据类型,if语句又是如何知道它的数据类型的呢?实际上,if语句会结合if和else语句最后一行返回的值来取其返回值类型的公共父类来作为返回值的类型。比如上面String和String的父类是String,Int和String类型的公共父类是Any。

    如果只有if语句而没有else语句,那么实际上是if返回值的类型和else的返回值Unit来取公共父类,Unit相当于Java 中的void或者null,也可以写作();如if(a>10) 5 相当于if(a>10) 5 else ()。

    scala默认不使用语句终结符,一行写一条语句,但是如果想要在一行写多条语句,这些语句之间必须使用“;”隔开。

    在scala中使用print或者println来输出结果,和Java 一样。输入使用readLine来从控制台读取用户输入的数据,类似于Java 中的System.in。

    循环语句

    scala中的while语句和Java中一样,可以参考Java或者C++等其他语言的while语句来使用。

    scala中的for循环语句有点类似于python中for循环,不过,写法上有点区别,首先来看个例子:

    scala> val n:Int=10
    n: Int = 10
    
    scala> var tmp=0
    tmp: Int = 0
    scala> for(i <- 1 to 10)
         | tmp+=i
    
    scala> println(tmp)
    55

    上面代码使用for循环连续求和。也可以对字符串使用for循环遍历字符串:

    scala> for(c <- "Hello World")
         | println(c)
    H
    e
    l
    l
    o
    
    W
    o
    r
    l
    d

    在scala中没有提供break等终止循环的语句,不过我们可以定义Boolean类型变量来自己实现或者使用Breaks类的函数break来终止循环,同样还可以使用return语句来终止。

    高级for循环

    for循环还提供了简介的语法,在Java中,我们写多重for循环,可能会像下面这样写:

    for(i=0;i<10;i++){
        for(j=0;j<10;j++){
           //循环体
           }
         }

    但是在 scala中,我们可以将两个for循环语句写到一个for循环语句中,例如:

    for(i <- 1 to 10;j <- 1 to 10)
      if(j==9)
        println(i*j)
      else
        print(i*j+" ")

    执行结果如下:

    1 2 3 4 5 6 7 8 9
    10 2 4 6 8 10 12 14 16 18
    20 3 6 9 12 15 18 21 24 27
    30 4 8 12 16 20 24 28 32 36
    40 5 10 15 20 25 30 35 40 45
    50 6 12 18 24 30 36 42 48 54
    60 7 14 21 28 35 42 49 56 63
    70 8 16 24 32 40 48 56 64 72
    80 9 18 27 36 45 54 63 72 81
    90 10 20 30 40 50 60 70 80 90
    100

    我们还可以有更加简洁的书写方式 ,即if守卫,比如下面代码打印偶数:

    for(i <- 1 to 10 if i%2==0)
    print(i+" ")

    我们也可以使用for循环通过yield来构造集合,如下代码所示:

    scala> for(i <- 1 to  5) yield i%3
    res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 0, 1, 2)

    函数

    scala中定义函数使用def关键字来定义一个函数,函数的每个参数必须指定参数类型,在函数体中有返回值时,与Java等其他语言不同的时,scala中不需要使用return来返回,默认的函数体最后一行的表达式就是函数的返回值,也可以不用定义函数返回值的具体返回类型,scala可以进行自动类型推断,具体定义形式看下面Demo:

    def func(name:String,age:Int)={
     println("my name is "+name+",i am "+age+"years old.")
    }
    func("shinelon",19)

    函数执行结果如下所示:

    my name is shinelon,i am 19years old.

    在函数中也可以使用缺省函数,即当不需要为某一个函数的参数传递值,需要使用默认值的时候,这时可以给参数设置默认值:

    def func(name:String,age:Int=19)={
     println("my name is "+name+",i am "+age+"years old.")
    }
    func("shinelon")

    上面这段代码和上面代码的执行结果是一样的,不过设置函数参数的默认值的时候必须将没有设置默认值的参数依次防止在最左边。

    还可以使用序列来调用变长参数的函数,不过这时函数的参数定义形式稍有改变,如下面函数形式:

    def func(nums:Int*):Int={
     if(nums.length==0) 0
     else nums.head+func(nums.tail:_*)
    }
    func(1,2,3,4,5)

    函数的执行结果如下:

    res3: Int = 15
    展开全文
  • 大数据—Scala

    千人学习 2019-06-24 16:07:22
    近年来随着大数据的兴起,大数据核心框架Spark和Kafka也受到到广泛关注, Spark底层是Scala实现的, 因此也带火了Scala语言,目前Scala被全球知名公司(如:谷歌、百度、阿里、Twitter、京东等)广泛用于Spark开发。...
  • Scala超全详解

    千次阅读 多人点赞 2018-08-16 14:49:52
    转自:...amp;fps=1】 一 Scala安装与配置 1 安装 2 配置IDEA 二 Scala基础 1 Hello Scala 11 IDEA运行HelloScala程序 12 控制台运行HelloScala程序 ...

    转自:【https://blog.csdn.net/c391183914/article/details/78647533?locationNum=2&fps=1

     

    一、 Scala安装与配置

    1.1 安装

    Scala需要Java运行时库,安装Scala需要首先安装JVM虚拟机,推荐安装JDK1.8。 
    http://www.scala-lang.org/ 下载Scala2.11.8程序安装包 
    这里写图片描述

    根据不同的操作系统选择不同的安装包,下载完成后,将安装包解压到安装目录。将scala安装目录下的bin目录加入到PATH环境变量: 
    SCALA_HOME: 
    SCALA_HOME= D:\scala-2.11.8 
    PATH: 
    在PATH变量中添加:%SCALA_HOME%\bin 
    完成以上流程后,在命令行输入:scala,进入如下界面: 
    这里写图片描述 
    注意:该操作Windows和Linux配置流程是一样的。可以参考Java的JDK的配置过程。 
    到此为止,Scala的安装已经成功。

    1.2 配置IDEA

    1) 打开IDEA工具,如图:点击Configure 
    这里写图片描述
    2) 点击Plugins 
    这里写图片描述 
    3) 点击Install plugin from disk 
    这里写图片描述 
    4) 选择scala的plugins 
    这里写图片描述 
    5) 此时会显示一个Scala的条目,在右侧点击Restart IntelliJ IDEA 
    这里写图片描述 
    6) 创建Maven项目 
    创建的maven项目默认是不支持scala的,需要为项目添加scala的framework,如图: 
    这里写图片描述 
    在这里选择Scala后,在右边的Use library中配置你的安装目录即可,最后点击OK。 
    这里写图片描述
    7) 在项目的目录结构中,创建scala文件夹,并标记为source 
    这里写图片描述
    这里写图片描述
    8) 以上配置都完成后,就可以在scala上点击右键创建scala class了 
    这里写图片描述

    二、 Scala基础

    2.1 Hello Scala

    2.1.1 IDEA运行HelloScala程序

    1) 在scala上右键,创建scala object 
    这里写图片描述 
    2) 编写代码如下:

    object HelloScala {
      def main(args: Array[String]): Unit = {
        println("Hello Scala")
      }
    }
    

    右键Run HelloScala,如图: 
    这里写图片描述

    2.1.2 控制台运行HelloScala程序

    1) 打包 
    scala程序的打包过程和java工程的打包是一样的,因为我们都是使用maven构建的项目,所以你只需要一Java的Maven工程一致的打包方式打包项目即可。即:使用idea,右侧的maven工具,双击package即可,该步骤完成后会在项目的目录结构中生成一个target文件夹,其中有打包好的jar文件如图: 
    这里写图片描述 
    打包好的jar文件: 
    这里写图片描述 
    2) 运行 
    将该jar包复制到任意位置,然后打开cmd,执行命令: 
    scala -cp C:\Users\61661\Desktop\scala-1.0-SNAPSHOT.jar HelloScala 
    即可看到效果如图: 
    这里写图片描述

    2.2 声明值和变量

    Scala声明变量有两种方式,一个用val,一个用var。 
    val / var 变量名 : 变量类型 = 变量值。 
    val定义的值是不可变的,它不是一个常量,是不可变量,或称之为只读变量。 
    val示例:

    scala> val a1 = 10
    scala> a1 = 20(此处会报错,因为val不允许初始化后再次修改a1变量的引用)

    var示例:

    scala> var a2 = 10
    scala> a2 = 20

    尖叫提示: 
    1、scala默认为匿名变量分配val 
    2、val定义的变量虽然不能改变其引用的内存地址,但是可以改变其引用的对象的内部的其他属性值。 
    3、为了减少可变性引起的bug,应该尽可能地使用不可变变量。变量类型可以省略,解析器会根据值进行推断。val和var声明变量时都必须初始化。

    2.3 常用类型

    2.3.1 常用类型介绍

    Scala有8种数据类型:Byte、Char、Short、Int、Long、Float、Double以及Boolean。

    类型 介绍
    Boolean true 或者 false
    Byte 8位, 有符号
    Short 16位, 有符号
    Int 32位, 有符号
    Long 64位, 有符号
    Char 16位, 无符号
    Float 32位, 单精度浮点数
    Double 64位, 双精度浮点数
    String 其实就是由Char数组组成

    与Java中的数据类型不同,Scala并不区分基本类型和引用类型,所以这些类型都是对象,可以调用相对应的方法。String直接使用的是java.lang.String. 不过,由于String实际是一系列Char的不可变的集合,Scala中大部分针对集合的操作,都可以用于String,具体来说,String的这些方法存在于类scala.collection.immutable.StringOps中。

    由于String在需要时能隐式转换为StringOps,因此不需要任何额外的转换,String就可以使用这些方法。

    每一种数据类型都有对应的Rich* 类型,如RichInt、RichChar等,为基本类型提供了更多的有用操作。

    2.3.2 常用类型结构图

    Scala中,所有的值都是类对象,而所有的类,包括值类型,都最终继承自一个统一的根类型Any。统一类型,是Scala的又一大特点。更特别的是,Scala中还定义了几个底层类(Bottom Class),比如Null和Nothing。

    1) Null是所有引用类型的子类型,而Nothing是所有类型的子类型。Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型,但是不能赋值给值类型。

    2) Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。

    3) Unit类型用来标识过程,也就是没有明确返回值的函数。 由此可见,Unit类似于Java里的void。Unit只有一个实例,(),这个实例也没有实质的意义。 
    这里写图片描述

    2.4 算数操作符重载

    +-*/%可以完成和Java中相同的工作,但是有一点区别,他们都是方法。你几乎可以用任何符号来为方法命名。 
    举例:

    scala> 1 + 2
    等同于:
    scala> 1.+(2)

    尖叫提示:Scala中没有++、–操作符,需要通过+=、-=来实现同样的效果。

    2.5 调用函数与方法

    在scala中,一般情况下我们不会刻意的去区分函数与方法的区别,但是他们确实是不同的东西。后面我们再详细探讨。首先我们要学会使用scala来调用函数与方法。 
    1) 调用函数,求方根

    scala> import scala.math._
    scala> sqrt(100)

    2) 调用方法,静态方法(scala中没有静态方法这个概念,需要通过伴生类对象来实现) 
    scala> BigInt.probablePrime(16, scala.util.Random) 
    3) 调用方法,非静态方法,使用对象调用

    scala> "HelloWorld".distinct

    4) apply与update方法 
    apply方法是调用时可以省略方法名的方法。用于构造和获取元素:

    "Hello"(4)  等同于  "Hello".apply(4)
    Array(1,2,3) 等同于 Array.apply(1,2,3)
    如:
    println("Hello"(4))
    println("Hello".apply(4))

    在StringOps中你会发现一个 def apply(n: Int): Char方法定义。update方法也是调用时可以省略方法名的方法,用于元素的更新:

    arr(4) = 5  等同于  arr.update(4,5)
    如:
    val arr1 = new Array[Int](5)
    arr1(1) = 2
    arr1.update(1, 2)
    println(arr1.mkString(","))

    2.6 option类型

    Scala为单个值提供了对象的包装器,表示为那种可能存在也可能不存在的值。他只有两个有效的子类对象,一个是Some,表示某个值,另外一个是None,表示为空,通过Option的使用,避免了使用null、空字符串等方式来表示缺少某个值的做法。 
    如:

    val map1 = Map("Alice" -> 20, "Bob" -> 30)
    println(map1.get("Alice"))
    println(map1.get("Jone"))

    三、 控制结构和函数

    3.1 if else表达式

    scala中没有三目运算符,因为根本不需要。scala中if else表达式是有返回值的,如果if或者else返回的类型不一样,就返回Any类型(所有类型的公共超类型)。 
    例如:if else返回类型一样

    val a3 = 10
    val a4 =
      if(a3 > 20){
        "a3大于20"
      }else{
        "a3小于20"
      }
    println(a4)

    例如:if else返回类型不一样

    val a5 =
      if(a3 > 20){
        "a3大于20"
      }
    println(a5)

    如果缺少一个判断,什么都没有返回,但是Scala认为任何表达式都会有值,对于空值,使用Unit类,写做(),叫做无用占位符,相当于java中的void。 
    尖叫提示:行尾的位置不需要分号,只要能够从上下文判断出语句的终止即可。但是如果在单行中写多个语句,则需要分号分割。在Scala中,{}快包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。

    3.2 while表达式

    Scala提供和Java一样的while和do循环,与If语句不同,While语句本身没有值,即整个While语句的结果是Unit类型的()。 
    1) while循环

    var n = 1;
    val while1 = while(n <= 10){
      n += 1
    }
    
    println(while1)
    println(n)

    2) while循环的中断

    import scala.util.control.Breaks
    val loop = new Breaks
    loop.breakable{
      while(n <= 20){
        n += 1;
        if(n == 18){
          loop.break()
        }
      }
    }
    println(n)

    尖叫提示:scala并没有提供break和continue语句来退出循环,如果需要break,可以通过几种方法来做1、使用Boolean型的控制变量 2、使用嵌套函数,从函数中return 3、使用Breaks对象的break方法。

    3.3 for表达式

    Scala 也为for 循环这一常见的控制结构提供了非常多的特性,这些for 循环的特性被称为for 推导式(for comprehension)或for 表达式(for expression)。 
    1) for示例1:to左右两边为前闭后闭的访问

    for(i <- 1 to 3; j <- 1 to 3){
      print(i * j + " ")
    }
    println()

    2) for示例2:until左右两边为前闭后开的访问

    for(i <- 1 until 3; j <- 1 until 3) {
      print(i * j + " ")
    }
    println()

    3) for示例3:引入保护式(也称条件判断式)该语句只打印1 3。保护式满足为true则进入循环内部,满足为false则跳过,类似于continue

    for(i <- 1 to 3 if i != 2) {
      print(i + " ")
    }
    println()

    4) for示例4:引入变量

    for(i <- 1 to 3; j = 4 - i) {
      print(j + " ")
    }
    println()

    5) for示例5:将遍历过程中处理的结果返回到一个,使用yield关键字

    val for5 = for(i <- 1 to 10) yield i
    println(for5)

    6) for示例6:使用花括号{}代替小括号()

    for{
      i <- 1 to 3
      j = 4 - i}
      print(i * j + " ")
    println()

    尖叫提示:{}和()对于for表达式来说都可以。for 推导式有一个不成文的约定:当for 推导式仅包含单一表达式时使用原括号,当其包含多个表达式时使用大括号。值得注意的是,使用原括号时,早前版本的Scala 要求表达式之间必须使用分号。

    3.4 函数

    scala定义函数的标准格式为: 
    def 函数名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 = {函数体} 
    1) 函数示例1:返回Unit类型的函数

    def shout1(content: String) : Unit = {
      println(content)
    }

    2) 函数示例2:返回Unit类型的函数,但是没有显式指定返回类型。(当然也可以返回非Unit类型的值)

    def shout2(content: String) = {
      println(content)
    }

    3) 函数示例3:返回值类型有多种可能,此时也可以省略Unit

    def shout3(content: String) = {
      if(content.length >= 3)
        content + "喵喵喵~"
      else
        3
    }

    4) 函数示例4:带有默认值参数的函数,调用该函数时,可以只给无默认值的参数传递值,也可以都传递,新值会覆盖默认值;传递参数时如果不按照定义顺序,则可以通过参数名来指定。

    def shout4(content: String, leg: Int = 4) = {
      println(content + "," + leg)
    }

    5) 函数示例5:变长参数(不确定个数参数,类似Java的…)

    def sum(args: Int*) = {
      var result = 0
      for(arg <- args)
        result += arg
      result
    }

    6) 递归函数:递归函数在使用时必须有明确的返回值类型

    def factorial(n: Int): Int = {
      if(n <= 0)
        1
      else
        n * factorial(n - 1)
    }

    尖叫提示: 
    1、Scala可以通过=右边的表达式 推断出函数的返回类型。如果函数体需要多个表达式,可以用代码块{}。 
    2、可以把return 当做 函数版本的break语句。 
    3、递归函数一定要指定返回类型。 
    4、变长参数通过*来指定,所有参数会转化为一个seq序列。

    3.5 过程

    我们将函数的返回类型为Unit的函数称之为过程。 
    1) 定义过程示例1:

    def shout1(content: String) : Unit = {
      println(content)
    }

    2) 定义过程示例2:

    def shout1(content: String) = {
      println(content)
    }

    3) 定义过程示例3:

    def shout1(content: String) {
      println(content)
    }

    尖叫提示:这只是一个逻辑上的细分,如果因为该概念导致了理解上的混淆,可以暂时直接跳过过程这样的描述。毕竟过程,在某种意义上也是函数。

    3.6 懒值

    当val被声明为lazy时,他的初始化将被推迟,直到我们首次对此取值,适用于初始化开销较大的场景。 
    1) lazy示例:通过lazy关键字的使用与否,来观察执行过程

    object Lazy {
    
      def init(): String = {
        println("init方法执行")
        "嘿嘿嘿,我来了~"
      }
    
      def main(args: Array[String]): Unit = {
        lazy val msg = init()
        println("lazy方法没有执行")
        println(msg)
      }
    }

    3.7 异常

    当碰到异常情况时,方法抛出一个异常,终止方法本身的执行,异常传递到其调用者,调用者可以处理该异常,也可以升级到它的调用者。运行系统会一直这样升级异常,直到有调用者能处理它。 如果一直没有处理,则终止整个程序。

    Scala的异常的工作机制和Java一样,但是Scala没有“checked”异常,你不需要声明说函数或者方法可能会抛出某种异常。受检异常在编译器被检查,java必须声明方法所会抛出的异常类型。 
    抛出异常:用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。 
    捕捉异常:在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句。

    异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。

    finally字句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作。 
    1) 异常示例:

    object ExceptionSyllabus {
    
      def divider(x: Int, y: Int): Float= {
        if(y == 0) throw new Exception("0作为了除数")
        else x / y
      }
    
      def main(args: Array[String]): Unit = {
            try {
              println(divider(10, 3))
            } catch {
              case ex: Exception => println("捕获了异常:" + ex)
            } finally {}
      }
    }

    四、 数据结构

    4.1 数据结构特点

    Scala同时支持可变集合和不可变集合,不可变集合从不可变,可以安全的并发访问。 
    两个主要的包: 
    不可变集合:scala.collection.immutable 
    可变集合: scala.collection.mutable 
    Scala优先采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。 
    不可变集合继承层次: 
    这里写图片描述
    可变集合继承层次: 
    这里写图片描述

    4.2 数组 Array

    1) 定长数组

    //定义
    val arr1 = new Array[Int](10)
    //赋值
    arr1(1) = 7
    或:
    //定义
    val arr1 = Array(1, 2)

    2) 变长数组

    //定义
    val arr2 = ArrayBuffer[Int]()
    //追加值
    arr2.append(7)
    //重新赋值
    arr2(0) = 7

    3) 定长数组与变长数组的转换

    arr1.toBuffer
    arr2.toArray

    4) 多维数组

    //定义
    val arr3 = Array.ofDim[Double](3,4)
    //赋值
    arr3(1)(1) = 11.11

    5) 与Java数组的互转 
    Scal数组转Java数组:

    val arr4 = ArrayBuffer("1", "2", "3")
    //Scala to Java
    import scala.collection.JavaConversions.bufferAsJavaList
    val javaArr = new ProcessBuilder(arr4)
    println(javaArr.command())

    Java数组转Scala数组:

    import scala.collection.JavaConversions.asScalaBuffer
    import scala.collection.mutable.Buffer
    val scalaArr: Buffer[String] = javaArr.command()
    println(scalaArr)

    6) 数组的遍历

    for(x <- arr1) {
      println(x)
    }

    4.3 元组 Tuple

    元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。 
    1) 元组的创建

    val tuple1 = (1, 2, 3, "heiheihei")
    println(tuple1)

    2) 元组数据的访问,注意元组元素的访问有下划线,并且访问下标从1开始,而不是0

    val value1 = tuple1._4
    println(value1)

    3) 元组的遍历 
    方式1:

    for (elem <- tuple1.productIterator) {
      print(elem)
    }
    println()

    方式2:

    tuple1.productIterator.foreach(i => println(i))
    tuple1.productIterator.foreach(print(_))

    4.4 列表 List

    如果List列表为空,则使用Nil来表示。 
    1) 创建List

    val list1 = List(1, 2)
    println(list1)

    2) 访问List元素

    val value1 = list1(1)
    println(value1)

    3) List元素的追加

    val list2 = list1 :+ 99
    println(list2)
    val list3 = 100 +: list1
    println(list3)

    4) List的创建与追加,符号“::”,注意观察去掉Nil和不去掉Nil的区别

    val list4 = 1 :: 2 :: 3 :: list1 :: Nil
    println(list4)

    4.5 队列 Queue

    队列数据存取符合先进先出策略 
    1) 队列的创建

    import scala.collection.mutable
    val q1 = new mutable.Queue[Int]
    println(q1)

    2) 队列元素的追加

    q1 += 1;
    println(q1)

    3) 向队列中追加List

    q1 ++= List(2, 3, 4)
    println(q1)

    4) 按照进入队列的顺序删除元素

    q1.dequeue()
    println(q1)

    5) 塞入数据

    q1.enqueue(9, 8, 7)
    println(q1)

    6) 返回队列的第一个元素

    println(q1.head)

    7) 返回队列最后一个元素

    println(q1.last)

    8) 返回除了第一个以外的元素

    println(q1.tail)

    4.6 映射 Map

    这个地方的学习,就类比Java的map集合学习即可。 
    1) 构造不可变映射

    val map1 = Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)

    2) 构造可变映射

    val map2 = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)

    3) 空的映射

    val map3 = new scala.collection.mutable.HashMap[String, Int]

    4) 对偶元组

    val map4 = Map(("Alice", 10), ("Bob", 20), ("Kotlin", 30))

    5) 取值 
    如果映射中没有值,则会抛出异常,使用contains方法检查是否存在key。如果通过 映射.get(键) 这样的调用返回一个Option对象,要么是Some,要么是None。

    val value1 = map1("Alice")//建议使用get方法得到map中的元素
    println(value1)

    6) 更新值

    map2("Alice") = 99
    println(map2("Alice"))
    或:
    map2 += ("Bob" -> 99)
    map2 -= ("Alice", "Kotlin")
    println(map2)
    或:
    val map5 = map2 + ("AAA" -> 10, "BBB" -> 20)
    println(map5)

    7) 遍历

    for ((k, v) <- map1) println(k + " is mapped to " + v)
    for (v <- map1.keys) println(v)
    for (v <- map1.values) println(v)
    for(v <- map1) prinln(v)

    4.7 集 Set

    集是不重复元素的结合。集不保留顺序,默认是以哈希集实现。

    如果想要按照已排序的顺序来访问集中的元素,可以使用SortedSet(已排序数据集),已排序的数据集是用红黑树实现的。

    默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包。 
    1) Set不可变集合的创建

    val set = Set(1, 2, 3)
    println(set)

    2) Set可变集合的创建,如果import了可变集合,那么后续使用默认也是可变集合

    import scala.collection.mutable.Set
    val mutableSet = Set(1, 2, 3)

    3) 可变集合的元素添加

    mutableSet.add(4)
    mutableSet += 6
    // 注意该方法返回一个新的Set集合,而非在原有的基础上进行添加
    mutableSet.+(5)

    4) 可变集合的元素删除

    mutableSet -= 1
    mutableSet.remove(2)
    println(mutableSet)

    5) 遍历

    for(x <- mutableSet) {
      println(x)
    }

    6) Set更多常用操作

    序号 方法 描述
    1 def +(elem: A): Set[A] 为集合添加新元素,并创建一个新的集合,除非元素已存在
    2 def -(elem: A): Set[A] 移除集合中的元素,并创建一个新的集合
    3 def contains(elem: A): Boolean 如果元素在集合中存在,返回 true,否则返回 false。
    4 def &(that: Set[A]): Set[A] 返回两个集合的交集
    5 def &~(that: Set[A]): Set[A] 返回两个集合的差集
    6 def ++(elems: A): Set[A] 合并两个集合
    7 def drop(n: Int): Set[A]] 返回丢弃前n个元素新集合
    8 def dropRight(n: Int): Set[A] 返回丢弃最后n个元素新集合
    9 def dropWhile(p: (A) => Boolean): Set[A] 从左向右丢弃元素,直到条件p不成立
    10 def max: A 查找最大元素
    11 def min: A 查找最小元素
    12 def take(n: Int): Set[A] 返回前 n 个元素

    4.8 集合元素与函数的映射

    1) map:将集合中的每一个元素映射到某一个函数

    val names = List("Alice", "Bob", "Nick")
    println(names.map(_.toUpperCase))

    2) flatmap:flat即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合

    val names = List("Alice", "Bob", "Nick")
    println(names.flatMap(_.toUpperCase()))

    4.9 化简、折叠、扫描

    1) 折叠,化简:将二元函数引用于集合中的函数

    val list = List(1, 2, 3, 4, 5)
    val i1 = list.reduceLeft(_ - _)
    val i2 = list.reduceRight(_ - _)
    println(i1)
    println(i2)

    .reduceLefft(_ - _)这个函数的执行逻辑如图所示: 
    这里写图片描述 
    .reduceRight(_ - _)反之同理 
    2) 折叠,化简:fold 
    fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历。可以把reduceLeft看做简化版的foldLeft。相关函数:fold,foldLeft,foldRight,可以参考reduce的相关方法理解。

    val list2 = List(1, 9, 2, 8)
    val i4 = list2.fold(5)((sum, y) => sum + y)
    println(i4)

    foldRight

    val list3 = List(1, 9, 2, 8)
    val i5 = list3.foldRight(100)(_ - _)
    println(i5)

    尖叫提示:foldLeft和foldRight有一种缩写方法对应分别是/:和:\ 
    例如: 
    foldLeft

    val list4 = List(1, 9, 2, 8)
    val i6 = (0 /: list4)(_ - _)
    println(i6)

    3) 统计一句话中,各个文字出现的次数

    val sentence = "一首现代诗《笑里藏刀》:哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈刀哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈"
    //m +  (“一” -> 1, “首” -> 1, “哈” -> 1)
    val i7 = (Map[Char, Int]() /: sentence)((m, c) => m + (c -> (m.getOrElse(c, 0) + 1)))
    println(i7)

    4) 折叠,化简,扫描 
    这个理解需要结合上面的知识点,扫描,即对某个集合的所有元素做fold操作,但是会把产生的所有中间结果放置于一个集合中保存。

    val i8 = (1 to 10).scanLeft(0)(_ + _)
    println(i8)

    4.10 拉链

    val list1 = List("15837312345", "13737312345", "13811332299")
    val list2 = List(17, 87)
    println(i1)

    4.11 迭代器

    你可以通过iterator方法从集合获得一个迭代器,通过while循环和for表达式对集合进行遍历。

    val iterator = List(1, 2, 3, 4, 5).iterator
    while (iterator.hasNext) {
      println(iterator.next())
    }
    或:
    for(enum <- iterator) {
      println(enum)
    }

    4.12 流 Stream

    stream是一个集合。这个集合,可以用于存放,无穷多个元素,但是这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循lazy规则。 
    1) 使用#::得到一个stream

    def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)

    2) 传递一个值,并打印stream集合

    val tenOrMore = numsForm(10)
    println(tenOrMore)

    3) tail的每一次使用,都会动态的向stream集合按照规则生成新的元素

    println(tenOrMore.tail)
    println(tenOrMore)

    4) 使用map映射stream的元素并进行一些计算

    println(numsForm(5).map(x => x * x))

    4.13 视图 View

    Stream的懒执行行为,你可以对其他集合应用view方法来得到类似的效果,该方法产出一个其方法总是被懒执行的集合。但是view不会缓存数据,每次都要重新计算。 
    例如:我们找到10万以内,所有数字倒序排列还是它本身的数字。

    val viewSquares = (1 to 100000)
      .view
      .map(x => {
    //        println(x)
        x.toLong * x.toLong
    }).filter(x => {
      x.toString == x.toString.reverse
    })
    
    println(viewSquares(3))
    
    for(x <- viewSquares){
      print(x + ",")
    }

    4.14 线程安全的集合

    所有线程安全的集合都是以Synchronized开头的集合,例如: 
    SynchronizedBuffer 
    SynchronizedMap 
    SynchronizedPriorityQueue 
    SynchronizedQueue 
    SynchronizedSet 
    SynchronizedStack

    4.15 并行集合

    Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。 
    主要用到的算法有: 
    Divide and conquer : 分治算法,Scala通过splitters,combiners等抽象层来实现,主要原理是将计算工作分解很多任务,分发给一些处理器去完成,并将它们处理结果合并返回 
    Work stealin:算法,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的。 
    1) 打印1~5

    (1 to 5).foreach(println(_))
    println()
    (1 to 5).par.foreach(println(_))

    2) 查看并行集合中元素访问的线程

    val result1 = (0 to 10000).map{case _ => Thread.currentThread.getName}.distinct
    val result2 = (0 to 10000).par.map{case _ => Thread.currentThread.getName}.distinct
    println(result1)
    println(result2)

    4.16 操作符

    这部分内容没有必要刻意去理解和记忆,语法使用的多了,自然就会产生感觉,该部分内容暂时大致了解一下即可。 
    1) 如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号反引号:

    val `val` = 42

    2) 这种形式叫中置操作符,A操作符B等同于A.操作符(B) 
    3) 后置操作符,A操作符等同于A.操作符,如果操作符定义的时候不带()则调用时不能加括号 
    4) 前置操作符,+、-、!、~等操作符A等同于A.unary_操作符 
    5) 赋值操作符,A操作符=B等同于A=A操作符B

    五、 模式匹配

    5.1 switch

    与default等效的是捕获所有的case_ 模式。如果没有模式匹配,抛出MatchError,每个case中,不用break语句。可以在match中使用任何类型,而不仅仅是数字。

    var result = 0;
    val op : Char = '-'
    
    op match {
      case '+' => result = 1
      case '-' => result = -1
      case _ => result = 0
    }
    println(result)

    5.2 守卫

    像if表达式一样,match也提供守卫功能,守卫可以是任何Boolean条件

    for (ch <- "+-3!") {
      var sign = 0
      var digit = 0
    
      ch match {
        case '+' => sign = 1
        case '-' => sign = -1
        case _ if ch.toString.equals("3") => digit = 3
        case _ => sign = 0
      }
      println(ch + " " + sign + " " + digit)
    }

    5.3 模式中的变量

    如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量。

    val str = "+-3!"
    for (i <- str.indices) {
      var sign = 0
      var digit = 0
    
      str(i) match {
        case '+' => sign = 1
        case '-' => sign = -1
        case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10)
        case _ =>
      }
      println(str(i) + " " + sign + " " + digit)
    }

    5.4 类型模式

    可以匹配对象的任意类型,但是不能直接匹配泛型类型,这样描述比较抽象,看下面的例子:这样做的意义在于,避免了使用isInstanceOf和asInstanceOf方法。

    val a = 8
      val obj = if(a == 1) 1
      else if(a == 2) "2"
      else if(a == 3) BigInt(3)
      else if(a == 4) Map("aa" -> 1)
      else if(a == 5) Map(1 -> "aa")
      else if(a == 6) Array(1, 2, 3)
      else if(a == 7) Array("aa", 1)
      else if(a == 8) Array("aa")
      val r1 = obj match {
        case x: Int => x
        case s: String => s.toInt
        case BigInt => -1 //不能这么匹配
        case _: BigInt => Int.MaxValue
        case m: Map[String, Int] => "Map[String, Int]类型的Map集合"
        case m: Map[_, _] => "Map集合"
        case a: Array[Int] => "It's an Array[Int]"
        case a: Array[String] => "It's an Array[String]"
        case a: Array[_] => "It's an array of something other than Int"
        case _ => 0
      }
      println(r1 + ", " + r1.getClass.getName)
    

    尖叫提示:Map类型的泛型在匹配的时候,会自动删除泛型类型,只会匹配到Map类型,而不会精确到Map里面的泛型类型。

    5.5 匹配数组、列表、元组

    Array(0) 匹配只有一个元素且为0的数组。 
    Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。 
    Array(0,_*) 匹配数组以0开始。 
    1) 匹配数组

    for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))) {
      val result = arr match {
        case Array(0) => "0"
        case Array(x, y) => x + " " + y
        case Array(x, y, z) => x + " " + y + " " + z
        case Array(0, _*) => "0..."
        case _ => "something else"
      }
      println(result)
    }

    2) 匹配列表 
    与匹配数组相似,同样可以应用于列表

    for (lst <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) {
      val result = lst match {
        case 0 :: Nil => "0"
        case x :: y :: Nil => x + " " + y
        case 0 :: tail => "0 ..."
        case _ => "something else"
      }
      println(result)
    }

    3) 匹配元组 
    同样可以应用于元组

    for (pair <- Array((0, 1), (1, 0), (1, 1))) {
      val result = pair match {
        case (0, _) => "0 ..."
        case (y, 0) => y + " 0"
        case _ => "neither is 0"
      }
      println(result)
    }

    5.6 提取器

    模式匹配,什么才算是匹配呢?即,case中unapply方法返回some集合则为匹配成功,返回none集合则为匹配失败。下面我们来看几个例子做详细探讨。 
    1) unapply 
    —— 调用unapply,传入number 
    —— 接收返回值并判断返回值是None,还是Some 
    —— 如果是Some,则将其解开,并将其中的值赋值给n(就是case Square(n)中的n) 
    创建object Square:

    object Square {
      def unapply(z: Double): Option[Double] = Some(math.sqrt(z))
    }

    模式匹配使用:

    val number: Double = 36.0
    number match {
      case Square(n) => println(s"square root of $number is $n")
      case _ => println("nothing matched")
    }

    2) unapplySeq 
    —— 调用unapplySeq,传入namesString 
    —— 接收返回值并判断返回值是None,还是Some 
    —— 如果是Some,则将其解开 
    —— 判断解开之后得到的sequence中的元素的个数是否是三个 
    —— 如果是三个,则把三个元素分别取出,赋值给first,second和third 
    创建object Names:

    object Names {
      def unapplySeq(str: String): Option[Seq[String]] = {
        if (str.contains(",")) Some(str.split(","))
        else None
      }
    }

    模式匹配使用:

    val namesString = "Alice,Bob,Thomas"
    namesString match {
      case Names(first, second, third) => {
        println("the string contains three people's names")
        println(s"$first $second $third")
      }
      case _ => println("nothing matched")
    }

    5.7 变量声明中的模式

    match中每一个case都可以单独提取出来,意思是一样的,如下:

    val (x, y) = (1, 2)
    val (q, r) = BigInt(10) /% 3
    val arr = Array(1, 7, 2, 9)
    val Array(first, second, _*) = arr
    println(first, second)

    5.8 for表达式中的模式

    import scala.collection.JavaConverters._
    for ((k, v) <- System.getProperties.asScala)
      println(k + " -> " + v)
    
    for ((k, "") <- System.getProperties.asScala)
      println(k)
    
    for ((k, v) <- System.getProperties.asScala if v == "")
      println(k)

    尖叫提示:for中匹配会自动忽略失败的匹配

    5.9 样例类

    样例类首先是类,除此之外它是为模式匹配而优化的类,样例类用case关键字进行声明: 
    1) 样例类的创建

    package unit6
    
    abstract class Amount
      case class Dollar(value: Double) extends Amount
      case class Currency(value: Double, unit: String) extends Amount
      case object Nothing extends Amount

    2) 当我们有一个类型为Amount的对象时,我们可以用模式匹配来匹配他的类型,并将属性值绑定到变量:

    for (amt <- Array(Dollar(1000.0), Currency(1000.0, "EUR"), Nothing)) {
      val result = amt match {
        case Dollar(v) => "$" + v
        case Currency(_, u) => u
        case Nothing => ""
      }
      println(amt + ": " + result)
    }

    当你声明样例类时,如下几件事情会自动发生: 
    —— 构造其中的每一个参数都成为val——除非它被显式地声明为var(不建议这样做) 
    —— 在半生对象中提供apply方法让你不用new关键字就能构造出相应的对象,比如Dollar(29.95)或Currency(29.95, “EUR”) 
    —— 提供unapply方法让模式匹配可以工作 
    —— 将生成toString、equals、hashCode和copy方法——除非显式地给出这些方法的定义。 
    除上述外,样例类和其他类型完全一样。你可以添加方法和字段,扩展它们。

    5.10 Copy方法和带名参数

    copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。

    val amt = Currency(29.95, "EUR")
    val price = amt.copy(value = 19.95)
    println(amt)
    println(price)
    println(amt.copy(unit = "CHF"))

    5.11 Case语句的中置(缀)表达式

    什么是中置表达式?1 + 2,这就是一个中置表达式。如果unapply方法产出一个元组,你可以在case语句中使用中置表示法。比如可以匹配一个List序列。

    List(1, 7, 2, 9) match {
      case first :: second :: rest => println(first + second + rest.length)
      case _ => 0
    }

    5.12 匹配嵌套结构

    比如某一系列商品想捆绑打折出售 
    1) 创建样例类

    abstract class Item
    case class Article(description: String, price: Double) extends Item
    case class Bundle(description: String, discount: Double, item: Item*) extends Item

    2) 匹配嵌套结构

    val sale = Bundle("愚人节大甩卖系列", 10,
        Article("《九阴真经》", 40),
        Bundle("从出门一条狗到装备全发光的修炼之路系列", 20,
          Article("《如何快速捡起地上的装备》", 80),
          Article("《名字起得太长躲在树后容易被地方发现》",30)))

    3) 将descr绑定到第一个Article的描述

    val result1 = sale match {
      case Bundle(_, _, Article(descr, _), _*) => descr
    }
    println(result1)

    4) 通过@表示法将嵌套的值绑定到变量。_*绑定剩余Item到rest

    val result2 = sale match {
      case Bundle(_, _, art @ Article(_, _), rest @ _*) => (art, rest)
    }
    println(result2)

    5) 不使用_*绑定剩余Item到rest

    val result3 = sale match {
      case Bundle(_, _, art @ Article(_, _), rest) => (art, rest)
    }
    println(result3)

    6) 计算某个Item价格的函数,并调用

    def price(it: Item): Double = {
      it match {
        case Article(_, p) => p
        case Bundle(_, disc, its@_*) => its.map(price _).sum - disc
      }
    }
    
    println(SwitchBaseSyllabus.price(sale))

    5.13 密封类

    如果想让case类的所有子类都必须在申明该类的相同的文件中定义,可以将样例类的通用超类声明为sealed,叫做密封类,密封就是外部用户不能在其他文件中定义子类。

    5.14 模拟枚举

    样例类可以模拟出枚举类型 
    1) 创建密封样例类(不密封也可以,在这里只是为了用一下sealed关键字)

    package unit6
    sealed abstract class TrafficLightColor
    case object Red extends TrafficLightColor
    case object Yellow extends TrafficLightColor
    case object Green extends TrafficLightColor

    2) 模拟枚举

    for (color <- Array(Red, Yellow, Green))
      println(
        color match {
          case Red => "stop"
          case Yellow => "slowly"
          case Green => "go"
        })

    5.15 偏函数

    偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算

    val f: PartialFunction[Char, Int] = {
      case '+' => 1
      case '-' => -1
    }
    println(f('-'))
    println(f.isDefinedAt('0'))
    println(f('+'))
    //    println(f('0'))

    再深入探讨一点点: 
    我们定义一个将List集合里面数据+1的偏函数:

    val f1 = new PartialFunction[Any, Int] {
      def apply(any: Any) = any.asInstanceOf[Int] + 1
    
      def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
    }
    val rf1 = List(1, 3, 5, "seven") collect f1
    println(rf1)

    如上的功能,等同于:

    def f2: PartialFunction[Any, Int] = {
      case i: Int => i + 1
    }
    val rf2 = List(1, 3, 5, "seven") collect f2
    println(rf2)

    六、 高阶函数

    6.1 作为参数的高阶函数

    函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:function1,即:(参数类型) => 返回类型

    def plus(x: Int) = 3 + x
    val result1 = Array(1, 2, 3, 4).map(plus(_))
    println(result1.mkString(","))

    尖叫提示:带有一个参数的函数的类型是function1,带有两个是function2,以此类推

    6.2 匿名函数

    即没有名字的函数,可以通过函数表达式来设置匿名函数。

    val triple = (x: Double) => 3 * x
    println(triple(3))

    6.3 高阶函数

    能够接受函数作为参数的函数,叫做高阶函数。 
    1) 高阶函数的使用

    def highOrderFunction1(f: Double => Double) = f(10)
    def minus7(x: Double) = x - 7
    val result2 = highOrderFunction1(minus7)
    println(result2)

    2) 高阶函数同样可以返回函数类型

    def minusxy(x: Int) = (y: Int) => x - y
    val result3 = minusxy(3)(5)
    println(result3)

    6..4 参数(类型)推断

    // 传入函数表达式
    highOrderFunction1((x: Double) => 3 * x)
    // 参数推断省去类型信息
    highOrderFunction1((x) => 3 * x)
    // 单个参数可以省去括号
    highOrderFunction1(x => 3 * x)
    // 如果变量旨在=>右边只出现一次,可以用_来代替
    highOrderFunction1(3 * _)

    6.5 闭包

    闭包就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来。

    def minusxy(x: Int) = (y: Int) => x - y

    这就是一个闭包: 
    1) 匿名函数(y: Int) => x -y嵌套在minusxy函数中。 
    2) 匿名函数(y: Int) => x -y使用了该匿名函数之外的变量x 
    3) 函数minusxy返回了引用了局部变量的匿名函数 
    再举一例:

    def minusxy(x: Int) = (y: Int) => x - y
    val f1 = minusxy(10)
    val f2 = minusxy(10)
    println(f1(3) + f2(3))

    此处f1,f2这两个函数就叫闭包。

    6.6 柯里化

    函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化,柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作,所以这也印证了,柯里化就是以函数为主体这种思想发展的必然产生的结果。 
    1) 柯里化示例

    def mul(x: Int, y: Int) = x * y
    println(mul(10, 10))
    
    def mulCurry(x: Int) = (y: Int) => x * y
    println(mulCurry(10)(9))
    
    def mulCurry2(x: Int)(y:Int) = x * y
    println(mulCurry2(10)(8))

    2) 柯里化的应用 
    比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务: 
    1、全部转大写(或小写) 
    2、比较是否相等 
    针对这两个操作,我们用一个函数去处理的思想,其实无意间也变成了两个函数处理的思想。示例如下:

    val a = Array("Hello", "World")
    val b = Array("hello", "world")
    println(a.corresponds(b)(_.equalsIgnoreCase(_)))

    其中corresponds函数的源码如下:

    def corresponds[B](that: GenSeq[B])(p: (A,B) => Boolean): Boolean = {
    val i = this.iterator
    val j = that.iterator
    while (i.hasNext && j.hasNext)
      if (!p(i.next(), j.next()))
        return false
    
    !i.hasNext && !j.hasNext
    }

    尖叫提示:不要设立柯里化存在的意义这样的命题,柯里化,是面向函数思想的必然产生结果。

    6.7 控制抽象

    控制抽象是一类函数: 
    1、参数是函数。 
    2、函数参数没有输入值也没有返回值。 
    1) 使用示例

    def runInThread(f1: () => Unit): Unit = {
      new Thread {
        override def run(): Unit = {
          f1()
        }
      }.start()
    }
    
    runInThread {
      () => println("干活咯!")
      Thread.sleep(5000)
      println("干完咯!")
      }

    是不是很爽?是不是有点类似线程池的感觉,同一个线程,可以动态的向里面塞不同的任务去执行。 
    可以再简化一下,省略(),看下如下形式:

    def runInThread(f1: => Unit): Unit = {
      new Thread {
        override def run(): Unit = {
          f1
        }
      }.start()
    }
    
    runInThread {
      println("干活咯!")
      Thread.sleep(5000)
      println("干完咯!")
      }

    2) 进阶用法:实现类似while的until函数

    def until(condition: => Boolean)(block: => Unit) {
      if (!condition) {
        block
        until(condition)(block)
      }
    }
    
    var x = 10
    until(x == 0) {
      x -= 1
      println(x)
    }

    七、 类

    7.1 简单类和无参方法

    类的定义可以通过class关键字实现,如下:

    package unit7
    
    class Dog {
      private var leg = 4
      def shout(content: String) {
        println(content)
      }
      def currentLeg = leg
    }

    使用这个类:

    val dog = new Dog
    dog shout "汪汪汪"
    println(dog currentLeg)

    尖叫提示:在Scala中,类并不声明为Public,一个Scala源文件可以包含多个类。所有这些类都具有公有可见性。调用无参方法时,可以加(),也可以不加;如果方法定义中不带括号,那么调用时就不能带括号。

    7.2 Getter Setter方法

    对于scala类中的每一个属性,编译后,会有一个私有的字段和相应的getter、setter方法生成:

    //getter
    println(dog leg)
    //setter
    dog.leg_=(10)
    println(dog currentLeg)

    当然了,你也可以不使用自动生成的方式,自己定义getter和setter方法

    class Dog2 {
      private var _leg = 4
      def leg = _leg
      def leg_=(newLeg: Int) {
        _leg = newLeg
      }
    }

    使用之:

    val dog2 = new Dog2
    dog2.leg_=(10)
    println(dog2.leg)

    尖叫提示:自己手动创建变量的getter和setter方法需要遵循以下原则: 
    1) 字段属性名以“_”作为前缀,如:_leg 
    2) getter方法定义为:def leg = _leg 
    3) setter方法定义时,方法名为属性名去掉前缀,并加上后缀,后缀是:“leg_=”,如例子所示

    7.3 对象私有字段

    变量:workDetails在封闭包professional中的任何类中可访问。 
    封闭包:friends的任何类都可以被society包中任何类访问。 
    变量:secrets只能在实例方法的隐式对象(this)中访问。

    package unit7
    
    package society {
      package professional {
        class Executive {
          private[professional] var workDetails = null
          private[society] var friends = null
          private[this] var secrets = null
    
          def help(another: Executive) {
            println(another.workDetails)
    //        println(another.secrets) 报错:访问不到
          }
        }
      }
    }

    7.4 Bean属性

    JavaBeans规范定义了Java的属性是像getXXX()和setXXX()的方法。许多Java工具都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时,这样的方法会自动生成。 
    1) 创建一个Bean,使用@BeanProperty注解标识某个属性变量

    package unit7
    import scala.beans.BeanProperty
    class Person {
      @BeanProperty var name: String = _
    }

    2) 通过getName、setName访问属性

    val person = new Person
    person.setName("Nick")
    person.getName
    println(person.name)

    尖叫提示: 
    Person将会生成四个方法: 
    —— name:String 
    —— name_=(newValue:String): Unit 
    —— getName():String 
    —– setName(newValue:String):Unit

    7.5 构造器

    scala中构造分为主构造器和辅助构造器 
    1) 主构造的参数直接放置于类名之后 
    定义类:

    class ClassConstructor (var name: String, private var price: Double){
      def myPrintln = println(name + "," + price)
    }

    执行:

    val classConstructor = new ClassConstructor("《傲慢与偏见》", 20.5)
    classConstructor.myPrintln

    2) 主构造器会执行类定义中的所有语句 
    定义类:

    class ClassConstructor2(val name: String = "", val price: Double = 0) {
      println(name + "," + price)
    }

    执行:

    val classConstructor2 = new ClassConstructor2("aa", 20)
    val classConstructor2_2 = new ClassConstructor2()

    3) 通过private设置的主构造器的私有属性 
    参考1) 
    4) 如果不带val和var的参数至少被一个方法使用,该参数将自动升级为字段,这时,name和price就变成了类的不可变字段,而且这两个字段是对象私有的,这类似于 private[this] val 字段的效果。 
    否则,该参数将不被保存为字段,即实例化该对象时传入的参数值,不会被保留在实例化后的对象之中。

    主构造器参数 生成的字段/方法
    name: String 对象私有字段。如果没有方法使用name, 则没有该字段
    private val/var name: String 私有字段,私有的getter和setter方法
    var name: String 私有字段,公有的getter和setter方法
    @BeanProperty val/var name: String 私有字段,公有的Scala版和Java版的getter和setter方法

    如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了

    class Person private () {...}

    5) 辅助构造器名称为this,通过不同参数进行区分,每一个辅助构造器都必须以主构造器或者已经定义的辅助构造器的调用开始

    class Person {
      private var name = ""
      private var age = 0
    
      def this(name: String) {
        this()
        this.name = name
      }
      def this(name: String, age: Int) {
        this(name)
        this.age = age
      }
    
      def description = name + " is " + age + " years old"
    }

    7.6 嵌套类

    即,在class中,再定义一个class,以此类推。 
    Java中的内部类从属于外部类。Scala中内部类从属于实例。 
    1) 创建一个嵌套类,模拟局域网的聊天场景

    import scala.collection.mutable.ArrayBuffer
    //嵌套类
    class Network {
      class Member(val name: String) {
        val contacts = new ArrayBuffer[Member]
      }
      private val members = new ArrayBuffer[Member]
      def join(name: String) = {
        val m = new Member(name)
        members += m
        m
      }
    }

    2) 使用该嵌套类

    //创建两个局域网
    val chatter1 = new Network
    val chatter2 = new Network
    
    //Fred 和 Wilma加入局域网1
    val fred = chatter1.join("Fred")
    val wilma = chatter1.join("Wilma")
    //Barney加入局域网2
    val barney = chatter2.join("Barney")
    
    //Fred将同属于局域网1中的Wilma添加为联系人
    fred.contacts += wilma
    //fred.contacts += barney //这样做是不行的,Fred和Barney不属于同一个局域网,即,Fred和Barney不是同一个class Member实例化出来的对象

    在Scala中,每个实例都有它自己的Member类,就和他们有自己的members字段一样。也就是说,chatter1.Member和chatter2.Member是不同的两个类。也就是所谓的:路径依赖类型,此处需要详细解释之。 
    如果想让members接受所有实例的Member,一般有两种办法: 
    1) 将Member作为Network的伴生对象存在 
    创建类:

    import scala.collection.mutable.ArrayBuffer
    
    // 伴生对象
    class Network2 {
      private val members = new ArrayBuffer[Network2.Member]
      def join(name: String) = {
        val m = new Network2.Member(name)
        members += m
        m
      }
      def description = "该局域网中的联系人:" +
        (for (m <- members) yield m.description).mkString(", ")
    }
    
    object Network2 {
      class Member(val name: String) {
        val contacts = new ArrayBuffer[Member]
        def description = name + "的联系人:" +
          (for (c <- contacts) yield c.name).mkString(" ")
      }
    }

    使用:

    val chatter3 = new Network2
    val chatter4 = new Network2
    
    //Fred 和 Wilma加入局域网1
    val fred2 = chatter3.join("Fred")
    val wilma2 = chatter3.join("Wilma")
    //Barney加入局域网2
    val barney2 = chatter4.join("Barney")
    //Fred将同属于局域网3中的Wilma添加为联系人
    fred2.contacts += wilma2
    //Fred将不同属于局域网3中,属于局域网4中的的Wilma添加为联系人
    fred2.contacts += barney2
    
    println(chatter3.description)
    println(chatter4.description)
    
    println(fred2.description)
    println(wilma2.description)
    println(barney2.description)

    2) 使用类型投影,注意留意关键符号:“#” 
    创建类:

    import scala.collection.mutable.ArrayBuffer
    
    //投影
    class Network3 {
      class Member(val name: String) {
        val contacts = new ArrayBuffer[Network3#Member]
      }
    
      private val members = new ArrayBuffer[Member]
    
      def join(name: String) = {
        val m = new Member(name)
        members += m
        m
      }
    }

    使用:

    val chatter5 = new Network3
    val chatter6 = new Network3
    
    //Fred 和 Wilma加入局域网1
    val fred3 = chatter5.join("Fred")
    val wilma3 = chatter5.join("Wilma")
    //Barney加入局域网2
    val barney3 = chatter6.join("Barney")
    fred3.contacts += wilma3

    尖叫提示:与Java一样,在嵌套类中,如果想得到外部类的实例化对象的引用,可以使用“外部类.this”的方式得到。

    八、 对象

    8.1 单例对象

    Scala中没有静态方法和静态字段,可以用object这个语法结构来达到同样的目的。

    object Dog {
      println("已初始化...")
      private var leg = 0
      def plus() = {
        leg += 1
        leg
      }
    }

    对象的构造器在该对象第一次使用时调用。如果对象没有使用过,他的构造器也不会被执行。 
    对象基本具有类的所有特性,就是一点,你不能设置构造器的参数。

    8.2 伴生对象

    Java中的类可以既有实例方法又有静态方法,Scala中可以通过伴生对象进行实现。如下:

    class Cat {
      val hair = Cat.growHair
      private var name = ""
      def changeName(name: String) = {
        this.name = name
      }
      def describe = println("hair:" + hair + "name:" + name)
    }
    
    object Cat {
      private var hair = 0
    
      private def growHair = {
        hair += 1
        hair
      }
    }

    测试:

    val cat1 = new Cat
    val cat2 = new Cat
    
    cat1.changeName("黑猫")
    cat2.changeName("白猫")
    
    cat1.describe
    cat2.describe

    尖叫提示:类和它的伴生对象可以相互访问私有特性,他们必须存在同一个源文件中。必须同名

    8.3 Apply方法

    1) apply方法一般都声明在伴生类对象中,可以用来实例化伴生类的对象:

    class Man private(val sex: String, name: String) {
      def describe = {
        println("Sex:" + sex + "name:" + name)
      }
    }
    
    object Man {
      def apply(name: String) = {
        new Man("男", name)
      }
    }

    测试:

    val man1 = Man("Nick")
    val man2 = Man("Thomas")
    man1.describe
    man2.describe

    2) 也可以用来实现单例模式,我们只需要对上述列子稍加改进:

    class Man private(val sex: String, name: String) {
      def describe = {
        println("Sex:" + sex + "name:" + name)
      }
    }
    
    object Man {
      var instance: Man = null
      def apply(name: String) = {
        if(instance == null) {
          instance = new Man("男", name)
        }
        instance
      }
    }

    测试:

    val man1 = Man("Nick")
    val man2 = Man("Thomas")
    man1.describe
    man2.describe

    8.4 应用程序对象

    每一个Scala应用程序都需要从一个对象的main方法开始执行,这个方法的类型为Array[String]=>Unit:

    object Hello {
      def main(args: Array[String]) {
        println("Hello, World!")
      }
    }

    或者扩展一个App特质:

    object Hello extends App {
      if (args.length > 0)
        println("Hello, " + args(0))
      else
        println("Hello, World!")
    }

    8.5 枚举

    Scala中没有枚举类型,定义一个扩展Enumeration类的对象,并以value调用初始化枚举中的所有可能值:

    object TrafficLightColor extends Enumeration {
      val Red = Value(0, "Stop")
      val Yellow = Value(1, "Slow")
      val Green = Value(2, "Go")
    }

    测试:

    println(TrafficLightColor.Red)
    println(TrafficLightColor.Red.id)
    
    println(TrafficLightColor.Yellow)
    println(TrafficLightColor.Yellow.id)
    
    println(TrafficLightColor.Green)
    println(TrafficLightColor.Green.id)

    九、 包和引用

    9.1 包/作用域

    在Java和Scala中管理项目可以使用包结构,C和C#使用命名空间。 
    对于package,有如下几种形式: 
    1) 形式体现:

    package com.nick.impatient.people
    class Person{
      val name = "Nick"
      def play(message: String): Unit ={
      }
    }

    等同于:

    package com.nick.impatient
    package people
    class Person{
      val name = "Nick"
      def play(message: String): Unit ={
      }
    }

    等同于:

    package com.nick.impatient{// com和com.nick的成员在这里不可见
      package people{
        class Person{
          val name = "Nick"
          def play(message: String): Unit ={
          }
        }
      }
    }

    尖叫提示:位于文件顶部不带花括号的包声明在整个文件范围内有效。 
    通过以上形式,发出总结: 
    1、包也可以像内部类那样嵌套,作用域原则:可以直接向上访问。即,子package中直接访问父package中的内容。(即:作用域) 
    2、包对象可以持有函数和变量 
    3、引入语句可以引入包、类和对象 
    4、源文件的目录和包之间并没有强制的关联关系 
    5、可以在同一个.scala文件中,声明多个并列的package 
    6、包名可以相对也可以绝对,比如,访问ArrayBuffer的绝对路径是:root.scala.collection.mutable.ArrayBuffer

    9.2 包对象

    包可以包含类、对象和特质,但不能包含函数或变量的定义。很不幸,这是Java虚拟机的局限。把工具函数或常量添加到包而不是某个Utils对象,这是更加合理的做法。包对象的出现正是为了解决这个局限。每个包都可以有一个包对象。你需要在父包中定义它,且名称与子包一样。

    package com.nick.impatient
    
    package object people {
      val defaultName = "Nick"
    }
    package people {
    
      class Person {
        var name = defaultName // 从包对象拿到的常置
      }
    }

    9.3 包可见性

    在Java中,没有被声明为public、private或protected的类成员在包含该类的包中可见。在Scala中,你可以通过修饰符达到同样的效果。以下方法在它自己的包中可见:

    package com.nick.impatient.people
    class Person {
      private[people] def description="人的名字:" + name
    }

    当然,也可以将可见度延展到上层包:

    private[impatient] def description="人的名字:" + name

    9.4 引入

    在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部。import语句的效果一直延伸到包含该语句的块末尾:这是个很有用的特性,尤其是对于通配引入而言。从多个源引入大量名称总是让人担心。事实上,有些Java程序员特别不喜欢通配引入,以至于从不使用这个特性,而是让IDE帮他们生成一长串引入语句。通过将引入放置在需要这些引入的地方,你可以大幅减少可能的名称冲突。

    9.5 重命名和隐藏方法

    1) 重命名: 
    如果你想要引人包中的几个成员,可以像这样使用选取器( selector),而且选取的同时,可以重命名:

    import java.util.{ HashMap=>JavaHashMap, List}
    import scala.collection.mutable._

    这样一来,JavaHashMap就是java.utiI.HashMap,而HashMap则对应

    scala.collection.mutable.HashMap。

    2) 隐藏: 
    选取器HashMap =>_将隐藏某个成员而不是重命名它。这仅在你需要引入其他成员时有用:

    import java.util.{HashMap=>_, _ }
    import scala.collection.mutable._

    现在,HashMap很明确的便指向了scala.collection.mutable.HashMap,因为java.util.HashMap被隐藏起来了。 
    尖叫提示: 
    每个Scala程序都隐式地以如下代码开始: 
    import java.lang._ 
    import scala._ 
    import Predef._ 
    和Java程序一样,java.lang总是被引入。接下来,scala包也被引入,不过方式有些特殊。不像所有其他引入,这个引入被允许可以覆盖之前的引入。举例来说,scala.StringBuilder覆盖java.lang.StringBuilder而不是与之冲突。最后,Predef对象被引入。它包含了相当多有用的函数,这些同样可以被放置在scala包对象中,不过Predef在Scala还没有加入包对象之前就存在了。 
    由于scala包默认被引入,对于那些以scala开头的包,你完全不需要写全这个前缀。例如:

    collection.mutable.HashMap

    上述代码和以下写法一样好:

    scala.collection.mutable. HashMap

    十、 继承

    10.1 继承类

    和Java一样使用extends关键字,在定义中给出子类需要而超类没有的字段和方法,或者重写超类的方法。

    class Person  {
      var name = ""
    }
    
    class Employee extends Person{
      var salary = 0.0
      def description = "员工姓名:" + name + " 薪水:" + salary
    }

    尖叫提示:如果类声明为final,他讲不能被继承。如果单个方法声明为final,将不能被重写。

    10.2 重写方法

    重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字

    class Person {
      var name = ""
      override def toString = getClass.getName + "[name=" + name + "]"
    }
    
    class Employee extends Person {
      var salary = 0.0
      override def toString = super.toString + "[salary=" + salary + "]"
    }

    10.3 类型检查和转换

    要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。 
    1) classOf[String]就如同Java的 String.class 
    2) obj.isInstanceOf[T]就如同Java的obj instanceof T 
    3) obj.asInstanceOf[T]就如同Java的(T)obj

    println("Hello".isInstanceOf[String])
    println("Hello".asInstanceOf[String])
    println(classOf[String])

    10.4 受保护的字段和方法

    protected在scala中比Java要更严格一点,即, 只有继承关系才可以访问,同一个包下,也是不可以的。

    10.5 超类的构造

    类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须以对先前定义的辅助构造器或主构造器的调用开始。子类的辅助构造器最终都会调用主构造器,只有主构造器可以调用超类的构造器。辅助构造器永远都不可能直接调用超类的构造器。在Scala的构造器中,你不能调用super(params)。

    class Person(val name: String, val age: Int) {
      override def toString = getClass.getName + "[name=" + name +
        ",age=" + age + "]"
    }
    class Employee(name: String, age: Int, val salary : Double) extends Person(name, age) {
      override def toString = super.toString + "[salary=" + salary + "]"
    }

    10.6 重名字段

    子类改写父类或者抽象父类的字段,通过以下方式:

    class Person1(val name:String,var age:Int){
      println("主构造器已经被调用")
      val school="五道口职业技术学院"
      def sleep="8 hours"
      override def toString="我的学校是:" + school + "我的名字和年龄是:" + name + "," + age
    }
    
    class Person2(name:String, age:Int) extends Person1(name, age){
      override val school: String = "清华大学"
    }

    尖叫提示: 
    1、def只能重写另一个def 
    2、val只能重写另一个val或不带参数的def 
    3、var只能重写另一个抽象的var 
    什么是抽象var?

    abstract class Person3 {
      var name:String
    }

    10.7 匿名子类

    和Java一样,你可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类:

    class Person4(val name: String) {
      override def toString = getClass.getName + "[name=" + name + "]"
    }

    使用:

    val alien = new Person4("Fred") {
      def greeting = "Greetings, Earthling! My name is Fred."
    }
    println(alien.greeting)

    10.8 抽象类

    可以通过abstract关键字标记不能被实例化的类。方法不用标记abstract,只要省掉方法体即可。类可以拥有抽象字段,抽象字段就是没有初始值的字段。

    abstract class Person(val pname: String) {
      val id: Int
      var name: String
      def idString: Int
    }
    
    class Employee(pname: String) extends Person(pname) {
      val id = 5;
      var name = ">>>"
      def idString = pname.hashCode
    }

    尖叫提示:子类实现抽象方法不需要override

    10.9 构造顺序和提前定义

    当子类重写了父类的方法或者字段后,父类又依赖这些字段或者方法初始化,这个时候就会出现问题,比如:

    class Creature {
      val range: Int = 10
      val env: Array[Int] = new Array[Int](range)
    }
    class Ant extends Creature {
      override val range = 2
    }

    此时的构造顺序为:

    1) Ant的构造器在做它自己的构造之前,调用Creature的构造器 
    2) Creature的构造器将它的range字段设为10 
    3) Creature的构造器为了初始化env数组,调用range()取值器 
    4) 该方法被重写以输出(还未初始化的)Ant类的range字段值 
    5) range方法返回0,(这是对象被分配空间时所有整形字段的初始值) 
    6) env被设为长度为0的数组。 
    7) Ant构造器继续执行,将其range字段设为2. 
    那么env的大小是多少?是0,惊不惊喜,意不意外?

    问题解决,3种方案:

    1) 可以将val声明为final,这样子类不可改写。 
    2) 可以将超类中将val声明为lazy,这样安全但并不高效。 
    3) 还可以使用提前定义语法,可以在超类的构造器执行之前初始化子类的val字段:

    class Ant2 extends {
      override val range = 3
    } with Creature

    10.10 Scala继承层级

    在scala中,所有其他类都是AnyRef的子类,类似Java的Object。 
    AnyVal和AnyRef都扩展自Any类。Any类是跟节点 
    Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等。 
    Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量。 
    Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。

    十一、 特质

    11.1 不允许多重集成

    所有的面向对象的语言都不允许直接的多重继承,因为会出现“deadly diamond of death”问题。Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现多个特质。

    11.2 当做接口使用的特质

    特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质。

    trait Logger {
      def log(msg: String)
    }
    
    class ConsoleLogger extends Logger with Cloneable with Serializable {
      def log(msg: String) {
        println(msg)
      }
    }

    Logger with Cloneable with Serializable是一个整体,extends这个整体 
    所有的java接口都可以当做Scala特质使用。

    11.3 带有具体实现的特质

    特质中的方法并不一定是抽象的:

    trait ConsoleLogger {
      def log(msg: String) {
        println(msg)
      }
    }
    
    class Account {
      protected var balance = 0.0
    }
    
    class SavingsAccount extends Account with ConsoleLogger {
      def withdraw(amount: Double) {
        if (amount > balance) log("余额不足")
        else balance -= amount
      }
    }

    11.4 带有特质的对象

    在构建对象时混入某个具体的特质,覆盖掉抽象方法,提供具体实现:

    trait Logger {
      def log(msg: String)
    }
    
    trait ConsoleLogger extends Logger {
      def log(msg: String) {
        println(msg)
      }
    }
    
    class Account {
      protected var balance = 0.0
    }
    
    abstract class SavingsAccount extends Account with Logger {
      def withdraw(amount: Double) {
        if (amount > balance) log("余额不足")
        else balance -= amount
      }
    }
    
    object Main extends App {
      val account = new SavingsAccount with ConsoleLogger
      account.withdraw(100)
    }

    11.5 叠加在一起的特质

    super并不是指继承关系,而是指的加载顺序。 
    继承多个相同父特质的类,会从右到左依次调用特质的方法。Super指的是继承特质左边的特质,从源码是无法判断super.method会执行哪里的方法,如果想要调用具体特质的方法,可以指定:super[ConsoleLogger].log(…).其中的泛型必须是该特质的直接超类类型

    trait Logger {
      def log(msg: String);
    }
    
    trait ConsoleLogger extends Logger {
      def log(msg: String) {
        println(msg)
      }
    }
    
    trait TimestampLogger extends ConsoleLogger {
      override def log(msg: String) {
        super.log(new java.util.Date() + " " + msg)
      }
    }
    
    trait ShortLogger extends ConsoleLogger {
      override def log(msg: String) {
        super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
      }
    }
    
    class Account {
      protected var balance = 0.0
    }
    
    abstract class SavingsAccount extends Account with Logger {
      def withdraw(amount: Double) {
        if (amount > balance) log("余额不足")
        else balance -= amount
      }
    }
    
    object Main extends App {
      val acct1 = new SavingsAccount with TimestampLogger with ShortLogger
      val acct2 = new SavingsAccount with ShortLogger with TimestampLogger
      acct1.withdraw(100)
      acct2.withdraw(100)
    }

    11.6 在特质中重写抽象方法

    trait Logger2 {
      def log(msg: String)
    }
    
    //因为有super,Scala认为log还是一个抽象方法
    trait TimestampLogger2 extends Logger2 {
      abstract override def log(msg: String) {
        super.log(new java.util.Date() + " " + msg)
      }
    }
    
    trait ShortLogger2 extends Logger2 {
      abstract override def log(msg: String) {
        super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
      }
    }
    
    trait ConsoleLogger2 extends Logger2 {
      override def log(msg: String) {
        println(msg)
      }
    }
    
    class Account2 {
      protected var balance = 0.0
    }
    
    abstract class SavingsAccount2 extends Account2 with Logger2 {
      def withdraw(amount: Double) {
        if (amount > balance) log("余额不足")
        else balance -= amount
      }
    }
    
    object Main2 extends App {
      val acct1 = new SavingsAccount2 with ConsoleLogger2 with TimestampLogger2 with ShortLogger2
      acct1.withdraw(100)
    }

    11.7 当做富接口使用的特质

    即该特质中既有抽象方法,又有非抽象方法

    //富特质
    trait Logger3 {
      def log(msg: String)
    
      def info(msg: String) {
        log("INFO: " + msg)
      }
    
      def warn(msg: String) {
        log("WARN: " + msg)
      }
    
      def severe(msg: String) {
        log("SEVERE: " + msg)
      }
    }
    
    trait ConsoleLogger3 extends Logger3 {
      def log(msg: String) {
        println(msg)
      }
    }
    
    class Account3 {
      protected var balance = 0.0
    }
    
    abstract class SavingsAccount3 extends Account3 with Logger3 {
      def withdraw(amount: Double) {
        if (amount > balance) severe("余额不足")
        else balance -= amount
      }
    }
    
    object Main3 extends App {
      val acct = new SavingsAccount with ConsoleLogger
      acct.withdraw(100)
    }

    11.8 特质中的具体字段

    特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。 
    混入该特质的类就具有了该字段,字段不是继承,而是简单的加入类。是自己的字段。

    trait Logger4 {
      def log(msg: String)
    }
    
    trait ConsoleLogger4 extends Logger4 {
      def log(msg: String) {
        println(msg)
      }
    }
    
    trait ShortLogger4 extends Logger4 {
      val maxLength = 15
      abstract override def log(msg: String) {
        super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
      }
    }
    
    class Account4 {
      protected var balance = 0.0
    }
    
    class SavingsAccount4 extends Account4 with ConsoleLogger4 with ShortLogger4 {
      var interest = 0.0
      def withdraw(amount: Double) {
        if (amount > balance) log("余额不足")
        else balance -= amount
      }
    }
    
    object Main4 extends App {
      val acct = new SavingsAccount4
      acct.withdraw(100)
      println(acct.maxLength)
    }

    11.9 特质中的抽象字段

    特质中未被初始化的字段在具体的子类中必须被重写。

    //特质中的具体字段
    trait Logger5 {
      def log(msg: String)
    }
    
    trait ConsoleLogger5 extends Logger5 {
      def log(msg: String) {
        println(msg)
      }
    }
    
    trait ShortLogger5 extends Logger5 {
      val maxLength: Int
    
      abstract override def log(msg: String) {
        super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
      }
    }
    
    class Account5 {
      protected var balance = 0.0
    }
    
    abstract class SavingsAccount5 extends Account5 with Logger5 {
      var interest = 0.0
    
      def withdraw(amount: Double) {
        if (amount > balance) log("余额不足")
        else balance -= amount
      }
    }
    
    object Main5 extends App {
      val acct = new SavingsAccount5 with ConsoleLogger5 with ShortLogger5 {
        val maxLength = 20
      }
      acct.withdraw(100)
      println(acct.maxLength)
    }

    11.10 特质构造顺序

    特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成

    trait Logger6 {
      println("我在Logger6特质构造器中,嘿嘿嘿。。。")
      def log(msg: String)
    }
    
    trait ConsoleLogger6 extends Logger6 {
      println("我在ConsoleLogger6特质构造器中,嘿嘿嘿。。。")
      def log(msg: String) {
        println(msg)
      }
    }
    
    trait ShortLogger6 extends Logger6 {
      val maxLength: Int
      println("我在ShortLogger6特质构造器中,嘿嘿嘿。。。")
    
      abstract override def log(msg: String) {
        super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
      }
    }
    
    class Account6 {
      println("我在Account6构造器中,嘿嘿嘿。。。")
      protected var balance = 0.0
    }
    
    abstract class SavingsAccount6 extends Account6 with ConsoleLogger6 with ShortLogger6{
      println("我再SavingsAccount6构造器中")
      var interest = 0.0
      override val maxLength: Int = 20
      def withdraw(amount: Double) {
        if (amount > balance) log("余额不足")
        else balance -= amount
      }
    }
    
    object Main6 extends App {
      val acct = new SavingsAccount6 with ConsoleLogger6 with ShortLogger6
      acct.withdraw(100)
      println(acct.maxLength)
    }

    步骤总结: 
    1、调用当前类的超类构造器 
    2、第一个特质的父特质构造器 
    3、第一个特质构造器 
    4、第二个特质构造器的父特质构造器由于已经执行完成,所以不再执行 
    5、第二个特质构造器 
    6、当前类构造器

    11.11 初始化特质中的字段

    特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造器参数是特质与类之间唯一的技术差别。除此之外,特质可以具备类的所有特性,比如具体的和抽象的字段,以及超类。现在有如下情景:我们想通过特质来实现日志数据的输出,输出到某一个文件中

    import java.io.PrintStream
    
    
    trait Logger7{
      def log(msg:String)
    }
    
    trait FileLogger7 extends Logger7{
      val fileName:String
      val out = new PrintStream(fileName)
    
      override def log(msg: String): Unit = {
        out.print(msg)
        out.flush()
      }
    }
    
    class SavingsAccount7{
    
    }
    
    object Main7 extends App {
      val acct = new SavingsAccount7 with FileLogger7 {
        override val fileName = "2017-11-24.log"//空指针异常
      }
    }

    如果想修复如上错误,可以: 
    1) 使用“提前定义”

    import java.io.PrintStream
    
    trait Logger7 {
      def log(msg: String)
    }
    
    trait FileLogger7 extends Logger7 {
      val fileName: String
      val out = new PrintStream(fileName)
    
      override def log(msg: String): Unit = {
        out.print(msg)
        out.flush()
      }
    }
    
    class SavingsAccount7 {
    
    }
    
    object Main7 extends App {
      //提前定义
      val acct = new {
        override val fileName = "2017-11-24.log"
      } with SavingsAccount7 with FileLogger7
      acct.log("heiheihei")
    }

    或这样提前定义:

    package unit12
    
    import java.io.PrintStream
    
    trait Logger7 {
      def log(msg: String)
    }
    
    trait FileLogger7 extends Logger7 {
      val fileName: String
      val out = new PrintStream(fileName)
    
      override def log(msg: String): Unit = {
        out.print(msg)
        out.flush()
      }
    }
    
    //提前定义在这里
    class SavingsAccount7 extends {
      override val fileName = "2017-11-24.log"
    } with FileLogger7
    
    object Main7 extends App {
      val acct = new SavingsAccount7 with FileLogger7
      acct.log("嘿嘿嘿")
    }
    2)  使用lazy
    package unit12
    
    import java.io.PrintStream
    
    trait Logger7 {
      def log(msg: String)
    }
    
    trait FileLogger7 extends Logger7 {
      val fileName: String
      lazy val out = new PrintStream(fileName)
    
      override def log(msg: String): Unit = {
        out.print(msg)
        out.flush()
      }
    }
    
    class SavingsAccount7 {
    
    }
    
    object Main7 extends App {
      val acct = new SavingsAccount7 with FileLogger7 {
        override val fileName = "2017-11-24.log"
      }
      acct.log("哈哈哈")
    }

    11.12 扩展类的特质

    总结: 
    1、特质可以继承自类,以用来拓展该类的一些功能 
    2、所有混入该特质的类,会自动成为那个特质所继承的超类的子类 
    3、如果混入该特质的类,已经继承了另一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。 
    例如: 
    1) 特质可以继承自类,以用来拓展该类的一些功能

    trait LoggedException extends Exception{
      def log(): Unit ={
        println(getMessage())
      }
    }

    2) 所有混入该特质的类,会自动成为那个特质所继承的超类的子类

    class UnhappyException extends LoggedException{
      override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
    }

    3) 如果混入该特质的类,已经继承了另一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。 
    正确:

    class UnhappyException2 extends IndexOutOfBoundsException with LoggedException{
      override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
    }

    错误:

    class UnhappyException3 extends JFrame with LoggedException{
      override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
    }

    11.13 自身类型

    主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。

    比如:
    //自身类型特质
    trait Logger9{
      this: Exception =>
      def log(): Unit ={
        println(getMessage)
      }
    }

    这样一来,在该特质中,可以随意调用“自身类型”中的各种方法。

    十二、 注解

    注解就是标签。 
    标签是用来标记某些代码需要特殊处理的。 
    处理的手段可以在代码运行时操作,也可以在编译期操作。

    12.1 什么可以被注解

    1) 可以为类,方法,字段局部变量,参数,表达式,类型参数以及各种类型定义添加注解

    @Entity class Student
    @Test def play() {}
    @BeanProperty var username = _
    def doSomething(@NotNull message: String) {}
    @BeanProperty @Id var username = _

    2) 构造器注解,需要在主构造器之前,类名之后,且需要加括号,如果注解有参数,则写在注解括号里

    class Student @Inject() (var username: String, var password: String)

    3) 为表达式添加注解,在表达式后添加冒号

    (map1.get(key): @unchecked) match {...}

    4) 泛型添加注解

    class Student[@specialized T]

    5) 实际类型添加注解

    String @cps[Unit]

    12.2 注解参数

    Java注解可以有带名参数:

    @Test(timeout = 100, expected = classOf[IOException])
    // 如果参数名为value,则该名称可以直接略去。
    @Named("creds") var credentials: Credentials = _  // value参数的值为 “creds”
    // 注解不带参数,圆括号可以省去
    @Entity class Credentials

    Java 注解的参数类型只能是: 
    数值型的字面量 
    字符串 
    类字面量 
    Java枚举 
    其他注解 
    上述类型的数组(但不能是数组的数组) 
    Scala注解可以是任何类型,但只有少数几个Scala注解利用了这个增加的灵活性。

    12.3 注解实现

    你可以实现自己的注解,但是更多的是使用Scala和Java提供的注解。 
    注解必须扩展Annotation特质:

    class unchecked extends annotation.Annotation

    12.4 针对Java的注解

    1) Java修饰符:对于那些不是很常用的Java特性,Scala使用注解,而不是修饰符关键字。

    @volatile var done = false  // JVM中将成为volatile的字段
    @transient var recentLookups = new HashMap[String, String]  // 在JVM中将成为transient字段,该字段不会被序列化。
    @strictfp def calculate(x: Double) = ...
    @native def win32RegKeys(root: Int, path: String): Array[String]

    2) 标记接口:Scala用注解@cloneable和@remote 而不是 Cloneable和Java.rmi.Remote“标记接口”来标记可被克隆的对象和远程的对象。

    @cloneable class Employee

    3) 受检异常:和Scala不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws注解来生成正确的签名。

    class Book {
    @throws (classOf[IOException]) def read(filename: String) { ... }
    ...
    }
    Java版本的方法签名:
    void read(String fileName) throws IOException
    // 如果没有@throws注解,Java代码将不能捕获该异常
    try {//Java代码
    book.read("war-and-peace.txt");
    } catch (IOException ex) {
    ...
    }

    即:Java编译期需要在编译时就知道read方法可以抛IOException异常,否则Java会拒绝捕获该异常。

    12.5 由于优化的注解

    尾递归的优化 
    啥玩是尾递归? 
    尾递归:

    def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story()}

    尖叫提示:进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回。 
    非尾递归:

    def story(): Unit =  {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story(),小和尚听了,找了块豆腐撞死了}

    尖叫提示:下一个函数结束以后此函数还有后续,所以必须保存本身的环境以供处理返回值。 
    递归调用有时候能被转化成循环,这样能节约栈空间:

    object Util {
    def sum(xs: Seq[Int]): BigInt = {
     if (xs.isEmpty) 0 else xs.head + sum(xs.tail)
    }
    ...
    }

    上面的sum方法无法被优化,因为计算过程中最后一步是加法,而不是递归调用。调整后的代码:

    def sum2(xs: Seq[Int], partial: BigInt): BigInt = {
    if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial)
    }

    Scala编译器会自动对sum2应用“尾递归”优化。如果你调用sum(1 to 1000000) 将会发生一个栈溢出错误。不过sum2(1 to 1000000, 0) 将会得到正确的结果。 
    尽管Scala编译器会尝试使用尾递归优化,但有时候某些不太明显的原因会造成它无法这样做。如果你想编译器无法进行优化时报错,则应该给你的方法加上@tailrec注解。 
    *尖叫提示:对于消除递归,一个更加通用的机制叫做“蹦床”。蹦床的实现会将执行一个循环,不停的调用函数。每个函数都返回下一个将被调用的函数。尾递归在这里是一个特例,每个函数都返回它自己。*Scala有一个名为TailCalls的工具对象,帮助我们轻松实现蹦床:

    import scala.util.control.TailCalls._
    def evenLength(xs: Seq[Int]): TailRec[Boolean] = {
      if(xs.isEmpty) done(true) else tailcall(oddLength(xs.tail))
    }
    
    def oddLength(xs: Seq[Int]): TailRec[Boolean] = {
      if(xs.isEmpty) done(false) else tailcall(evenLength(xs.tail))
    }
    
    // 获得TailRec对象获取最终结果,可以用result方法
    evenLength(1 to 1000000).result

    十三、 类型参数(先了解即可)

    13.1 泛类型

    类和特质都可以带类型参数,用方括号来定义类型参数,可以用类型参数来定义变量、方法参数和返回值。带有一个或多个类型参数的类是泛型的。如下p1,如果实例化时没有指定泛型类型,则scala会自动根据构造参数的类型自动推断泛型的具体类型。

    class Pair[T, S](val first: T, val second: S) {
      override def toString = "(" + first + "," + second + ")"
    }
    //从构造参数推断类型
    val p1 = new Pair(42, "String")
    //设置类型
    val p2 = new Pair[Any, Any](42, "String")

    13.2 泛型函数

    函数或方法也可以有类型(泛型)参数。

    // 从参数类型来推断类型
    println(getMiddle(Array("Bob", "had", "a", "little", "brother")).getClass.getTypeName)
    //指定类型,并保存为具体的函数。
    val f = getMiddle[String] _
    println(f(Array("Bob", "had", "a", "little", "brother")))

    13.3 类型变量限定

    在Java泛型里不表示某个泛型是另外一个泛型的子类型可以使用extends关键字,而在scala中使用符号“<:”,这种形式称之为泛型的上界。

    class Pair1[T <: Comparable[T]](val first: T, val second: T) {
      def smaller = if (first.compareTo(second) < 0) first else second
    }
    
    object Main1 extends App{
      override def main(args: Array[String]): Unit = {
        val p = new Pair1("Fred", "Brooks")
        println(p.smaller)
      }
    }

    在Java泛型里表示某个泛型是另外一个泛型的父类型,使用super关键字,而在scala中,使用符号“>:”,这种形式称之为泛型的下界。

    class Pair2[T](val first: T, val second: T) {
      def replaceFirst[R >: T](newFirst: R) = new Pair2[R](newFirst, second)
      override def toString = "(" + first + "," + second + ")"
    }
    
    object Main2 extends App{
      override def main(args: Array[String]): Unit = {
        val p = new Pair2("Nick", "Alice")
        println(p)
        println(p.replaceFirst("Joke"))
        println(p)
      }
    }

    在Java中,T同时是A和B的子类型,称之为多界,形式如:

    13.5 视图界定

    在Scala中,如果你想标记某一个泛型可以隐式的转换为另一个泛型,可以使用:[T <% Comparable[T]],由于Scala的Int类型没有实现Comparable接口,所以我们需要将Int类型隐式的转换为RichInt类型,比如:

    class Pair3[T <% Comparable[T]](val first: T, val second: T) {
      def smaller = if (first.compareTo(second) < 0) first else second
      override def toString = "(" + first + "," + second + ")"
    }
    
    object Main3 extends App {
      val p = new Pair3(4, 2)
      println(p.smaller)
    }

    13.6 上下文界定

    视图界定 T <% V要求必须存在一个从T到V的隐式转换。上下文界定的形式为T:M,其中M是另一个泛型类,它要求必须存在一个类型为M[T]的隐式值。 
    下面类定义要求必须存在一个类型为Ordering[T]的隐式值,当你使用了一个使用了隐式值得方法时,传入该隐式参数。

    class Pair4[T: Ordering](val first: T, val second: T) {
      def smaller(implicit ord: Ordering[T]) = {
        println(ord)
        if (ord.compare(first, second) < 0) first else second
      }
    
      override def toString = "(" + first + "," + second + ")"
    }
    
    object Main4 extends App{
      override def main(args: Array[String]): Unit = {
        val p4 = new Pair4(1, 2)
        println(p4.smaller)
      }
    }

    13.7 Manifest上下文界定

    Manifest是scala2.8引入的一个特质,用于编译器在运行时也能获取泛型类型的信息。在JVM上,泛型参数类型T在运行时是被“擦拭”掉的,编译器把T当作Object来对待,所以T的具体信息是无法得到的;为了使得在运行时得到T的信息,scala需要额外通过Manifest来存储T的信息,并作为参数用在方法的运行时上下文。

    def test[T] (x:T, m:Manifest[T]) { ... }

    有了Manifest[T]这个记录T类型信息的参数m,在运行时就可以根据m来更准确的判断T了。但如果每个方法都这么写,让方法的调用者要额外传入m参数,非常不友好,且对方法的设计是一道伤疤。好在scala中有隐式转换、隐式参数的功能,在这个地方可以用隐式参数来减轻调用者的麻烦。

    def foo[T](x: List[T])(implicit m: Manifest[T]) = {
      println(m)
      if (m <:< manifest[String])
        println("Hey, this list is full of strings")
      else
        println("Non-stringy list")
    }
    
    foo(List("one", "two"))
    foo(List(1, 2))
    foo(List("one", 2))

    隐式参数m是由编译器根据上下文自动传入的,比如上面是编译器根据 “one”,”two” 推断出 T 的类型是 String,从而隐式的传入了一个Manifest[String]类型的对象参数,使得运行时可以根据这个参数做更多的事情。 
    不过上面的foo 方法定义使用隐式参数的方式,仍显得啰嗦,于是scala里又引入了“上下文绑定”,

    def foo[T](x: List[T]) (implicit m: Manifest[T])

    可以简化为: 
    def foo[T:Manifest] (x: List[T]) 
    在引入Manifest的时候,还引入了一个更弱一点的ClassManifest,所谓的弱是指类型信息不如Manifest那么完整,主要针对高阶类型的情况 
    scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路径依赖类型中,Manifest存在问题:

    scala> class Foo{class Bar}
    defined class Foo
    
    scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
    warning: there were 2 deprecation warnings; re-run with -deprecation for details
    m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar]
    
    scala> val f1 = new Foo;val b1 = new f1.Bar
    f1: Foo = Foo@681e731c
    b1: f1.Bar = Foo$Bar@271768ab
    
    scala> val f2 = new Foo;val b2 = new f2.Bar
    f2: Foo = Foo@3e50039c
    b2: f2.Bar = Foo$Bar@771d16b9
    
    scala> val ev1 = m(f1)(b1)
    warning: there were 2 deprecation warnings; re-run with -deprecation for details
    ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar
    
    scala> val ev2 = m(f2)(b2)
    warning: there were 2 deprecation warnings; re-run with -deprecation for details
    ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar
    
    scala> ev1 == ev2 // they should be different, thus the result is wrong
    res28: Boolean = true

    了解之后,我们总结一下,TypeTag到底有啥用呢?看下面的例子: 
    请留意: 
    =:=,意思为:type equality 
    <:< ,意思为:subtype relation 
    类型判断不要用 == 或 !=

    class Animal{}
    class Dog extends Animal{}
    
    object MainFoo extends App{
      override def main(args: Array[String]): Unit = {
        val list1 = List(1, 2, 3)
        val list2 = List("1", "2", "3")
        val list3 = List("1", "2", 3)
    
        def test1(x: List[Any]) = {
          x match {
            case list: List[Int] => "Int list"
            case list: List[String] => "String list"
            case list: List[Any] => "Any list"
          }
        }
        println(test1(list1))
        println(test1(list2))
        println(test1(list3))
    
        import scala.reflect.runtime.universe._
        def test2[A : TypeTag](x: List[A]) = typeOf[A] match {
          case t if t =:= typeOf[String] => "String List"
          case t if t <:< typeOf[Animal] => "Dog List"
          case t if t =:= typeOf[Int] => "Int List"
        }
    
        println(test2(List("string")))
        println(test2(List(new Dog)))
        println(test2(List(1, 2)))
      }
    }

    13.8 多重界定

    不能同时有多个上界或下界,变通的方式是使用复合类型 
    T <: A with B 
    T >: A with B 
    可以同时有上界和下界,如 
    T >: A <: B 
    这种情况下界必须写在前边,上界写在后边,位置不能反。同时A要符合B的子类型,A与B不能是两个无关的类型。 
    可以同时有多个view bounds 
    T <% A <% B 
    这种情况要求必须同时存在 T=>A的隐式转换,和T=>B的隐式转换。

    class A{}
    class B{}
    implicit def string2A(s:String) = new A
    implicit def string2B(s:String) = new B
    def foo2[ T <% A <% B](x:T)  = println("foo2 OK")
    foo2("test")

    可以同时有多个上下文界定 
    T : A : B 
    这种情况要求必须同时存在C[T]类型的隐式值,和D[T]类型的隐式值。

    class C[T];
    class D[T];
    implicit val c = new C[Int]
    implicit val d = new D[Int]
    def foo3[ T : C : D ](i:T) = println("foo3 OK")
    foo3(2)

    13.9 类型约束

    类型约束,提供了限定类型的另一种方式,一共有3中关系声明: 
    T =:= U意思为:T类型是否等于U类型 
    T <:< U意思为:T类型是否为U或U的子类型 
    T <%

    class Pair5[T] (val first: T, val second: T)(implicit ev: T <:< Comparable[T]){}

    使用举例:

    import java.io.File
    
    class Pair6[T](val first: T, val second: T) {
      def smaller(implicit ev: T <:< Ordered[T]) = {
        if(first < second) first else second
      }
    }
    
    object Main6 extends App{
      override def main(args: Array[String]): Unit = {
        //构造Pair6[File]时,注意此时是不会报错的
        val p6 = new Pair6[File](new File(""), new File(""))
        //这就报错了
        p6.smaller
      }
    }

    13.10 型变

    术语:

    英文 中文 示例
    Variance 型变 Function[-T, +R]
    Nonvariant 不变 Array[A]
    Covariant 协变 Supplier[+A]
    Contravariant 逆变 Consumer[-A]
    Immutable 不可变 String
    Mutable 可变 StringBuilder

    其中,Mutable常常意味着Nonvariant,但是Noncovariant与Mutable分别表示两个不同的范畴。 
    即:可变的,一般意味着“不可型变”,但是“不可协变”和可变的,分别表示两个不同范畴。 
    型变(Variance)拥有三种基本形态:协变(Covariant), 逆变(Contravariant), 不变(Nonconviant),可以形式化地描述为: 
    一般地,假设类型C[T]持有类型参数T;给定两个类型A和B,如果满足A <: B,则C[A]与 C[B]之间存在三种关系:

    如果C[A] <: C[B],那么C是协变的(Covariant);
    如果C[A] :> C[B],那么C是逆变的(Contravariant);
    否则,C是不变的(Nonvariant)。

    Scala的类型参数使用+标识“协变”,-标识“逆变”,而不带任何标识的表示“不变”(Nonvariable):

    如何判断一个类型是否有型变能力:

    一般地,“不可变的”(Immutable)类型意味着“型变”(Variant),而“可变的”(Mutable)意味着“不变”(Nonvariant)。
    其中,对于不可变的(Immutable)类型C[T]
    如果它是一个生产者,其类型参数应该是协变的,即C[+T];
    如果它是一个消费者,其类型参数应该是逆变的,即C[-T]。

    十四、 隐式转换和隐式参数

    14.1 隐式转换

    隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型。

    implicit def a(d: Double) = d.toInt
    //不加上边这句你试试
    val i1: Int = 3.5
    println(i1)

    14.2 利用隐式转换丰富类库功能

    如果需要为一个类增加一个方法,可以通过隐式转换来实现。比如想为File增加一个read方法,可以如下定义:

    class RichFile(val from: File) {
      def read = Source.fromFile(from.getPath).mkString
    }
    
    implicit def file2RichFile(from: File) = new RichFile(from)
    
    val contents = new File("C:\\Users\\61661\\Desktop\\scala笔记.txt").read
    println(contents)

    有什么好处呢?好处就是你可以不修改原版本的代码而为原本的代码增加新功能。

    14.3 隐式值

    将name变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺少参数。

    implicit val name = "Nick"
    def person(implicit name: String) = name
    println(person)

    但是如果此时你又相同作用域中定义一个隐式变量,再次调用方法时就会报错:

    implicit val name = "Nick"
    implicit val name2 = "Nick"
    def person(implicit name: String) = name
    println(person)

    14.4 隐式视图

    1) 隐式转换为目标类型:把一种类型自动转换到另一种类型

    def foo(msg : String) = println(msg)
    implicit def intToString(x : Int) = x.toString
    foo(10)

    2) 隐式转换调用类中本不存在的方法

    class Dog {
      val name = "金毛"
    }
    
    class Skill{
      def fly(animal: Dog, skill: String) = println(animal.name + "已领悟" + skill)
    }
    
    object Learn{
      implicit def learningType(s : Dog) = new Skill
    }
    
    object Main2 extends App{
      override def main(args: Array[String]): Unit = {
        import unit15.Learn._
        val dog = new Dog
        dog.fly(dog, "飞行技能")
      }
    }

    当然了,以上操作也可以定义在包对象中,即,在object Learn的外面再套一层,package,没问题的!

    14.5 隐式类

    在scala2.10后提供了隐式类,可以使用implicit声明类,但是需要注意以下几点: 
    —– 其所带的构造参数有且只能有一个 
    —– 隐式类必须被定义在“类”或“伴生对象”或“包对象”里 
    —– 隐式类不能是case class(case class在定义会自动生成伴生对象与2矛盾) 
    —– 作用域内不能有与之相同名称的标示符

    object StringUtils {
      implicit class StringImprovement(val s : String){ //隐式类
        def increment = s.map(x => (x +1).toChar)
      }
    }
    object Main3 extends  App{
      import unit15.StringUtils._
      println("mobin".increment)
    }

    14.6 隐式的转换时机

    1) 当方法中的参数的类型与目标类型不一致时 
    2) 当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换

    14.7 隐式解析机制

    即编译器是如何查找到缺失信息的,解析具有以下两种规则: 
    1) 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。 
    2) 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下: 
    a) 如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索。 
    b) 如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象。 
    c) 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索。 
    d) 如果T是个类型注入S#T,那么S和T都会被搜索。

    14.8 隐式转换的前提

    1) 不能存在二义性 
    2) 隐式操作不能嵌套

    十五、 文件和正则表达式

    15.1 读取行

    import scala.io.Source
    
    object FileSyllabus {
      def main(args: Array[String]): Unit = {
        //文件读取
        val file1 = Source.fromFile("C:\\Users\\61661\\Desktop\\scala笔记.txt")
        val lines = file1.getLines
        for (line <- lines) {
          println(line)
        }
        file1.close
      }
    }

    尖叫提示:记得close 
    1) 文件内容转数组:

    val array= file1.getLines.toArray

    2) 文件内容转字符串:

    val iterator = file1.mkString

    15.2 读取字符

    由于Source.fromFile直接返回的就是Iterator[Char],所以可以直接对其进行迭代,按照字符访问里边每一个元素。

    Source.fromFile("C:\\Users\\61661\\Desktop\\scala笔记.txt", "UTF-8") 
    for(ch <- file2){
      println(ch)
    }
    file2.close

    15.3 读取词法单元和数字

    如果想将以某个字符或某个正则表达式分开的字符成组读取,可以这么做:

    val file3 = Source.fromFile("D:\\BigData课堂笔记\\尚硅谷BigData笔记\\尚硅谷大数据技术之Scala\\2.资料\\info.csv")
    val tokens = file3.mkString.split(",")
    println(tokens.mkString(" "))
    file3.close

    15.4 读取网络资源、文件写入、控制台操作

    1) 读取网络资源

    val webFile = Source.fromURL("http://www.baidu.com")
    webFile.foreach(print)
    webFile.close()

    2) 写入数据到文件

    import java.io.{File, PrintWriter}
    val writer = new PrintWriter(new File("嘿嘿嘿.txt"))
    for (i <- 1 to 100)
      writer.println(i)
    writer.close()

    3) 控制台操作

    //控制台交互--老API
    print("请输入内容:")
    val consoleLine1 = Console.readLine()
    println("刚才输入的内容是:" + consoleLine1)
    
    //控制台交互--新API
    print("请输入内容(新API):")
    val consoleLine2 = StdIn.readLine()
    println("刚才输入的内容是:" + consoleLine2)

    15.5 序列化

    @SerialVersionUID(1L) class Person extends Serializable{
      override def toString = name + "," + age
    
      val name = "Nick"
      val age = 20
    
    }
    
    object PersonMain extends App{
      override def main(args: Array[String]): Unit = {
    
        import java.io.{FileOutputStream, FileInputStream, ObjectOutputStream, ObjectInputStream}
        val nick = new Person
        val out = new ObjectOutputStream(new FileOutputStream("Nick.obj"))
        out.writeObject(nick)
        out.close()
    
        val in = new ObjectInputStream(new FileInputStream("Nick.obj"))
        val saveNick = in.readObject()
        in.close()
        println(saveNick)
      }
    }

    15.6 进程控制

    我们可以使用scala来操作shell,scala提供了scala.sys.process包提供了用于shell程序交互的工具。 
    1) 执行shell

    import sys.process._
    "ls -al /"!
    "ls -al /"!!

    尖叫提示:!和!!的区别在于:process包中有一个将字符串隐式转换成ProcessBuild对象的功能,感叹号就是执行这个对象,单感叹号的意思就是程序执行成功返回0,执行失败返回非0,如果双感叹号,则结果以字符串的形式返回。 
    2) 管道符

    import sys.process._
    "ls -al /" #| "grep etc" !

    3) 将shell的执行结果重定向到文件

    import sys.process._
    "ls -al /" #| "grep etc" !;
    "ls -al /" #>> new File("output.txt") !;

    尖叫提示:注意,每一个感叹号后边,有分号结束 
    scala进程还可以提供: 
    p #&& q操作,即p任务执行成功后,则执行q任务。 
    p #|| q操作,即p任务执行不成功,则执行q任务。 
    既然这么强大,那么crontab + scala + shell,就完全不需要使用oozie了。

    15.7 正则表达式

    我们可以通过正则表达式匹配一个句子中所有符合匹配的内容,并输出:

    import scala.util.matching.Regex
    val pattern1 = new Regex("(S|s)cala")
    val pattern2 = "(S|s)cala".r
    val str = "Scala is scalable and cool"
    println((pattern2 findAllIn str).mkString(","))

    十六、 高级类型

    16.1 类型与类的区别

    在Java里,一直到jdk1.5之前,我们说一个对象的类型(type),都与它的class是一一映射的,通过获取它们的class对象,比如 String.class, int.class, obj.getClass() 等,就可以判断它们的类型(type)是不是一致的。 
    而到了jdk1.5之后,因为引入了泛型的概念,类型系统变得复杂了,并且因为jvm选择了在运行时采用类型擦拭的做法(兼容性考虑),类型已经不能单纯的用class来区分了,比如 List 和 List 的class 都是 Class,然而两者类型(type)却是不同的。泛型类型的信息要通过反射的技巧来获取,同时java里增加了Type接口来表达更泛的类型,这样对于 List这样由类型构造器和类型参数组成的类型,可以通过 Type 来描述;它和 List 类型的对应的Type对象是完全不同的。

    在Scala里,类型系统又比java复杂很多,泛型从一开始就存在,还支持高阶的概念(后续会讲述)。所以它没有直接用Java里的Type接口,而是自己提供了一个scala.reflect.runtime.universe.Type(2.10后) 
    在scala里获取类型信息是比较便捷的:

    class A{}
    object TypeSyllabus {
      def main(args: Array[String]): Unit = {
        import scala.reflect.runtime.universe._
        println(typeOf[A])
      }
    }

    同样scala里获取类(Class)信息也很便捷,类似:

    class A{}
    object TypeSyllabus {
      def main(args: Array[String]): Unit = {
        import scala.reflect.runtime.universe._
        println(typeOf[A])
        println(classOf[A])
      }
    }

    尖叫提示:注意,typeOf 和 classOf 方法接收的都是类型符号(symbol),并不是对象实例。

    16.2 classOf与getClass的区别

    获取Class时的两个方法:classOf 和 getClass

    scala> class  A
    scala> val a = new A
    scala> a.getClass
    res2: Class[_ <: A] = class A
    scala> classOf[A]
    res3: Class[A] = class A

    上面显示了两者的不同,getClass方法得到的是 Class[A]的某个子类,而 classOf[A] 得到是正确的 Class[A],但是去比较的话,这两个类型是equals为true的。 
    这种细微的差别,体现在类型赋值时,因为java里的 Class[T]是不支持协变的,所以无法把一个 Class[_ < : A] 赋值给一个 Class[A]。

    16.3 单例类型

    16.4 类型投影

    在scala里,内部类型(排除定义在object内部的),想要表达所有的外部类A实例路径下的B类型,即对 a1.B 和 a2.B及所有的 an.B类型找一个共同的父类型,这就是类型投影,用 A#B的形式表示。 
    A#B 
    / \ 
    / \ 
    a1.B a2.B 
    我们回头来对比一下scala里的类型投影与java里的内部类型的概念,java里的内部类型在写法上是 Outter.Inner 它其实等同于scala里的投影类型 Outter#Inner,java里没有路径依赖类型的概念,比较简化。

    16.5 类型别名

    可以通过type关键字来创建一个简单的别名,类型别名必须被嵌套在类或者对象中,不能出现在scala文件的顶层:

    class Document {
      import scala.collection.mutable._
      type Index = HashMap[String, (Int, Int)]
    }
    def play(x: Index): Unit ={}

    16.6 结构类型

    结构类型是指一组关于抽象方法、字段和类型的规格说明,你可以对任何具备append方法的类的实例调用appendLines方法,这种方式比定义特质更加灵活,是通过反射进行调用的:

    class Structure {
      def play() = println("play方法调用了")
    }
    
    object HelloStructure {
      def main(args: Array[String]): Unit = {
        type X = {def play(): Unit} //type关键字是把 = 后面的内容命名为别名。
    
        def init(res: X) = res.play //本地方法
    
        init(new {
          def play() = println("Played")
        })
    
        init(new {
          def play() = println("Play再一次")
        })
    
        object A {
          def play() {
            println("A object play")
          }
        }
    
        init(A)
        val structure = new Structure
        init(structure)
      }
    }

    总结: 
    结构类型,简单来说,就是只要是传入的类型,符合之前定义的结构的,都可以调用。

    16.7 复合类型

    class A extends B with C with D with E 
    应做类似如下形式解读: 
    class A extends (B with C with D with E) 
    T1 with T2 with T3 … 
    这种形式的类型称为复合类型(compound type)或者也叫交集类型(intersection type)。 
    也可以通过type的方式声明符合类型:

    type X = X1 with X2

    16.8 中置类型

    中置类型是一个带有两个类型参数的类型,以中置语法表示,比如可以将Map[String, Int]表示为:

    val scores: String Map Int = Map("Fred" -> 42)

    16.9 自身类型

    self => 这句相当于给this起了一个别名为self:

    class A {
      self =>  //this别名
      val x=2
      def foo = self.x + this.x
    }

    self不是关键字,可以用除了this外的任何名字命名(除关键字)。就上面的代码,在A内部,可以用this指代当前对象,也可以用self指代,两者是等价的。 
    它的一个场景是用在有内部类的情况下:

    class Outer { outer =>
      val v1 = "here"
      class Inner {
        println(outer.v1) // 用outer表示外部类,相当于Outer.this
      }
    }

    对于this别名 self =>这种写法形式,是自身类型(self type)的一种特殊方式。self在不声明类型的情况下,只是this的别名,所以不允许用this做this的别名。

    16.10 运行时反射

    scala编译器会将scala代码编译成JVM字节码,编译过程中会擦除scala特有的一些类型信息,在scala-2.10以前,只能在scala中利用java的反射机制,但是通过java反射机制得到的是只是擦除后的类型信息,并不包括scala的一些特定类型信息。从scala-2.10起,scala实现了自己的反射机制,我们可以通过scala的反射机制得到scala的类型信息。 
    给定类型或者对象实例,通过scala运行时反射,可以做到:1)获取运行时类型信息;2)通过类型信息实例化新对象;3)访问或调用对象的方法和属性等。

    16.10.1 获取运行时类型信息

    scala运行时类型信息是保存在TypeTag对象中,编译器在编译过程中将类型信息保存到TypeTag中,并将其携带到运行期。我们可以通过typeTag方法获取TypeTag类型信息。

    import scala.reflect.runtime.universe._
    val typeTagList = typeTag[List[Int]]//得到了包装Type对象的TypeTag对象
    println(typeTagList)
    或者使用:
    typeOf[List[Int]]//直接得到了Type对象

    尖叫提示:Type对象是没有被类型擦除的 
    我们可以通过typeTag得到里面的type,再通过type得到里面封装的各种内容:

    import scala.reflect.runtime.universe._
    val typeTagList = typeTag[List[Int]]
    println(typeTagList)
    println(typeTagList.tpe)
    println(typeTagList.tpe.decls.take(10))

    16.10.2 运行时类型实例化

    我们已经知道通过Type对象可以获取未擦除的详尽的类型信息,下面我们通过Type对象中的信息找到构造方法并实例化类型的一个对象:

    class Person(name:String, age: Int) {
      def myPrint() = {
        println(name + "," + age)
      }
    }
    
    object PersonMain extends App{
      override def main(args: Array[String]): Unit = {
        //得到JavaUniverse用于反射
        val ru = scala.reflect.runtime.universe
        //得到一个JavaMirror,一会用于反射Person.class
        val mirror = ru.runtimeMirror(getClass.getClassLoader)
        //得到Person类的Type对象后,得到type的特征值并转为ClassSymbol对象
        val classPerson = ru.typeOf[Person].typeSymbol.asClass
        //得到classMirror对象
        val classMirror = mirror.reflectClass(classPerson)
        //得到构造器Method
        val constructor = ru.typeOf[Person].decl(ru.termNames.CONSTRUCTOR).asMethod
        //得到MethodMirror
        val methodMirror = classMirror.reflectConstructor(constructor)
        //实例化该对象
        val p = methodMirror("Mike", 1)
        println(p)
      }
    }

    16.10.3 运行时类成员的访问

    class Person(name:String, age: Int) {
      def myPrint() = {
        println(name + "," + age)
      }
    }
    
    object PersonMain extends App{
      override def main(args: Array[String]): Unit = {
        //获取Environment和universe
        val ru = scala.reflect.runtime.universe
        //获取对应的Mirrors,这里是运行时的
        val mirror = ru.runtimeMirror(getClass.getClassLoader)
        //得到Person类的Type对象后,得到type的特征值并转为ClassSymbol对象
        val classPerson = ru.typeOf[Person].typeSymbol.asClass
        //用Mirrors去reflect对应的类,返回一个Mirrors的实例,而该Mirrors装载着对应类的信息
        val classMirror = mirror.reflectClass(classPerson)
        //得到构造器Method
        val constructor = ru.typeOf[Person].decl(ru.termNames.CONSTRUCTOR).asMethod
        //得到MethodMirror
        val methodMirror = classMirror.reflectConstructor(constructor)
        //实例化该对象
        val p = methodMirror("Mike", 1)
        println(p)
    
    
        //反射方法并调用
        val instanceMirror = mirror.reflect(p)
        //得到Method的Mirror
        val myPrintMethod = ru.typeOf[Person].decl(ru.TermName("myPrint")).asMethod
        //通过Method的Mirror索取方法
        val myPrint = instanceMirror.reflectMethod(myPrintMethod)
        //运行myPrint方法
        myPrint()
    
        //得到属性Field的Mirror
        val nameField = ru.typeOf[Person].decl(ru.TermName("name")).asTerm
        val name = instanceMirror.reflectField(nameField)
        println(name.get)
    
      }
    }
    展开全文
  • Scala中文教程 + 中文参考手册

    千次下载 热门讨论 2020-07-30 23:32:46
    本书是一份Scala编程语言的教程。写给那些直接参与Scala开发的人群。我们的目标是通过阅读此书,你能够学会一切所需,成为多产的Scala程序员。本书中所有的例子都能在Scala版本2.7.2下面编译通过。 谁应该阅读此书 ...
  • Scala编程语言学习总结

    万次阅读 多人点赞 2020-08-20 16:02:12
    &amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;&amp;amp;nbsp;...&

                               Scala学习总结

    一、Scala的简介

          Scala是一种基于JVM的编程语言,学习目的主要是为了Spark的学习与相关代码的编写。
        Scala的六大特征:

            1).Java和scala可以无缝混编,都是运行在JVM上的
    	2).类型推测(自动推测类型),不用指定类型  
    	3).并发和分布式(Actor,类似Java多线程Thread) 
    	4).特质trait,特征(类似java中interfaces 和 abstract结合)
    	5).模式匹配,match case(类似java switch case)
    	6).高阶函数(函数的参数是函数,函数的返回是函数),可进行函数式编程
    

    二、Scala安装使用

    1. windows安装,配置环境变量
    • 官网下载scala2.10:https://www.scala-lang.org/download/2.10.4.html

    • 下载好后安装。双击msi包安装,记住安装的路径。

    • 配置环境变量(和配置jdk一样)
               ①新建SCALA_HOME,配置好Scala安装的目录位置信息。
               ②编辑Path变量,在后面追加如下:
                 ;%SCALA_HOME%\bin;%SCALA_HOME%\jre\bin

    • 打开cmd,输入:scala - version 看是否显示版本号,确定是否安装成功
      在这里插入图片描述

    1. Scala程序编译工具
          ①Scala ide
           下载网址:http://scala-ide.org/download/sdk.html
          ②推荐使用IDEA中配置scala插件
           1》 打开idea项目后,点击Configure->Plugins
      在这里插入图片描述     2》搜索scala,点击Install安装
      在这里插入图片描述     3》设置jdk,打开Project Structure,点击new 选择安装好的jdk路径
      在这里插入图片描述在这里插入图片描述     4》创建scala项目,配置scala sdk(Software Development Kit)
      在这里插入图片描述

    三、Scala基础

    1.数据类型
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
    2.变量和常量的声明
        Scala中通常用var声明变量,可修改;用val声明常量,不可修改即不可对常量进行再赋值;不用指定数据类型,由Scala自己进行类型推测。
    注意点: 在scala中每行后面不需要加分号,直接通过换行如来区分;但是一行中写了多个语句,语句与语句之间必须用分号来分割
    例如:

    var age = 18   ; var name = "angelababy"
        
        var heighat,score = 10
        val sex = "male"
    

    3.类和对象

    class :修饰的称为伴生类;定义在class中的属性都是动态的,用于实例化 的;scala中的class类默认可以传参数,默认的传参数就是默认的构造函数。class 类属性自带getter ,setter方法。使用class时要new (必须new,除非在对象伴生用apply方法【在加载类的时候默认自动调用】已实经例化好),并且new的时候,class中除了方法不执行,其他都执行。

    object: 修饰的称为伴生对象;定义在object中的属性(字段、方法)都是静 态的,main函数写在里面;scala 中的object是单例对象,相当于java中的工具类,可以看成是定义静态的方法的类.object不可以传参数。使用object时,不用new.

    创建类

    class Person{
      val name = "zhangsan"
      val age = 18
      def sayName() = {
        "my name is "+ name
      }
    }
    

        在创建类时,可以在类名后面加入一个括号,括号中写定义的参数类型,括号中的参数就表示类的构造函数由括号内的参数组成。补充: ①当参数用var修饰那么可以通过对象修改其值;当参数用val修饰那么无法通过对象来修改值;当参数没有修饰符,那么在外部无法通过对象来调用。
          ②若想增加一个类的传入参数,则需要在声明的类中重写构造函数,这样就可以在mian函数中声明有增加的属性的对象,当然原来的对象也可以声明。
                   重写this函数

      /*
       *  重写的构造函数,参数不能有修饰符
       */
      def this (id:Int,name:String,facePower:Double ){
        //首先要调用父构造函数
        this(id,name)
        fcp = facePower
        
      }
    

    创建对象

    object Lesson_Class {
       def main(args: Array[String]): Unit = {
        val person = new Person()
        println(person.age);
        println(person.sayName())
      }
    }
    

    Apply方法
           使用此方法时,可以在main函数中不通过new来创建一个对象,即可以不用专门的一次一次地进行实例化,加载创建对象的这个类的时候,会自动调用apply这个方法,类似Java中的static静态块。
           Apply方法的使用用例:

    object ScalaDemo01 {
      def main(args: Array[String]): Unit = {
        val p = new Person("zs",19)
         val person = Person("wagnwu",10)   //不用使用new来创建一个实例
      }
    }
    
    class Person(xname :String , xage :Int){
      val name = "zs"
      val age = xage
      var gender = "m"
      def this(name:String,age:Int,g:String){
        this(name,age)
        gender = g
      }
    }
    
    object Person{
      def apply(name:String,age:Int)={
        new Person(name,age)  
      }
    }
    

    4.if{…}else{…}语句

        /**
         * if else 
         */
        val age =18 
        if (age < 18 ){
        	println("no allow")
        }else if (18<=age&&age<=20){
        	println("allow with other")
        }else{
        	println("allow self")
        }
    

    5.for ,while,do…while
        ①to和until 的用法(不带步长,带步长区别)

     /**
         * to和until
         * 例:
         * 1 to 10 返回1到10的Range数组,包含10
         * 1 until 10 返回1到10 Range数组 ,不包含10
         */
        
        println(1 to 10 )//打印 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
        
        println(1 to (10 ,2))//步长为2,从1开始打印 ,1,3,5,7,9
        
        println(1 until 10 ) //不包含最后一个数,打印 1,2,3,4,5,6,7,8,9
        
        println(1 until (10 ,3 ))//步长为2,从1开始打印,打印1,4,7
    

        ②for循环(可加判断条件)

        //可以分号隔开,写入多个list赋值的变量,构成多层for循环
        //scala中 不能写count++ count-- 只能写count+
        //可以在for循环中加入条件判断
        var count = 0;
        for(i <- 1 to 10; j <- 1 until 10;if (i%2) == 0){
          println("i="+ i +",j="+j)
          count += 1
        }
        println(count);
        
        //例子: 打印小九九
        for(i <- 1 until 10 ;j <- 1 until 10){
          if(i>=j){
        	  print(i +" * " + j + " = "+ i*j+"	")  
          }
          if(i==j ){
            println()
          }
        }
    

    补充: for循环用yield 关键字返回一个集合

    /*
     *  yield 关键字
     *  1-100集合中的偶数存储到另一个集合
     *  yield关键字能够将符合要求的元素自动封装到一个集合中
     */
    val rest = for(i <- 1 to 100; if i%2 == 0) yield i     //此时rest就为选择出的i构成的一个集合
    for(elem <- rest) println(elem)
    

        ③while循环,while(){},do {}while()

       /**
         * while 循环
         */
        var index = 0 
        while(index < 100 ){
        	println("第"+index+"次while 循环")
          index += 1 
        }
        index = 0 
        do{
        	index +=1 
        	println("第"+index+"次do while 循环")
    }while(index <100 )
    

    6.函数
        ①普通函数

    def fun (a: Int , b: Int ) : Unit = {
       println(a+b)
     }
    fun(1,1)
        
    def fun1 (a : Int , b : Int)= a+b
        println(fun1(1,2))  
    

        ②递归函数

     /**
         * 递归函数 
         * 5的阶乘
         */
        def fun2(num :Int) :Int= {  //必须写返回值类型
          if(num ==1)
            num
          else 
            num * fun2(num-1)
        }
        print(fun2(5))
    

        ③包含参数默认值的函数

     /**
       * 包含参数默认值的函数
       * 	1.	函数的参数有默认值,在调函数的时候可以传参,也可以不传参,
       * 	2.	若不传参使用的默认值,
       * 	3.	如果传参,默认值会被覆盖
       */
      def fun2(num1:Int = 10,num2:Int = 20) = {
        num1 + num2
      }
      
      def fun3(num1:Int,num2:Int = 20) = {
        num1 + num2
      }
      
      def fun4(num1:Int=10,num2:Int) = {
        num1 + num2
      }
      调用:
       println(fun2())
       println(fun3(100))
       println(fun4(num2=1000))
    

        ④可变参数个数的函数:函数参数可以是一个也可以是多个,随机灵活的

     def fun5(args:Double*) = {
        /**
         * 在scala中
         * 		+=前后的数值类型必须一致
         * 		+前后的数值类型可以不一致
         */
        var sum = 0.0
        for(arg <- args) sum += arg
        sum
      }
    

        ⑤匿名函数:没有函数名的函数

    /**
     * 匿名函数
     * 1.有参数匿名函数
     * 2.无参数匿名函数
     * 3.有返回值的匿名函数
     * 注意:
     * 可以将匿名函数返回给定义的一个变量
     * 匿名函数不能显式声明函数的返回类型
    
     */
    //有参数匿名函数
    val value1 = (a : Int) => {
      println(a)
    }
    value1(1)
    //无参数匿名函数
    val value2 = ()=>{
      println("我爱Angelababy")
    }
    value2()
    //有返回值的匿名函数
    val value3 = (a:Int,b:Int) =>{
      a+b
    }
    println(value3(4,4)) 
    

        ⑥嵌套函数

        /**
         * 嵌套函数:
         * 		在函数体内有定义了一个函数
         */
        def fun7(num:Int)={
          def fun8(a:Int,b:Int):Int={
            if(a == 1){
              b
            }else{
              fun8(a-1,a*b)
            }
          }
          fun8(num,1)
        }
    

        ⑦偏应用函数:偏应用函数是一种表达式,不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。在多个函数调用时,有共同的参数被使用,则可提取出来默认写死,只需要为函数提供部分的参数。

    /**
     * 偏应用函数
     */
    def log(date :Date, s :String)= {
      println("date is "+ date +",log is "+ s)
    }
    
    val date = new Date()
    log(date ,"log1")
    log(date ,"log2")
    log(date ,"log3")
    
    //想要调用log,以上变化的是第二个参数,可以用偏应用函数处理
    val logWithDate = log(date,_:String)    //下划线相当于占位符的作用,手动传入即可
    logWithDate("log11")
    logWithDate("log22")
    logWithDate("log33")
    

        ⑧ 高阶函数:函数的参数是函数,或者函数的返回类型是函数,或者函数的参数和函数的返回类型是函数的函数。

     //函数的参数是函数
        def hightFun(f : (Int,Int) =>Int, a:Int ) : Int = {
          f(a,100)
        }
        def f(v1 :Int,v2: Int):Int  = {
          v1+v2
        }
        
        println(hightFun(f, 1))
        
        //函数的返回是函数
        //1,2,3,4相加
        def hightFun2(a : Int,b:Int) : (Int,Int)=>Int = {
          def f2 (v1: Int,v2:Int) :Int = {
            v1+v2+a+b
          }
          f2
        }
        println(hightFun2(1,2)(3,4))
        
        //函数的参数是函数,函数的返回是函数
        def hightFun3(f : (Int ,Int) => Int) : (Int,Int) => Int = {
          f
        } 
        println(hightFun3(f)(100,200))
        println(hightFun3((a,b) =>{a+b})(200,200))
        //以上这句话还可以写成这样
        //如果函数的参数在方法体中只使用了一次 那么可以写成_表示
        println(hightFun3(_+_)(200,200))
    

        ⑨柯里化函数

        /**
           * 柯里化函数
           * 		可以理解为高阶函数的简化
           * 		klhFun函数就是对嵌套函数这种情况的高阶函数的简化版
           */
    
          def klhFun(a:Int)(b:Int) = a*b
          具体:
          def klhAllFun(a:Int):(Int)=>Int = {
            val fun = (b:Int)=>a*b
            fun
          }
    

    7.Scala字符串

    val str1 = "hello bi"
        val str2 = "hello sh"
        val flag = str1.equalsIgnoreCase(str2)    
        println(flag)
        
        str1.split(" ")
    
        val strBuilder = new StringBuilder
        strBuilder.append("hello\t")
        strBuilder.append("bj")
        println(strBuilder)
        println("hello Angelababy".hashCode())
    

    string操作方法举例
    1.比较:equals
    2.比较忽略大小写:equalsIgnoreCase
    3.indexOf:如果字符串中有传入的assci码对应的值,返回下标

      /**
         * String && StringBuilder
         */
        val str = "abcd"
        val str1 = "ABCD"
        
        println(str.indexOf(97))
        println(str.indexOf("b"))
    
        println(str==str1)
        /**
         * compareToIgnoreCase
         * 
         * 如果参数字符串等于此字符串,则返回值 0;
         * 如果此字符串小于字符串参数,则返回一个小于 0 的值;
         * 如果此字符串大于字符串参数,则返回一个大于 0 的值。
         * 
         */
        println(str.compareToIgnoreCase(str1))
        
        val strBuilder = new StringBuilder
        strBuilder.append("abc")
    //    strBuilder.+('d')
        strBuilder+ 'd'
    //    strBuilder.++=("efg")
        strBuilder++= "efg" 
    //    strBuilder.+=('h')
        strBuilder+= 'h' 
        strBuilder.append(1.0)
        strBuilder.append(18f)
        println(strBuilder)
    

    8.数组

     //数组  new String[10]
        /**
         * Array关键词 创建一个长度为10 的Int类型的数组
         * 如果创建了一个Int类型的数组,那么初始值都是0
         * String类型的数组,初始是null
         * Boolean类型的数组,初始是false
         */
        val nums = new Array[Int](10)
        for(index <- 0 until nums.length){
          nums(index) = index * index
        }
        nums.foreach {println }
        
        //二维数组
        val secArray = new Array[Array[String]](10)
        for(index <- 0 until secArray.length){
          secArray(index) = new Array[String](10)
        }
        
        //往二维数组中填充数据    
       /* for(i <- 0 until secArray.length){
          for(j <- 0 until secArray(i).length){
            secArray(i)(j) = i*j + ""
          }
        }*/
        for(i <- 0 until secArray.length;j <- 0 until secArray(i).length){
          secArray(i)(j) = i*j + ""
        }
    

    9.List集合

       /**
        * list集合
        * 
        * list集合是一个不可变的集合
        * 可变的list集合
        */
        println("=========List==========")
        val list = List(1,2,3,4,5)
        list.contains(6)
        val dropList = list.drop(2)
        dropList.foreach { println }
        list.reverse
        list.take(3).foreach(println)
        val mapList1 = list.map { x=> x +"" }
        mapList1.foreach { x=>println(x) }
        println(list.exists { x => x > 300 })
        println(list.mkString("\t"))
        
        val logList = List("hello bj","hello sh")
        val mapList = logList.map { _.split(" ") }
    //    mapList.foreach { x => x.foreach { println } }
        //flatMap方法   flat扁平 
        val flatMapList = logList.flatMap{_.split(" ")}
        flatMapList.foreach { println }
    
    /**
         * 不可变的list集合
         * 创建list集合的方式:
         * 		1、List(1,2,3)
         * 		2、Nil关键创建  Nil:空List
         * 				::往集合中添加元素
         */
        val list = 2::1::Nil
        
        /**
         * 可变的list集合
         */
        val listBuffer = new ListBuffer[String]
        listBuffer.+=("hello")
        listBuffer.+=("bj")
        listBuffer.foreach { println }
        
        listBuffer.-=("hello")
    

    10.set集合

       //创建 
        val set1 = Set(1,2,3,4,4)
        val set2 = Set(1,2,5)
        //遍历
        //注意:set会自动去重
        set1.foreach { println}
       for(s <- set1){
          println(s)
        }
        println("*******")
       /**
        * 方法举例
        */
        
       //交集
       val set3 = set1.intersect(set2)
       set3.foreach{println}
       val set4 = set1.&(set2)
       set4.foreach{println}
       println("*******")
       //差集
       set1.diff(set2).foreach { println }
       set1.&~(set2).foreach { println }
       //子集
       set1.subsetOf(set2)
       
       //最大值
       println(set1.max)
       //最小值
       println(set1.min)
       println("****")
       
       //转成数组,list
       set1.toArray.foreach{println}
       println("****")
       set1.toList.foreach{println}
       
       //mkString
       println(set1.mkString)
       println(set1.mkString("\t"))
    

    11.Map集合

     val map = Map(
                      "1" -> "bj",
                      2 -> "sh",
                      3 -> "gz"
                    )
        val keys = map.keys
        val keyIterator = keys.iterator
        while(keyIterator.hasNext){
          val key = keyIterator.next()
          /**
           * map集合的get方法返回值是一个Option类型的对象
           * Option类型有两个子类型  分别为some None
           */
           Map中的方法 :
           1.	filter:过滤,留下符合条件的记录
           2.	count:统计符合条件的记录数
           3.	contains:map中是否包含某个key
           4.	exist:符合条件的记录存在不存在
    
          println(key + "\t" + map.get(key).get)
        }
         
         /**
          * getOrElse 原理:
          * 		去集合中去取数据,若不存在返回默认值
          */
         println(map.get(4).getOrElse("default"))
        
         for(k <- map)
           println(k._1 + "\t" + k._2)     //分别取第一个位置的数据和第二个位置的数据,即Key-Value
           
         map.foreach(x=>println(x._1 + "\t" + x._2))    
         
         
         map.filter(x=>{
           Integer.parseInt(x._1 + "") >= 2
         }).foreach(println)
         
         val count = map.count(x=>{
           Integer.parseInt(x._1 + "") >= 2
         })
         println(count)
         
         /**
         合并Map的操作:
         1.++  例:map1.++(map2)  --map1中加入map2
         2.++:  例:map1.++:(map2) –map2中加入map1
         注意:合并map会将map中的相同key的value替换
         **/
    

    12.元组

     /**
          * 元组 vs list
          * list创建的时候指定了泛型,那么集合中必须是这个泛型的对象
          * 元祖中可以包含任意类型的元素,这些元素使用一对小括号来封装,Tuple关键字
          * 
          * 
          */
         val t2 = Tuple2(1,"hello")
         val tt2 = (1,"hello")
         
         val t3 = Tuple3(1,true,"hello")  //可以存放Int、boolean、String多种不同的类型
         val tt3 = (1,true,"hello")
         
         val moreTuple = (1,2,3)   
         
          val tupleIterator = tt3.productIterator    //元组的遍历
          while(tupleIterator.hasNext){
          println(tupleIterator.next())
        }
         //toString方法
         println(tt3.toString())
         //swap交换    注意只有二元组对象才会有这个方法
         val swapt2 = tt2.swap
         println(swapt2._1 + "\t" + swapt2._2)
    

    13.Trait特性
           ①Trait的概念理解
    1》 Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
    2》与接口不同的是,它还可以定义属性和方法的实现。抽象类和接口的结合。
    3》一般情况下Scala的类可以继承多个Trait,从结果来看就是实现了多重继承。Trait的继承用exten关键字继承,多继承时多个Trait之间用with连接。
    4》Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait。
    5》继承的多个trait中如果有同名的方法和属性,必须要在类中使用“override”重新定义。
    6》trait中不可以传参数

    trait Read {
      val readType = "Read"
      val gender = "m"
      def read(name:String){
    	println(name+" is reading")
      }
    }
    
    trait Listen {
      val listenType = "Listen"
      val gender = "m"
      def listen(name:String){
    	println(name + " is listenning")
      }
    }
    
    class Person() extends Read with Listen{
      override val gender = "f"
    }
    
    object test {
      def main(args: Array[String]): Unit = {
        val person = new Person()
        person.read("zhangsan")
        person.listen("lisi")
        println(person.listenType)
        println(person.readType)
        println(person.gender)
        
      }
    }
    

    14.模式匹配Match…Case…
          ①概念理解
    =>Java中的模式匹配为 switch case ;
    =>Scala 提供了强大的模式匹配机制,应用也非常广泛,除了匹配值还可以匹配类型,类型的匹配必须要有变量名
    =>一个模式匹配包含了一系列备选项,每个都开始于关键字 case。
    =>每个备选项都包含了一个模式及一到多个表达式。箭头符号 => 隔开了模式和表达式。

    object Lesson_Match {
      def main(args: Array[String]): Unit = {
        val tuple = Tuple6(1,2,3f,4,"abc",55d)
        val tupleIterator = tuple.productIterator
        while(tupleIterator.hasNext){
          matchTest(tupleIterator.next())
        }
        
      }
      /**
       * 注意点:
       * 1.模式匹配不仅可以匹配值,还可以匹配类型
       * 2.模式匹配中,从上到下顺序匹配,如果匹配到对应的类型或值,就不再继续往下匹配
       * 3.模式匹配中,都匹配不上时,会匹配到 case _ ,相当于default
       * 4. 模式匹配的时候,模式范围小的在最前面
       */
      def matchTest(x:Any) ={
        x match {
          case x:Int=> println("type is Int")    //类型匹配,必须要有变量名
          case 1 => println("result is 1")
          case 2 => println("result is 2")
          case 3=> println("result is 3")
          case 4 => println("result is 4")
          case x:String => println("type is String")
    //      case x :Double => println("type is Double")
          case _ => println("no match")
        }
      }
    }
    

    15.样例类(case class)
         ①概念理解
           使用了case关键字的类定义就是样例类(case classes),样例类是种特殊的类。实现了类构造参数的getter方法(构造参数默认被声明为val),当构造参数是声明为var类型的,它将帮你实现setter和getter方法。
           样例类默认帮你实现了toString,equals,copy和hashCode等方法。
           样例类可以new, 也可以不用new

    /**
          * 样例类
          * 使用case 来修饰的类就叫样例类
          * 
          * 样例类和普通类有什么区别?
          * 样例类默认帮你实现了toString,equals,copy和hashCode等方法。
    			样例类可以new, 也可以不用new 
          */
         val s1 = Student(1,1)
         val s2 = Student(2,2)
         val s3 = Student(3,3)
         val s4 = Student(4,4)
         val s5 = Student(5,5)
         
         val sl = List(s1,s2,s3,s4,s5)
         //样例类+match case
         sl.foreach { x => x match {
           case Student(1,1) => println("1 1")    //值匹配,且使用了样例类已经写好了的toString方法。
           case s:Student => println("Student" + s)    //类型匹配
           case _ => println("_______")
         } }
      } 
    }
    //样例类
    case class Student(val id :Int ,val age :Int)
    

    16.Actor Model
         ①概念理解
             Actor Model是用来编写并行计算或分布式系统的高层次抽象(类似java中的Thread)让程序员不必为多线程模式下共享锁而烦恼,被用在Erlang 语言上, 高可用性99.9999999 % 一年只有31ms 宕机Actors将状态和行为封装在一个轻量的进程/线程中,但是不和其他Actors分享状态,每个Actors有自己的世界观,当需要和其他Actors交互时,通过发送事件和消息,发送是异步的,非堵塞的(fire-andforget),发送消息后不必等另外Actors回复,也不必暂停,每个Actors有自己的消息队列,进来的消息按先来后到排列,这就有很好的并发策略和可伸缩性,可以建立性能很好的事件驱动系统。

    Actor的特征:
    1.ActorModel是消息传递模型,基本特征就是消息传递
    2.消息发送是异步的,非阻塞的
    3.消息一旦发送成功,不能修改,类似于邮件的发送来往
    4.Actor之间传递时,自己决定决定去检查消息,而不是一直等待,是异步非阻塞的

    //Actor与Actor之间通信
    case class Message(actor:Actor,msg:Any)
    
    class Actor1 extends Actor{
      def act(){
        while(true){
          receive{
            case  msg :Message => {      //类型匹配
              println("i sava msg! = "+ msg.msg)
              
              msg.actor!"i love you too !"
              }
            case msg :String => println(msg)
            case  _ => println("default msg!")
          }
        }
      }
    }
    
    class Actor2(actor :Actor) extends Actor{
      actor ! Message(this,"i love you !")
    	def act(){
    		while(true){
    			receive{
      			case msg :String => {
      			  if(msg.equals("i love you too !")){
      			    println(msg)
      			   actor! "could we have a date !"
      			  }
      			}
      			case  _ => println("default msg!")
    			}
    		}
    	}
    }
    
    object Lesson_Actor2 {
      def main(args: Array[String]): Unit = {
        val actor1 = new Actor1()
        actor1.start()
        val actor2 = new Actor2(actor1)
        actor2.start()
      }
    }
    

    17.Scala的隐式转换系统
            隐式转换,在编写程序的时候可以尽量少的去编写代码,让编译器去尝试在编译期间自动推导出这些信息来,这种特性可以极大的减少代码量,提高代码质量.
        ①隐式值+隐式视图
    通过implicit进行隐式的声明

      object Scala09 {
      def main(args: Array[String]): Unit = {
    //隐式值:
    //  implicit var abc = "Dilireba"
        implicit var cba = "Angelababy"
        sayName   //直接调用,不用传参 
      /* 将变量声明为implicit,编译器在执行sayName方法时发现缺少一个String类型的参数,
          此时会搜索作用域内类型为String的隐式值
          并且将搜索到的隐式值作为sayName的参数值
       */
    
        
        //    1.隐式转换必须满足无歧义规则
        //    2.声明隐式参数的类型最好是自定义的数据类型,
        //    3.不要使用Int,String这些常用类型,防止碰巧冲突
        //    4.一般要将自定义的对象设置为隐式的
        
        //  隐式视图  --》隐式转换为目标类型:把一种类型自动转换到另一种类型
        implicit def string2Int(num:String) = Integer.parseInt(num) 
    	
        //addNum参数是tring,不符合要求,那么他在编译的时候,发现作用域内
        //    有一个隐式方法,正好这个方法的参数是String 返回值是Int  此时
        //调用隐式方法,返回一个Int值,在将Int传给addNum
    	
        println(addNum("1000"))
     
      }
      
      
      def addNum(num:Int) = {
          num + 1000
        }
      
      def sayName(implicit name:String) = {
        println("I Love " + name)
      }
      
    }
    

        ②隐式类

       注意点:
        1.其所带的构造参数有且只能有一个
        2.隐式类必须被定义在类,伴生对象和包对象里
        3.隐式类不能是case class(case class在定义会自动生成伴生对象与2矛盾)
        4.作用域内不能有与之相同名称的标示符
        
    package com.hpe.scala
    
    object Scala10 {
      def main(args: Array[String]): Unit = {
        //  隐式类   导包要从包名开始
        import com.hpe.scala.Util._     //Util下的所有类
        "abc".getLength()
        /**
         * "abc"这个字符串想要调用getLength,但是自己没有实现这个方法
         * 发现作用域内有一个隐式转换的类,类的构造函数的参数是String类型
         * 自己传给构造函数,得到这个类的对象,近而调用getLength方法
         */
      }
    }
    
    object Util{
      implicit class Int2String(s:String){
        def getLength() = s.length
      }
    }
    

    补充点:“_”在scala中的意义

    1. _表示所有 、2.占位符、3.简写模式
    展开全文
  • scala

    2019-10-06 19:00:09
    https://www.scala-lang.org/ http://spark.apache.org/docs/1.6.3/ java、scala 基于jvm concise adj. 简明的,简洁的 The Scala Programming Language Scala combines object-oriented and functiona...

     

     

    https://www.scala-lang.org/
    http://spark.apache.org/docs/1.6.3/
    java、scala 基于jvm
    concise
    adj. 简明的,简洁的
    
    The Scala Programming Language
    Scala combines object-oriented and functional programming in one concise, high-level language. Scala's static types help avoid bugs in complex applications, and its JVM and JavaScript runtimes let you build high-performance systems with easy access to huge ecosystems of libraries. 
    scala 面向对象、面向函数。  scala可以在方法中传入方法
    seamless
    adj. 无缝的;无缝合线的;无伤痕的
    scala 
     var 变量, val:常量(val便于回收)
    C:\Users\Administrator>scala
    Welcome to Scala 2.12.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_201).
    Type in expressions for evaluation. Or try :help.
    scala> var a =100
    a: Int = 100
    
    scala> print(a)
    100
    scala> val a = 100
    a: Int = 100
    
    scala> val = 1000
    <console>:1: error: illegal start of simple pattern
           val = 1000
               ^
    
    scala> var b = 1
    b: Int = 1
    
    scala> b =3
    b: Int = 3
    
    scala>
    
    traits
    英  美 [tret] 
    n. 特性,特质,性格(trait的复数)
    scala 既可以单继承,多继承,接口定义变量
    
    方法与函数 指的是方法
    
    高阶函数:方法中的参数可以使方法。
    

     

     

     

     

     

     

     

     

      

    Scala介绍
    1.Spark中使用的是Sacla2.10。
    2.Scala官网6个特征。
    	1).Java和scala可以混编
    	2).类型推测(自动推测类型)
    	3).并发和分布式(Actor)
    	4).特质,特征(类似java中interfaces 和 abstract结合)
    	5).模式匹配(类似java switch)
    	6).高阶函数
    Scala安装使用
    1.	windows安装,配置环境变量
    	官网下载scala2.10:http://www.scala-lang.org/download/2.10.4.html 
    	下载好后安装。双击msi包安装,记住安装的路径。
    	配置环境变量(和配置jdk一样)
    	新建SCALA_HOME
    

      

     

     

     

     

    window安装scala环境,并且配置环境变量。
    
    eclispe配置环境: plugins
    idea配置环境  plugin install
    
    选择相应的jdk版本。eclipse选择java path compile scala jdk  版本。
    

      

    2.	eclipse 配置scala插件
    	下载插件(一定要对应eclipse版本下载)
    http://scala-ide.org/download/prev-stable.html  
    

      

    1	打开cmd,输入:scala  - version  看是否显示版本号,确定是否安装成功
    
    2.	eclipse 配置scala插件
    	下载插件(一定要对应eclipse版本下载)
    http://scala-ide.org/download/prev-stable.html  
    
    	将features和plugins两个文件夹拷贝到eclipse安装目录中的” dropins/scala”目录下。进入dropins,新建scala文件夹,将两个文件夹拷贝到“dropins/scala”下
    3.	scala ide
    下载网址:http://scala-ide.org/download/sdk.html 
    4.	idea 中配置scala插件
    	打开idea,close项目后,点击Configure->Plugins
    

      

    选用scala ide :实质上是eclipse的scala插件版。
    

      

     

     

    package com.bjsxt.scala
    
    /**
     * 1.scala中定义在object中的变量,方法都是静态的。object 交对象,相当于java中的单例对象,object可以传参,Trait不可以传参
     * 2.scala中一行代码后可以写“;" 也可以不写,会有分号推断机制,多行代码写在一行要用分号隔开
     * 3.定义变量用var,定义常量用val; a:Int 是变量的类型,可写可不写。不写会自动推断。
     * 4.scala中变量、类、对象、方法 命名建议服务驼峰命名法。
     * 5 class 是scala中的类;类可以传参数, 必须给参数指定类型;传参就默认有了带参数的构造
     * 6 当new 一个class时,类中方法不会执行,其他都执行。同一个包下,class不能重名
     * 7.scala中如果一个class名称和object的名称一致,那么这个class叫做这个object的伴生类,这个object叫做这个class的半生对象,他们之间可以互相访问私有变量。
     */
    
    class Person(xname:String,xage:Int){ 
      private val name = xname
      var age = xage
      var gender = 'm'
      
      println("*******************")
    
    
      /**
       * 重写构造
       */
      def this(yname:String,yage:Int,ygender:Char){
        this(yname,yage)
        this.gender = ygender
      }
      
      def showHello()={
    //    println("hello world" + Lesson_ObjAndClass.score)
        println("hello world")
      }
    }
    
    //object Lesson_ObjAndClass {
    object Person {
      val score = 200
      
      println("=================")
      
      def main(args: Array[String]): Unit = {
        /**
         * 变量,常量
         */
    //    var a:Int = 100;
    //    a = 900
    //    println(a)
        
    //    val a = 100  ## 常量不可改变
    //    a=200
        
    //    var p = new Person("smith",18)
    //    p.age = 100
    //    print(p.name)    
    //    print(p.age) 
       
        
    //     var p1 = new Person("smith",18,'f')
    //     p1.age = 100
    //     println(p1.age)
    //     p1.showHello()
        
    //    var p1 = new Person("smith",18)
    //    println(p1.name)
        
    //    val a = 100
    //    if(a<50){
    //      println("a小于50")
    //    }else if(a >=50 && a <= 100){
    //      println("50--a--100")
    //    }else{
    //      println("其他")
    //    }
         
    //      println(1 to 10)   // Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    //      println(1 until 10)//Range(1, 2, 3, 4, 5, 6, 7, 8, 9)
        
          /**
           * for 
           * 1 to 10 这种表示是scala中的操作符操作
           * 
           */
    //      println(1.to(10,3))   // 步长3 //Range(1, 4, 7, 10)
    //      println(1.until(10,3))   // 步长3 //Range(1, 4, 7, 10)
        
    //      for(i <- 1 to 10){   // i 是val的
    //        println(i)
    //      }
        
    //      for(i <- 1 to 10){   // i 是val的
    //        for(j <- 1 to 10){   // i 是val的
    //          println("i=" + ";j=" + j)
    //        }
    //      }
        
    //         for(i <- 1 to 10;j <- 1 to 10){   // i 是val的
    //           println("i=" + ";j=" + j)
    //         }
        
    //         for(i <- 1 to 9;j <- 1 to 10){   // i 是val的
    //           if(i > j){
    //             print(i + "*" + j +"=" + i*j + "\t")
    //           }
    //           if(i == j){
    //             println()
    //           }
    //         }
        
    //           for(i <- 1 to 100 if(i%2==0) if (i ==98)){  
    //              println( i)
    //           }
        
                val result = for(i <- 1 to 100 if(i%10==0)) yield i
    
    //            println(result)// Vector(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
        
    //            result.foreach ( (x:Int) => {
    //                println(x)
    //            })
    //             result.foreach ( x => {
    //                println(x)
    //             })
    //             result.foreach (println(_))
                   result.foreach (println)
        
        
        
        
        
        
        
        
        
        
      }
    }
    

      

     

     

     

     

    package com.bjsxt.scala
    
    import java.util.Date
    
    object Lesson_Fun2 {
      def main(args: Array[String]): Unit = {
        /**
         * 1.方法定义
         * 1.方法定义用def,函数的参数 要写类型,不写类型不可以。
         * 2.函数的返回值类型可以不写,会自动推断
         * 3.scala会将函数体中最后一行计算的结果当做返回值返回
         * 4.可以写“return”,写了return要显式的声明方法体的放回类型。
         * 5.定义方法时,如果不写“=”,那么无论方法体中最后一行计算的结果返回是什么,都会被丢弃,返回Unit
         */
        
    //    def max(x:Int,y:Int):Int={
    //      if(x >y){
    //        x
    //      }else{
    //        y
    //      }
    //    }
    //    
    //    println(max(10,11))
        
    //      def max(x:Int,y:Int) = if(x>y) x else y 
    //      println(max(100,2))
        
        
    //     def max(x:Int,y:Int):Int={  // 返回类型
    //      if(x >y){
    //        x
    //      }else{
    //        y
    //      }
    //    }
         
    //      def max(x:Int,y:Int){ // 返回Unit   ()
    //        if(x >y){
    //          x
    //        }else{
    //          y
    //        }
    //      }
        
        
        /**
         * 2.递归函数 , 要指定函数的返回值类型,因为fun(num-1)不能自动推断
         */
    //    def fun(num:Int):Int = {
    //      if(num == 1){
    //          num
    //      }else{
    //        num * fun(num-1)
    //      }
    //    }
    //    println(fun(5))
        
        /**
         * 3.参数有默认值的函数
         */
    //    def fun(a:Int=2,b:Int=3) = {
    //      a+b
    //    }
    //    
        println(fun(1,9))
    //    println(fun())
    //    println(fun(100)) // 替换a
    //    println(fun(b=101))
        
        
          /**
           * 4 可变长参数的函数
           */
    //    def fun(eles:String*) = {
          println(eles)
          for(e<-eles){
            println(e)
          }
            eles.foreach ( s => {println(s)} )
            eles.foreach (println(_))
    //        eles.foreach (println)
    //    }
    //    fun("a")
    //    fun("a","b","c")
        
        /**
         * 5.匿名函数
         */
    //    val fun = (a:Int,b:Int)=>{
          println("hello world" + a + "~"+ b)
    //      a+b
    //    }
    //    println(fun(2,7))
        
        /**
         * 6 嵌套函数
         * 
         */
        
    //    def fun(a:Int) ={
    //      def fun1(num:Int):Int={
    //        if(num ==1 ){
    //          1
    //        }else{
    //          num*fun1(num-1)
    //        }
    //      }
    //      fun1(a)
    //    }
    //    
    //    println(fun(5));
        
        /**
         * 7 偏应用函数
         */
    //    def  showLog(date:Date,log:String) ={
    //      println("date is " + date + ", log is " + log)
    //    }
    //    
    //    val date = new Date()
        showLog(date,"a")
        showLog(date,"b")
        showLog(date,"c")
    //    
    //    val fun = showLog(date:Date,_:String) // 与不变化的参数,变化的参数
    //    fun("aaa")
    //    fun("bbb")
    //    fun("ccc")
        
        /**
         * 8.高阶函数
         *  1.函数的参数时函数
         *  2.函数的返回值是函数 -- 需要显示的声明函数的返回类型
         *  3.函数的参数和返回值都是函数
         *  
         */
    //    def fun(a:Int,b:Int):Int = {
    //      a + b
    //    }
        
    //    def fun1(s:String,f:(Int,Int)=>Int) = {
    //      // 对应集群中逻辑被传入,但是集群不知道逻辑是什么,只要给数据,
    //      val rt = f(100,200)
    //      s + "~" + rt
    //    }
        // 1.函数的参数是函数
    //    println(fun1("hello",fun)) // fun 是函数,不是fun(1,2)
        
        // 传入逻辑,拿到集群的数据按传入的逻辑处理。(至于怎么处理就是传入逻辑的事情了)
        
        
    //     def fun1(f:(Int,Int)=>Int,s:String):String= {
    //      // 对应集群中逻辑被传入,但是集群不知道逻辑是什么,只要给数据,
    //      val rt = f(100,200)
    //      s + "~" + rt
    //    }
    //     val result = fun1((a:Int,b:Int) => { a * b },"hello")
    //     println(result)
        
        // 函数的返回是函数
    //    def fun(a:Int,b:Int):(String,String) =>String = {
    //      val rt = a*b
    //      def fun1(s:String,s1:String)={
    //        s + "@" +s1 + rt
    //      }
    //      fun1
    //    }
    //   
    //    println(fun(10,20)("hello","world")) // hello@world200
    //   
        // 函数的参数和返回值都是函数
        
    //     def fun(f:(Int,Int)=>Int):(String,String) =>String = {
    //      val rt = f(10,100)
    //      def fun1(s:String,s1:String)={
    //        s + "@" +s1 + "$" + rt
    //      }
    //      fun1
    //    }
    //    
    //    val rr = fun( (a,b)=>{a + b})("hello","world") // hello@world$110
    //    println(rr)
        
          /**
           * 9 柯里化函数,是高阶函数的简化版
           */
        def fun(a:Int,b:Int)(c:Int,d:Int) = {
          a + b + c +d 
        }
        
        println(fun(1,2)(3,4))
      }
    }
    

      

    lat
    n. 平地;公寓;平面
    adj. 平的;单调的;不景气的;干脆的;平坦的;扁平的;浅的
    vi. 逐渐变平;[音乐]以降调唱(或奏)
    tuple
    英 [tjʊpəl; ˈtʌpəl]  美 [ˈtjʊpəl; ˈtʌpəl] 
    n. [计] 元组,重数
    
    package com.bjsxt.scala
    
    object Lesson_String {
      def main(args: Array[String]): Unit = {
    //    val s= "bjsxt"
    //    val s1 = "BJSXT"
    //    
    //    println(s.equals(s1))
    //    println(s.equalsIgnoreCase(s1))
        
        val builder = new StringBuilder
        builder.+('a')
       builder.++=("hello")
       builder.append(true)
       println(builder)
      }
    }
    
    package com.bjsxt.scala
    
    object Lesson_Collections {
      def main(args: Array[String]): Unit = {
        /**
         * Array
         */
    //    val arr = Array(1,2,3,4,"hello",'c')
    //    	val arr = Array[Int](1,2,3,4)
        
    //      arr.foreach(println)
        
    //    	for(e<- arr){
    //    	   println(e)
    //    	}
        
    //    		val arr = new Array[String](3) // new 3 表示长度
    //    		arr(0) = "a"
    //    		arr(1) = "b"
    //    		arr(2) = "c"
        
        
        /**
         * List
         */
    //    val list =  List[Int](1,2,3,4,3)
    //    list.foreach(println)
    //    	for(e<- list){
    //    	   println(e)
    //    	}
        
        
        /**
         * map 输入输出 一对一;
         * flatmap 一对多
         */
        
        val list = List("hello world","hello bjsxt", "hello scala")
    //    val rt:List[Array[String]] = list.map(s =>{  // B 表示不为空;函数。// U 表示可以为Unit,或函数
    //      s.split(" ")
    //    })
    //    rt.foreach(arr=>{
    //      println("************")
    //      arr.foreach(println)
    //    })
        
    //    var rt2:List[String] = list.flatMap { s => {
    //      s.split(" ")
    //      }
    //    }
    //    rt2.foreach(println)
        
    //    val rt = list.filter(line =>{
    //      !line.equals("hello world")  // false 被留下,true被过滤
    //    })
    //    rt.foreach(println) // hello world
        
        /**
         * set 无序不重复
         */
    //    val set = Set[Int](1,2,3,4,5)
    //    set.foreach(println)
        
        /**
         * map
         */
    //    val map = Map(1->"a",2->"b",(3,"c"),(3,"d"))
    //    map.foreach(println)
        // 取值
    //    val v:Option[String] = map.get(2)  
    //    println(v) // Some(b)
        
    //    val v = map.get(2).get //String
    //    println(v) // Some(b)
        
    //     val v = map.get(12).getOrElse("xxx") //None.get  
    //    println(v) // Some(b)
        
    //    val keys:Iterable[Int] = map.keys
    //    keys.foreach(println)
        
    //      val vs = map.values
          
        /**
         * tuple 元组  
         * 最多支持22个元素
         */
        
    //    val tuple5 = new Tuple5(1,'c',3,4,"hello")
    //    val tuple1 = new Tuple1("hello")
    //    val tuple2 = new Tuple2("hello",10)
    //    val tuple3 = new Tuple3(1,2,3)
    //    
    //    // 最多22个元素
    //    val tuple = new Tuple22(1,2,2,3,"hello",  3,34,3,3,3,  3,2,23,3,3,  4,4,4,3,2, 2,3)
    //    
    //    val tuple6 = (1,2,3,4,5,6)
    //    
    //    val iter:Iterator[Any] = tuple.productIterator
        iter.foreach(println)
    //    while(iter.hasNext){
    //      println(iter.next())
    //    }
        
          /**
           * map中每个元素就是一个二元组
           */
    //      val map = Map(1->"a",2->"b",(3,"c"),(3,"d"))
          val tuple2 = new Tuple2("hello",10)
          println(tuple2.swap) // (10,hello)  只有tuple2有此方法
        
    //    val Tuple4 = new Tuple4(1,2,3.5,"world")
    //    println(Tuple4._3)
        
        
      }
    }
    
    package com.bjsxt.scala
    
    /**
     * trait接口的特质特性;第一个关键字用extends,之后用with
     */
    trait Speak{
      def speak(name:String)={
        println(name + " is speaking....")
      }
    }
    
    trait Listen{
      def listen(name:String)={
        println(name + " is listening....")
      }
    }
    
    class Person1() extends Speak with Listen{
      
    }
    object Trait01 {
      def main(args: Array[String]): Unit = {
        val p = new Person1()
        p.speak("wangcai")
        p.listen("xiaoqaing")
      }
    }
    
    package com.bjsxt.scala
    
    /**
     * trait 方法实现,不实现
     */
    trait IsEquale{
      def isEqu(p:Any):Boolean
      def isNotEqu(p:Any):Boolean = {
        !isEqu(p)
      }
    }
    
    
    class Point(xx:Int, xy:Int) extends IsEquale {
      val x = xx
      val y  = xy
    
      def isEqu(p: Any): Boolean = {
        p.isInstanceOf[Point] && p.asInstanceOf[Point].x == this.x
    //    		p.isInstanceOf[Point] && p.asInstanceOf[Point].y == this.x
      }
    
    
      
      
    
    }
    object Trait02 {
      def main(args: Array[String]): Unit = {
        val p1 = new Point(1,2)
        val p2 = new Point(1,3)
        println(p1.isEqu(p2))  // true
        println(p1.isNotEqu(p2))  // 
      }
    }
    
    
    ########## 需要导入lib/scala-actors.jar包
    package com.bjsxt.scala import scala.actors.Actor /** * Actor是通信模型,Spark底层代码就是scala编写,spark底层节点之间的通信就是Akka,Akka 是通信模型,Akka底层就是Actor实现的 * Actor 相当于我们理解的线程。Thread * */ class MyActor extends Actor { def act(): Unit = { while(true){ receive{ case s:String => println("value is " + s) case _=>{ println("default...") } } } } } object Lesson_Actor { def main(args: Array[String]): Unit = { val actor = new MyActor() actor.start() // actor ! "hello" actor ! "100" } } package com.bjsxt.scala import scala.actors.Actor case class Message(actor:Actor,msg:String) class MyActor1 extends Actor { def act(): Unit = { while(true){ receive{ case message:Message =>{ if(message.msg.equals("hello")){ println("hello") message.actor ! "hi~" }else if(message.msg.equals("could we have a date?")){ println("could we have a date?") message.actor !"yes!" }else if(message.msg.equals("ok ~")){ println("ok ~") println("Let's go") } } } } } } class MyActor2(actor:Actor) extends Actor { actor ! Message(this,"hello") def act(): Unit = { while(true){ receive{ case s:String =>{ if(s.equals("hi~")){ println("hi~") actor ! Message(this,"could we have a date?") }else if(s.equals("yes!")){ println("yes!") actor ! Message(this,"ok ~") } } } } } } object Lesson_Actor2 { def main(args: Array[String]): Unit = { val actor1 = new MyActor1() val actor2 = new MyActor2(actor1) actor1.start() actor2.start() } }

      

    使用scala 编写 wordcount
    
    src/words 文件
    lib/scala-actors.jar,spark-assembly-1.6.0-hadoop2.6.0.jar
    
    hello spark
    hello bjsxt
    hello spark
    hello spark
    hello java
    hello java
    
    package com.bjsxt.spark
    
    import org.apache.spark.SparkConf
    import org.apache.spark.SparkContext
    import org.apache.spark.rdd.RDD
    
    object ScalaWordCount {
      def main(args: Array[String]): Unit = {
    //    val conf = new SparkConf()
    //    
    //    conf.setMaster("local").setAppName("ScalaWordCount")
    //    
    //    val sc = new SparkContext(conf)
    //    
    //   
    //    val lines: RDD[String] = sc.textFile("src/words") // 一行行读取的
    //   
    //    val words:RDD[String] = lines.flatMap(line =>{
    //      line.split(" ")
    //    })
    //    
    //    val pairWords:RDD[(String, Int)] = words.map(word =>{
    //      new Tuple2(word,1)
    //    })
    //    
    //    /**
    //     * reduceByKey 先分组,后对每一组内的kye对应的value去聚合
    //     */
    //    val result = pairWords.reduceByKey((v1:Int,v2:Int) => { v1 + v2 })
    //    
    //    result.foreach(tuple =>{println(tuple)})
    //    
    //    sc.stop()
        
        
        // 简化版
        val conf = new SparkConf().setMaster("local").setAppName("ScalaWordCount")
        new SparkContext(conf).textFile("src/words").flatMap(_.split(" ")).map((_,1)).reduceByKey(_ + _).foreach(println(_))
    
      }
    }
    

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

      

      

    转载于:https://www.cnblogs.com/xhzd/p/11520776.html

    展开全文
  • scala总体详解

    2018-08-05 11:23:29
    scala 1.Scala怎样声明变量与常量? var val 2.Scala数据类型有哪些? 数据类型 描述 Byte 8位有符号补码整数。数值区间为 -128 到 127 Short 16位有符号补码整数。数值区间为 -32768 到 32767 ...
  • 初学scala者肯定会对:: , +:, :+, :::, +++,这些操作有些疑惑,今天总结一下,方便大家的使用 package test /** * scala中的:: , +:, :+, :::, +++, 等操作; */ object listTest { def main(args: Array[String])...
  • Scala中的"- >"和" -"以及"=>

    千次阅读 2019-11-05 14:54:09
    <-只会出现在for循环里面 ->只会出现在k->v里面 但是=>就较为复杂了,有四种...scala> def double(x: Int): Int = x*2 double: (x: Int)Int //定义一个函数变量: scala> var x : (Int) =&...
  • SCALA学习笔记(一)

    万次阅读 2016-03-07 11:53:28
    变量 获取变量的值是一个耗时的工作时,可以考虑使用lazy var....scala> val first :: rest = List(1, 2, 3) first: Int = 1 rest: List[Int] = List(2, 3) 函数定义 “=”并不只是
  • Scala基础】Scala基础总结(三)

    千次阅读 2019-05-21 23:36:57
    Scala总结 抽象类和抽象成员 与java相似,scala中abstract声明的类是抽象类,抽象类不可以被实例化。 在scala中,抽象类和特质中的方法、字段和类型都可以是抽象的。示例如下: trait MyAbstract { type T ...
  • Scala 将 => 转换成 ⇒

    2016-11-21 10:54:46
  • 大数据之编程语言:Scala视频教程

    万人学习 2018-10-22 21:38:02
    Scala视频培训教程:本课程帮助您掌握Scala编程语言,Scala是一种函数式面向对象语言,它融汇了许多前所未有的特性,而同时又运行于JVM之上。随着开发者对Scala的兴趣日增,以及越来越多的工具支持,无疑Scala语言将...
  • Scala SDK及IDEA插件安装配置

    万次阅读 2018-09-07 19:59:16
    Linux CentOS6.5 Scala SDK安装配置 1. 下载Scala SDK安装包 下载地址:https://www.scala-lang.org/download/ 本文linux版本为:scala-2.11.12.tgz   2. 创建本地安装目录  mkdir -p /usr/local/scala-...
  • Intellij idea配置scala开发环境

    万次阅读 2016-08-07 18:19:16
    1.Intellij idea配置scala开发环境解决Plugin Scala was not installed: No route to host Plugin Scala was not installed: connect timed out Plugin Scala was not installed: Cannot download ...
  • 具体步骤如下: 1、找到与IntelliJ IDEA对应的scala版本 ...在搜索框中输入scala,点击scala,在对话框右边出现该IntelliJ对应的scala版本,从图中看到该版本的IntelliJ对应的scala版本是2018/2/11更新的 ...
  • Scala IntelliJ添加Scala SDK

    万次阅读 2018-06-14 16:45:45
    1. 右键项目名称找到Open Module Settings2. 左侧Project Settings目录中点击Libraries3. 点击+new Project Library选择Scala SDK4. 添加下载好的jar文件夹
  • Scala开发入门教程

    万次阅读 多人点赞 2014-03-23 17:12:38
    Scala语言和其它语言比如Java相比,算是一个比较复杂的语言,它是一个面向对象和面向函数的统一体,使用起来非常灵活,因此也不容易掌握,前面的58篇文章仅仅是Scala的一个入门教程,有了这些基础知识,你就可以开始...
  • IDEA安装Scala,版本对应

    万次阅读 2017-07-31 10:42:51
    IDEA安装Scala,版本对应 一、下载插件 1、找到与IntelliJ IDEA对应的scala版本 2.在弹出的对话框中点击Plugins,然后点击Install JetBrains plugin 3.在搜索框中输入scala,点击scala,在对话框...
  • scala 字符串转Int, Long 类型

    万次阅读 2018-08-02 13:56:26
    val stringdemo = "123456" val intdemo = stringdemo.toInt val longdemo = stringdemo.toLong
1 2 3 4 5 ... 20
收藏数 142,850
精华内容 57,140
关键字:

scala