Scala基础学习记录

Table of Contents

1 Scala简介

Scala(Scala Language的简称)语言是一种能够运行于JVM和.Net平台之上的通用编程语言,既可用于大规模应用程序开发,也可用于脚本编程,它由由Martin Odersk于2001开发,2004年开始程序运行在JVM与.Net平台之上,由于其简洁、优雅、类型安全的编程模式而受到关注。

2 Scala特性

2.1 面向对象特性

Scala是一种纯面向对象的语言,每个值都是对象。对象的数据类型以及行为由类和特质描述。 类抽象机制的扩展有两种途径:一种途径是子类继承,另一种途径是灵活的混入机制。这两种途径能避免多重继承的种种问题。

2.2 函数式编程

  1. 高阶函数(Higher-order functions)
  2. 闭包( closures)
  3. 模式匹配( Pattern matching)
  4. 单一赋值( Single assignment )
  5. 延迟计算( Lazy evaluation)
  6. 类型推导( Type inference )
  7. 尾部调用优化( Tail call optimization)
  8. 类型推导( Type inference )

2.3 静态类型

Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性。类型系统具体支持以下特性:

  • 泛型类
  • 协变和逆变
  • 标注
  • 类型参数的上下限约束
  • 把类别和抽象类型作为对象成员
  • 复合类型
  • 引用自己时显式指定类型
  • 视图
  • 多态方法

2.4 兼容性、移植性

Scala运行于JVM上,能够与JAVA进行互操作,具有与JAVA一样的平台移植性

2.5 并发性

Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现。

3 为什么学习Scala

Scala是未来大数据处理的主流语言

4 语言学习

4.1 基础语法

4.1.1 注意的点

  • 区分大小写 - Scala是大小写敏感的,这意味着标识Hello 和 hello在Scala中会有不同的含义。
  • 类名 - 对于所有的类名的第一个字母要大写。
  • 方法名称 - 所有的方法名称的第一个字母用小写。
  • 程序文件名 - 程序文件的名称应该与对象名称完全匹配。
  • def main(args: Array[String]) - Scala程序从main()方法开始处理,这是每一个Scala程序的强制程序入口部分。

4.1.2 标识符

Scala 可以使用两种形式的标志符,字符数字和符号。 字符数字使用字母或是下划线开头,后面可以接字母或是数字,符号"\("在 Scala 中也看作为字母。然而以"\)"开头的标识符为保留的 Scala 编译器产生的标志符使用,应用程序应该避免使用"$"开始的标识符,以免造成冲突。 Scala 的命名规则采用和 Java 类似的 camel 命名规则,首字符小写,比如 toString。类名的首字符还是使用大写。此外也应该避免使用以下划线结尾的标志符以避免冲突。符号标志符包含一个或多个符号,如+,:,? 等,比如:

+ ++ ::: < ?> :->

Scala 内部实现时会使用转义的标志符,比如:-> 使用 $colon$minus$greater 来表示这个符号。因此如果你需要在 Java 代码中访问:->方法,你需要使用 Scala 的内部名称 $colon$minus$greater。 混合标志符由字符数字标志符后面跟着一个或多个符号组成,比如 unary_+ 为 Scala 对+方法的内部实现时的名称。字面量标志符为使用"定义的字符串,比如 `x` `yield`。 你可以在"之间使用任何有效的 Scala 标志符,Scala 将它们解释为一个 Scala 标志符,一个典型的使用为 Thread 的 yield 方法, 在 Scala 中你不能使用 Thread.yield()是因为 yield 为 Scala 中的关键字, 你必须使用 Thread.`yield`()来使用这个方法。

4.1.3 Scala包

定义包 Scala 使用 package 关键字定义包,在Scala将代码定义到某个包中有两种方式: 第一种方法和 Java 一样,在文件的头定义包名,这种方法就后续所有代码都放在该包中。 比如:

package com.hhh
class HelloWorld

第二种方法有些类似 C#,如:

package com.hhh {
  class HelloWorld
}

引用 Scala 使用 import 关键字引用包。

import java.awt.Color  // 引入Color

import java.awt._  // 引入包内所有成员

def handler(evt: event.ActionEvent) { // java.awt.event.ActionEvent
  ...  // 因为引入了java.awt,所以可以省去前面的部分
}

import语句可以出现在任何地方,而不是只能在文件顶部。import的效果从开始延伸到语句块的结束。这可以大幅减少名称冲突的可能性。 如果想要引入包中的几个成员,可以使用selector(选取器):

import java.awt.{Color, Font}

// 重命名成员
import java.util.{HashMap => JavaHashMap}

// 隐藏成员
import java.util.{HashMap => _, _} // 引入了util包的所有成员,但是HashMap被隐藏了

注意: 默认情况下,Scala 总会引入 java.lang._ 、 scala._ 和 Predef._,这里也能解释,为什么以scala开头的包,在使用时都是省去scala.的。

配置好环境后的第一个练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test1.scala

4.2 数据类型

Scala中的基本数据类型如下图:

www.anbgsl1110-dms-aliang.space\diary\img\201709\sacla-exercise001.png

从上表中可以看出,Scala的基本数据类型与Java中的基本数据类型是一一对应的,不同的是Scala的基本数据类型头字母必须大写。

4.2.1 整型字面量

整型字面量用于 Int 类型,如果表示 Long,可以在数字后面添加 L 或者小写 l 作为后缀。

4.2.2 浮点型字面量

如果浮点数后面有f或者F后缀时,表示这是一个Float类型,否则就是一个Double类型的。

4.2.3 布尔型字面量

布尔型字面量有 true 和 false。

4.2.4 符号字面量

符号字面量被写成: '<标识符> ,这里 <标识符> 可以是任何字母或数字的标识(注意:不能以数字开头)。这种字面量被映射成预定义类scala.Symbol的实例。 如: 符号字面量 'x 是表达式 scala.Symbol("x") 的简写。

4.2.5 字符字面量

在scala中字符类型表示为半角单引号(')中的字符。

4.2.6 字符串字面量

字符串表示方法是在双引号中(") 包含一系列字符。

4.2.7 多行字符串的表示方法

多行字符串用三个双引号来表示分隔符,格式为:""" … """。

4.2.8 Null 值

空值是 scala.Null 类型。 Scala.Null和scala.Nothing是用统一的方式处理Scala面向对象类型系统的某些"边界情况"的特殊类型。 Null类是null引用对象的类型,它是每个引用类(继承自AnyRef的类)的子类。Null不兼容值类型。

4.3 变量

变量是一种使用方便的占位符,用于引用计算机内存地址,变量创建后会占用一定的内存空间。 基于变量的数据类型,操作系统会进行内存分配并且决定什么将被储存在保留内存中。因此,通过给变量分配不同的数据类型,你可以在这些变量中存储整数,小数或者字母。

4.3.1 变量声明

在学习如何声明变量与常量之前,我们先来了解一些变量与常量。

  • 变量: 在程序运行过程中其值可能发生改变的量叫做变量。如:时间,年龄。
  • 常量 在程序运行过程中其值不会发生变化的量叫做常量。如:数值 3,字符'A'。

在 Scala 中,使用关键词 "var" 声明变量,使用关键词 "val" 声明常量。

4.3.2 变量类型声明

变量的类型在变量名之后等号之前声明。定义变量的类型的语法格式如下:

var VariableName : DataType [=  Initial Value]

或

val VariableName : DataType [=  Initial Value]

变量声明不一定需要初始值,以下也是正确的:

var myVar :Int;
val myVal :String;

4.3.3 变量类型引用

在 Scala 中声明变量和常量不一定要指明数据类型,在没有指明数据类型的情况下,其数据类型是通过变量或常量的初始值推断出来的。 所以,如果在没有指明数据类型的情况下声明变量或常量必须要给出其初始值,否则将会报错。

var myVar = 10;
val myVal = "Hello, Scala!";

以上实例中,myVar 会被推断为 Int 类型,myVal 会被推断为 String 类型。

4.3.4 Scala 多个变量声明

Scala 支持多个变量的声明:

val xmax, ymax = 100  // xmax, ymax都声明为100

变量练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test2.scala

4.4 访问修饰符

Scala 访问修饰符基本和Java的一样,分别有:private,protected,public。

如果没有指定访问修饰符符,默认情况下,Scala对象的访问级别都是 public。

Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。

4.4.1 私有(Private)成员

用private关键字修饰,带有此标记的成员仅在包含了成员定义的类或对象内部可见,同样的规则还适用内部类。

4.4.2 保护(Protected)成员

在 scala 中,对保护(Protected)成员的访问比 java 更严格一些。因为它只允许保护成员在定义了该成员的的类的子类中被访问。而在java中,用protected关键字修饰的成员,除了定义了该成员的类的子类可以访问,同一个包里的其他类也可以进行访问。

4.4.3 公共(Public)成员

Scala中,如果没有指定任何的修饰符,则默认为 public。这样的成员在任何地方都可以被访问。

4.4.4 作用域保护

Scala中,访问修饰符可以通过使用限定词强调。格式为:

private[x]

或

protected[x]

这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作"这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。

这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。

访问修饰符练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test3.scala

4.5 运算符

Scala 含有丰富的内置运算符,包括以下几种类型:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符

运算符优先级取决于所属的运算符组,它会影响算式的的计算。

以下表格,优先级从上到下依次递减,最上面具有最高的优先级,逗号操作符具有最低的优先级

类别 运算符 关联性
1 () [] 左到右
2 ! ~ 右到左
3 * / % 左到右
4 + - 左到右
5 >> >>> << 左到右
6 > >= < <= 左到右
7 = ! 左到右
8 & 左到右
9 ^ 左到右
10   左到右
11 && 左到右
12   左到右
13 = `+=` -= *= /= %= >>= <<= &= ^= 右到左
14 , 左到右

运算符练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test4.scala

4.6 循环

4.6.1 while 循环

运行一系列语句,如果条件为true,会重复运行,直到条件变为false。

while(condition)
{
   statement(s);
}

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。 condition 可以是任意的表达式,当为任意非零值时都为 true。当条件为 true 时执行循环。 当条件为 false 时,退出循环,程序流将继续执行紧接着循环的下一条语句。

sacla-exercise002.png

在这里,while 循环的关键点是循环可能一次都不会执行。当条件为 false 时,会跳过循环主体,直接执行紧接着 while 循环的下一条语句。

4.6.2 do…while 循环

类似whlie语句区别在于判断循环条件之前,先执行一次循环的代码块。

do {
   statement(s);
} while( condition );

sacla-exercise003.jpg

请注意,条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。 如果条件为 true,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。 这个过程会不断重复,直到给定条件变为 false 为止。

4.6.3 for 循环

用来重复执行一系列语句直到特定条件达成,一般通过在每次循环完成后增加计数器的值来实现。

Scala 语言中 for 循环的语法:

for( var x <- Range ){
   statement(s);
}

以上语法中,Range 可以是一个数字区间表示 i to j ,或者 i until j。左箭头 <- 用于为变量 x 赋值。

  1. for 循环集合

    for 循环集合的语法如下:

    for( var x <- List ){
       statement(s);
    }
    

    以上语法中, List 变量是一个集合,for 循环会迭代所有集合的元素。

  2. for 循环过滤

    Scala 可以使用一个或多个 if 语句来过滤一些元素。 以下是在 for 循环中使用过滤器的语法。

    for( var x <- List
          if condition1; if condition2...
       ){
       statement(s);
    

    你可以使用(;)来为表达式添加一个或多个过滤条件。

  3. for 使用 yield

    你可以将 for 循环的返回值作为一个变量存储。语法格式如下:

    var retVal = for{ var x <- List
         if condition1; if condition2...
    }yield x
    

    注意大括号中用于保存变量和条件,retVal 是变量, 循环中的 yield 会把当前的元素记下来,保存在集合中,循环结束后将返回该集合。

4.6.4 break 语句

中断循环

Scala 语言中默认是没有 break 语句,但是你在 Scala 2.8 版本后可以使用另外一种方式来实现 break 语句。当在循环中使用 break 语句,在执行到该语句时,就会中断循环并执行循环体之后的代码块。

// 导入以下包
import scala.util.control._

// 创建 Breaks 对象
val loop = new Breaks;

// 在 breakable 中循环
loop.breakable{
    // 循环
    for(...){
       ....
       // 循环中断
       loop.break;
   }
}

sacla-exercise004.jpg

4.6.5 无限循环

如果条件永远为 true,则循环将变成无限循环。我们可以使用 while 语句来实现无限循环:

object Test {
   def main(args: Array[String]) {
      var a = 10;
      // 无限循环
      while( true ){
         println( "a 的值为 : " + a );
      }
   }
}

循环练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test6.scala

4.7 函数

函数是一组一起执行一个任务的语句。 您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。

Scala 有函数和方法,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。

我们可以在任何地方定义函数,甚至可以在函数内定义函数(内嵌函数)。更重要的一点是 Scala 函数名可以有以下特殊字符:+, ++, ~, &,-, – , \, /, : 等。

4.7.1 函数声明

def functionName ([参数列表]) : [return type]

如果你不写等于号和方法主体,那么方法会被隐式声明为"抽象(abstract)",包含它的类型于是也是一个抽象类型。

4.7.2 函数定义

方法定义由一个def 关键字开始,紧接着是可选的参数列表,一个冒号":" 和方法的返回类型,一个等于号"=",最后是方法的主体。

Scala 函数定义格式如下:

def functionName ([参数列表]) : [return type] = {
   function body
   return [expr]
}

以上代码中 return type 可以是任意合法的 Scala 数据类型。参数列表中的参数可以使用逗号分隔。

如果函数没有返回值,可以返回为 Unit,这个类似于 Java 的 void, 实例如下:

4.7.3 函数调用

标准格式:

functionName( 参数列表 )

如果函数使用了实例的对象来调用,我们可以使用类似java的格式 (使用 . 号):

[instance.]functionName( 参数列表 )

4.7.4 函数传名调用(Call-By-Name)

Scala的解释器在解析函数参数(function arguments)时有两种方式:

  • 传值调用(call-by-value):先计算表达式的值,再应用到函数内部;
  • 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部

在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。 这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值。

object Test {
   def main(args: Array[String]) {
        delayed(time());
   }

   def time() = {
      println("获取时间,单位为纳秒")
      System.nanoTime
   }
   def delayed( t: => Long ) = {
      println("在 delayed 方法内")
      println("参数: " + t)
      t
   }
}

以上实例中我们声明了 delayed 方法, 该方法在变量名和变量类型使用 => 符号来设置传名调用。

4.7.5 指定函数参数名

一般情况下函数调用参数,就按照函数定义时的参数顺序一个个传递。但是我们也可以通过指定函数参数名,并且不需要按照顺序向函数传递参数,实例如下:

object Test {
   def main(args: Array[String]) {
        printInt(b=5, a=7);
   }
   def printInt( a:Int, b:Int ) = {
      println("Value of a : " + a );
      println("Value of b : " + b );
   }
}

4.7.6 函数-可变参数

Scala 允许你指明函数的最后一个参数可以是重复的,即我们不需要指定函数参数的个数,可以向函数传入可变长度参数列表。 Scala 通过在参数的类型之后放一个星号来设置可变参数(可重复的参数)。例如:

object Test {
   def main(args: Array[String]) {
        printStrings("Runoob", "Scala", "Python");
   }
   def printStrings( args:String* ) = {
      var i : Int = 0;
      for( arg <- args ){
         println("Arg value[" + i + "] = " + arg );
         i = i + 1;
      }
   }
}

4.7.7 递归函数

递归函数在函数式编程的语言中起着重要的作用。

Scala 同样支持递归函数。

递归函数意味着函数可以调用它本身。

以上实例使用递归函数来计算阶乘:

object Test {
   def main(args: Array[String]) {
      for (i <- 1 to 10)
         println(i + " 的阶乘为: = " + factorial(i) )
   }

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

4.7.8 默认参数值

Scala 可以为函数参数指定默认参数值,使用了默认参数,你在调用函数的过程中可以不需要传递参数,这时函数就会调用它的默认参数值,如果传递了参数,则传递值会取代默认值。实例如下:

object Test {
   def main(args: Array[String]) {
        println( "返回值 : " + addInt() );
   }
   def addInt( a:Int=5, b:Int=7 ) : Int = {
      var sum:Int = 0
      sum = a + b

      return sum
   }
}

4.7.9 高阶函数

高阶函数(Higher-Order Function)就是操作其他函数的函数。

Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。 以下实例中,apply() 函数使用了另外一个函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v:

object Test {
   def main(args: Array[String]) {

      println( apply( layout, 10) )

   }
   // 函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v
   def apply(f: Int => String, v: Int) = f(v)

   def layout[A](x: A) = "[" + x.toString() + "]"

}

4.7.10 内嵌函数

我们可以在 Scala 函数内定义函数,定义在函数内的函数称之为局部函数。 以下实例我们实现阶乘运算,并使用内嵌函数:

object Test {
   def main(args: Array[String]) {
      println( factorial(0) )
      println( factorial(1) )
      println( factorial(2) )
      println( factorial(3) )
   }

   def factorial(i: Int): Int = {
      def fact(i: Int, accumulator: Int): Int = {
         if (i <= 1)
            accumulator
         else
            fact(i - 1, i * accumulator)
      }
      fact(i, 1)
   }
}

4.7.11 匿名函数

Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体。 使用匿名函数后,我们的代码变得更简洁了。 下面的表达式就定义了一个接受一个Int类型输入参数的匿名函数:

var inc = (x:Int) => x+1

上述定义的匿名函数,其实是下面这种写法的简写:

def add2 = new Function1[Int,Int]{
    def apply(x:Int):Int = x+1;
}

以上实例的 inc 现在可作为一个函数,使用方式如下:

var x = inc(7)-1

同样我们可以在匿名函数中定义多个参数:

var mul = (x: Int, y: Int) => x*y

mul 现在可作为一个函数,使用方式如下:

println(mul(3, 4))

我们也可以不给匿名函数设置参数,如下所示:

var userDir = () => { System.getProperty("user.dir") }

userDir 现在可作为一个函数,使用方式如下:

println( userDir() )

4.7.12 偏应用函数

Scala 偏应用函数是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。 如下实例,我们打印日志信息:

import java.util.Date

object Test {
   def main(args: Array[String]) {
      val date = new Date
      log(date, "message1" )
      Thread.sleep(1000)
      log(date, "message2" )
      Thread.sleep(1000)
      log(date, "message3" )
   }

   def log(date: Date, message: String)  = {
     println(date + "----" + message)
   }
}

实例中,log() 方法接收两个参数:date 和 message。我们在程序执行时调用了三次,参数 date 值都相同,message 不同。 我们可以使用偏应用函数优化以上方法,绑定第一个 date 参数,第二个参数使用下划线(_)替换缺失的参数列表,并把这个新的函数值的索引的赋给变量。以上实例修改如下:

import java.util.Date

object Test {
   def main(args: Array[String]) {
      val date = new Date
      val logWithDateBound = log(date, _ : String)

      logWithDateBound("message1" )
      Thread.sleep(1000)
      logWithDateBound("message2" )
      Thread.sleep(1000)
      logWithDateBound("message3" )
   }

   def log(date: Date, message: String)  = {
     println(date + "----" + message)
   }
}

4.7.13 函数柯里化(Function Currying)

柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。

  1. 实例

    首先我们定义一个函数:

    def add(x:Int,y:Int)=x+y
    

    那么我们应用的时候,应该是这样用:add(1,2) 现在我们把这个函数变一下形:

    def add(x:Int)(y:Int) = x + y
    

    那么我们应用的时候,应该是这样用:add(1)(2),最后结果都一样是3,这种方式(过程)就叫柯里化。

  2. 实现过程

    add(1)(2) 实际上是依次调用两个普通函数(非柯里化函数),第一次调用使用一个参数 x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。

    实质上最先演变成这样一个方法:

    def add(x:Int)=(y:Int)=>x+y
    

    那么这个函数是什么意思呢? 接收一个x为参数,返回一个匿名函数,该匿名函数的定义是:接收一个Int型参数y,函数体为x+y。现在我们来对这个方法进行调用。

    val result = add(1)
    

    返回一个result,那result的值应该是一个匿名函数:(y:Int)=>1+y 所以为了得到结果,我们继续调用result。

    val sum = result(2)
    

    最后打印出来的结果就是3

4.7.14 闭包

闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。

闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。

如下面这段匿名的函数:

val multiplier = (i:Int) => i * 10

函数体内有一个变量 i,它作为函数的一个参数。如下面的另一段代码:

val multiplier = (i:Int) => i * factor

在 multiplier 中有两个变量:i 和 factor。其中的一个 i 是函数的形式参数,在 multiplier 函数被调用时,i 被赋予一个新的值。然而,factor不是形式参数,而是自由变量,考虑下面代码:

var factor = 3
val multiplier = (i:Int) => i * factor

这里我们引入一个自由变量 factor,这个变量定义在函数外面。 这样定义的函数变量 multiplier 成为一个"闭包",因为它引用到函数外面定义的变量,定义这个函数的过程是将这个自由变量捕获而构成一个封闭的函数。

object Test {
   def main(args: Array[String]) {
      println( "muliplier(1) value = " +  multiplier(1) )
      println( "muliplier(2) value = " +  multiplier(2) )
   }
   var factor = 3
   val multiplier = (i:Int) => i * factor
}

函数练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test7.scala

闭包练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test8.scala

4.8 字符串

在 Scala 中,字符串的类型实际上是 Java String,它本身没有 String 类。

在 Scala 中,String 是一个不可变的对象,所以该对象不可被修改。这就意味着你如果修改字符串就会产生一个新的字符串对象。

4.8.1 创建字符串

var greeting = "Hello World!";

或

var greeting:String = "Hello World!";

你不一定为字符串指定 String 类型,因为 Scala 编译器会自动推断出字符串的类型为 String。

String 对象是不可变的,如果你需要创建一个可以修改的字符串,可以使用 String Builder 类。

4.8.2 字符串长度

度我们可以使用 length() 方法来获取字符串长度。

4.8.3 字符串连接

  1. String 类中使用 concat() 方法来连接两个字符串;
  2. 同样你也可以使用加号(+)来连接

4.8.4 格式化字符串

String 类中你可以使用 printf() 方法来格式化字符串并输出,String format() 方法可以返回 String 对象而不是 PrintStream 对象。

4.8.5 String 方法

下表列出了 java.lang.String 中常用的方法,你可以在 Scala 中使用:

序号 方法 描述
1 char charAt(int index) 返回指定位置的字符
2 int compareTo(Object o) 比较字符串与对象
3 int compareTo(String anotherString) 按字典顺序比较两个字符串
4 int compareToIgnoreCase(String str) 按字典顺序比较两个字符串,不考虑大小写
5 String concat(String str) 将指定字符串连接到此字符串的结尾
6 boolean contentEquals(StringBuffer sb) 将此字符串与指定的 StringBuffer 比较。
7 static String copyValueOf(char[] data) 返回指定数组中表示该字符序列的 String
8 static String copyValueOf(char[] data, int offset, int count) 返回指定数组中表示该字符序列的 String
9 boolean endsWith(String suffix) 测试此字符串是否以指定的后缀结束
10 boolean equals(Object anObject) 将此字符串与指定的对象比较
11 boolean equalsIgnoreCase(String anotherString) 将此 String 与另一个 String 比较,不考虑大小写
12 byte getBytes() 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中
13 byte[] getBytes(String charsetName 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中
14 void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 将字符从此字符串复制到目标字符数组
15 int hashCode() 返回此字符串的哈希码
16 int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引
17 int indexOf(int ch, int fromIndex) 返返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索
18 int indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引
19 int indexOf(String str, int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
20 String intern() 返回字符串对象的规范化表示形式
21 int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引
22 int lastIndexOf(int ch, int fromIndex) 返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索
23 int lastIndexOf(String str) 返回指定子字符串在此字符串中最右边出现处的索引
24 int lastIndexOf(String str, int fromIndex) 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
25 int length() 返回此字符串的长度
26 boolean matches(String regex) 告知此字符串是否匹配给定的正则表达式
27 boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) 测试两个字符串区域是否相等
28 boolean regionMatches(int toffset, String other, int ooffset, int len) 测试两个字符串区域是否相等
29 String replace(char oldChar, char newChar) 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的
30 String replaceAll(String regex, String replacement 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串
31 String replaceFirst(String regex, String replacement) 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串
32 String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串
33 String[] split(String regex, int limit) 根据匹配给定的正则表达式来拆分此字符串
34 boolean startsWith(String prefix) 测试此字符串是否以指定的前缀开始
35 boolean startsWith(String prefix, int toffset) 测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
36 CharSequence subSequence(int beginIndex, int endIndex) 返回一个新的字符序列,它是此序列的一个子序列
37 String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串
38 String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串
39 char[] toCharArray() 将此字符串转换为一个新的字符数组
40 String toLowerCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为小写
41 String toLowerCase(Locale locale) 使用给定 Locale 的规则将此 String 中的所有字符都转换为小写
42 String toString() 返回此对象本身(它已经是一个字符串!)
43 String toUpperCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为大写
44 String toUpperCase(Locale locale) 使用给定 Locale 的规则将此 String 中的所有字符都转换为大写
45 String trim() 删除指定字符串的首尾空白符
46 static String valueOf(primitive data type x) 返回指定类型参数的字符串表示形式

字符串练习:https://github.com/anbgsl1110/exercise/blob/2017-V5.0/9911-9919/_9911/src/ScalaClass/Test9.scala

4.9 数组

Scala 语言中提供的数组是用来存储固定大小的同类型元素,数组对于每一门编辑应语言来说都是重要的数据结构之一。 声明数组变量并不是声明 number0、number1、…、number99 一个个单独的变量,而是声明一个就像 numbers 这样的变量,然后使用 numbers[0]、numbers[1]、…、numbers[99] 来表示一个个单独的变量。数组中某个指定的元素是通过索引来访问的。 数组的第一个元素索引为0,最后一个元素的索引为元素总数减1。

4.9.1 申明数组

以下是 Scala 数组声明的语法格式:

var z:Array[String] = new Array[String](3)

或

var z = new Array[String](3)

我们也可以使用以下方式来定义一个数组:

var z = Array("hhh1", "hhh2", "hhh3")

4.9.2 处理数组

数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本的 for 循环。 #+BEGIN_SRC object Test { def main(args: Array[String]) { var myList = Array(1.9, 2.9, 3.4, 3.5)

// 输出所有数组元素 for ( x <- myList ) { println( x ) }

// 计算数组所有元素的总和 var total = 0.0; for ( i <- 0 to (myList.length - 1)) { total += myList(i); } println("总和为 " + total);

// 查找数组中的最大元素 var max = myList(0