Scala基础语法指南

Scala

标签(空格分隔): Scala
By Vinfly


Scala介绍

  • Scala与Java的关系
    Scala是基于Java虚拟机,也就是JVM的一门编程语言。所有Scala的代码,都需要经过编译为字节码,然后交由Java虚拟机来运行。所以Scala和Java是可以无缝互操作的。Scala可以任意调用Java的代码。所以Scala与Java的关系是非常非常紧密的。
  • Scala安装
    1、从Scala官方网站下载,http://www.scala-lang.org/download/,windows版本的安装包是scala-2.11.7.msi。
    2、使用下载下来的安装包安装Scala。
    3、在PATH环境变量中,配置$SCALA_HOME/bin目录。
    4、在windows命令行内即可直接键入scala,打开scala命令行,进行scala编程。
  • Scala解释器的使用
    1、 REPL:Read(取值)-> Evaluation(求值)->Print(打印)->Loop(循环)。scala解释器也被称为REPL,会快速编译scala代码为字节码,然后交给JVM来执行。
    2、计算表达式:在scala>命令行内,键入scala代码,解释器会直接返回结果给你。如果你没有指定变量来存放这个值,那么值默认的名称为res,而且会显示结果的数据类型,比如Int、Double、String等等。
    – 例如,输入1 + 1,会看到res0: Int = 2
    3、内置变量:在后面可以继续使用res这个变量,以及它存放的值。
    –例如,2.0 * res0,返回res1: Double = 4.0
    –例如,”Hi, ” + res0,返回res2: String = Hi, 2
    4、自动补全:在scala>命令行内,可以使用Tab键进行自动补全。
    –例如,输入res2.to,敲击Tab键,解释器会显示出以下选项,toCharArray,toLowerCase,toString,toUpperCase。因为此时无法判定你需要补全的是哪一个,因此会提供给你所有的选项。
    –例如,输入res2.toU,敲击Tab键,直接会给你补全为res2.toUpperCase。

Scala语法

声明变量

  • 声明val变量:可以声明val变量来存放表达式的计算结果。
    –例如,val result = 1 + 1
    后续这些常量是可以继续使用的
    –例如,2 * result
    但是常量声明后,是无法改变它的值的,
    –例如,result = 1,会返回error: reassignment to val的错误信息。
  • 声明var变量:如果要声明值可以改变的引用,可以使用var变量。
    –例如,val myresult = 1,myresult = 2
    但是在scala程序中,通常建议使用val,也就是常量,因此比如类似于spark的大型复杂系统中,需要大量的网络传输数据,如果使用var,可能会担心值被错误的更改。
  • 在Java的大型复杂系统的设计和开发中,也使用了类似的特性,我们通常会将传递给其他模块 / 组件 / 服务的对象,设计成不可变类(Immutable Class)。在里面也会使用java的常量定义,比如final,阻止变量的值被改变。从而提高系统的健壮性(robust,鲁棒性),和安全性。
  • 指定类型:无论声明val变量,还是声明var变量,都可以手动指定其类型,如果不指定的话,scala会自动根据值,进行类型的推断。
    –例如,val name: String = null
    –例如,val name: Any = “leo”
  • 声明多个变量:可以将多个变量放在一起进行声明。
    –例如,val name1, name2:String = null
    –例如,val num1, num2 = 100
    image_1ap0df831jvm1tt240917fu18fq9.png-14.8kB

数据类型与操作符

  • 基本数据类型
    Byte、Char、Short、Int、Long、Float、Double、Boolean。
    乍一看与Java的基本数据类型的包装类型相同,但是scala没有基本数据类型与包装类型的概念,统一都是类。scala自己会负责基本数据类型和引用类型的转换操作。使用以上类型,直接就可以调用大量的函数。
    –例如,1.toString(),1.to(10)。
  • 类型的加强版类型:scala使用很多加强类给数据类型增加了上百种增强的功能或函数。
    –例如,String类通过StringOps类增强了大量的函数,”Hello”.intersect(” World”)。
    image_1ap0e4991fpo124uftmie9ijm.png-11kB
    –例如,Scala还提供了RichInt、RichDouble、RichChar等类型,RichInt就提供了to函数,1.to(10),此处Int先隐式转换为RichInt,然后再调用其to函数
    image_1ap0egu5u1tl079huqm12nr1ghl1g.png-21.7kB
  • 基本操作符:scala的算术操作符与java的算术操作符也没有什么区别,比如+、-、*、/、%等,以及&、|、^、>>、<<等。但是,在scala中,这些操作符其实是数据类型的函数,
    –比如1 + 1,可以写做1.+(1) ,1.to(10),又可以写做1 to 10
  • scala中没有提供++、–操作符,我们只能使用+和-,比如counter=1,counter++是错误的,必须写做counter += 1.

函数调用和apply()函数

  • ·函数调用方式:在scala中,函数调用也很简单。

    ·例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)。
    ·不同的一点是,如果调用函数时,不需要传递参数,则scala允许调用函数时省略括号的,例如,”Hello World”.distinct

  • ·apply函数

    ·Scala中的apply函数是非常特殊的一种函数,在Scala的object中,可以声明apply函数。而使用“类名()”的形式,其实就是“类名.apply()”的一种缩写。通常使用这种方式来构造类的对象,而不是使用“new 类名()”的方式。
    ·例如,”Hello World”(6),因为在StringOps类中有def apply(n: Int):Char的函数定义,所以”Hello World”(6),实际上是”Hello World”.apply(6)的缩写。
    image_1ap0f54jq1sse1k8ftv1ghifjn1t.png-8.3kB
    ·例如,Array(1, 2, 3, 4),实际上是用Array object的apply()函数来创建Array类的实例,也就是一个数组。
    image_1ap0fafss3qp1uau98a1qn68ri2q.png-12.2kB


条件控制和循环

  • if表达式
    if表达式的定义:在Scala中,if表达式是有值的,就是if或者else中最后一行语句返回的值。
    ·例如,val age = 30; if (age > 18) 1 else 0
    ·可以将if表达式赋予一个变量,例如,val isAdult = if (age > 18) 1 else 0
    ·另外一种写法,var isAdult = -1; if(age > 18) isAdult = 1 else isAdult=0,但是通常使用上一种写法
    ·if表达式的类型推断:由于if表达式是有值的,而if和else子句的值类型可能不同,此时if表达式的值是什么类型呢?Scala会自动进行推断,取两个类型的公共父类型。
    ·例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
    ·例如,if(age > 18) “adult” else 0,此时if和else的值分别是String和Int,则表达式的值是Any,Any是String和Int的公共父类型
    ·如果if后面没有跟else,则默认else的值是Unit,也用()表示,类似于java中的void或者null。例如,val age = 12; if(age > 18) “adult”。此时就相当于if(age > 18) “adult” else ()。
    ·将if语句放在多行中:默认情况下,REPL只能解释一行语句,但是if表达式通常需要放在多行。
    ·可以使用{}的方式,比如以下方式,或者使用:paste和ctrl+D的方式。
    if(age > 18) { “adult”
    } else if(age > 12) “teenager” else “children”
  • 语句终结符、块表达式
    ·默认情况下,scala不需要语句终结符,默认将每一行作为一个语句
    ·一行放多条语句:如果一行要放多条语句,则必须使用语句终结符
    ·例如,使用分号作为语句终结符,var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 }
    ·通常来说,对于多行语句,还是会使用花括号的方式
if(a < 10) {
     b = b + 1
     c = c + 1
} 

·块表达式:块表达式,指的就是{}中的值,其中可以包含多条语句,最后一个语句的值就是块表达式的返回值。
·例如,var d = if(a < 10) { b = b + 1; c + 1 }
- 输入和输出
·print和println:print打印时不会加换行符,而println打印时会加一个换行符。
·例如,print(“Hello World”); println(“Hello World”)
·printf:printf可以用于进行格式化
·例如,printf(“Hi, my name is %s, I’m %d years old.\n”, “Leo”, 30)
image_1ap0gg420of51i4j1gk5s621d7037.png-20.3kB
readLine: readLine允许我们从控制台读取用户输入的数据,类似于java中的System.in和Scanner的作用
·综合案例:游戏厅门禁

val name = readLine("Welcome to Game House. Please tell me your name: ")
print("Thanks. Then please tell me your age: ")
val age = readInt()
if(age > 18) {
  printf("Hi, %s, you are %d years old, so you are legel to come here!", name, age)
} else {
  printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!", name, age)
} 
  • 循环
    ·while do循环:Scala有while do循环,基本语义与Java相同。
var n = 10
while(n > 0) {
  println(n)
  n -= 1
}

·Scala没有for循环,只能使用while替代for循环,或者使用简易版的for语句
·简易版for语句:var n = 10; for(i <- 1 to n) println(i)
·或者使用until,表式不达到上限:for(i <- 1 until n) println(i)
·也可以对字符串进行遍历,类似于java的增强for循环,for(c <- “Hello World”) print(c)
·跳出循环语句
·scala没有提供类似于java的break语句。
·但是可以使用boolean类型变量、return或者Breaks的break函数来替代使用。

import scala.util.control.Breaks._
breakable {
    var n = 10
    for(c <- "Hello World") {
        if(n == 5) break;
        print(c)
        n -= 1
    }
}

image_1ap0htjbi1n8r1lda1jied2cuba3k.png-23.1kB

  • 高级for循环
    ·多重for循环:九九乘法表
for(i <- 1 to 9; j <- 1 to 9) {
  if(j == 9) {
    println(i * j)
  } else {
    print(i * j + " ")
  }
}

·if守卫:取偶数
for(i <- 1 to 100 if i % 2 == 0) println(i)

·for推导式:构造集合
for(i <- 1 to 10) yield i
image_1ap0i7tjidn9n0alr61kck17d241.png-24.4kB

函数

  • 函数的定义和调用
    在Scala中定义函数时,需要定义函数的函数名、参数、函数体。
def sayHello(name: String, age: Int) = {
  if (age > 18) { printf("hi %s, you are a big boy\n", name); age } 
  else { printf("hi %s, you are a little boy\n", name); age 
}

调用:sayHello(“leo”, 30)
Scala要求必须给出所有参数的类型,但是不一定给出函数返回值的类型,只要右侧的函数体中不包含递归的语句,Scala就可以自己根据右侧的表达式推断出返回类型。

  • 在代码块中定义包含多行语句的函数体
    单行的函数:def sayHello(name: String) = print(“Hello, ” + name)

如果函数体中有多行代码,则可以使用代码块的方式包裹多行代码,代码块中最后一行的返回值就是整个函数的返回值。与Java中不同,不是使用return返回值的。

比如如下的函数,实现累加的功能:

def sum(n: Int) = {
  var sum = 0;
  for(i <- 1 to n) sum += i
  sum
}
  • 递归函数与返回类型
    如果在函数体内递归调用函数自身,则必须手动给出函数的返回类型。

例如,实现经典的斐波那契数列:
9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5 + 5 + 4; ….

def fab(n: Int): Int = {
if(n <= 1) 1
else fab(n - 1) + fab(n - 2)
}

  • 默认参数和带名参数
    默认参数:
    在Scala中,有时我们调用某些函数时,不希望给出参数的具体值,而希望使用参数自身默认的值,此时就定义在定义函数时使用默认参数。

def sayHello(firstName: String, middleName: String = "William", lastName: String = "Croft") = firstName + " " + middleName + " " + lastName

如果给出的参数不够,则会从作往右依次应用参数。
带名参数:
在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。

sayHello(firstName = "Mick", lastName = "Nina", middleName = "Jack")

还可以混合使用未命名参数和带名参数,但是未命名参数必须排在带名参数前面。

sayHello("Mick", lastName = "Nina", middleName = "Jack")

  • 变长参数
    在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数。
def sum(nums: Int*) = {
  var res = 0
  for (num <- nums) res += num
  res
}

image_1ap0mv6qui851qme14o817g811ih4e.png-31.6kB
- 使用序列调用变长参数

在如果想要将一个已有的序列直接调用变长参数函数,是不对的。
比如val s = sum(1 to 5)
image_1ap0n9eug11jk10ni1mr6152718qb9.png-30.6kB
此时需要使用Scala特殊的语法将参数定义为序列,让Scala解释器能够识别。这种语法非常有用!
val s = sum(1 to 5: _*)
image_1ap0ndq621njk1ai61kdl1i4u18ckm.png-10.1kB
案例:使用递归函数实现累加

def sum2(nums: Int*): Int = {
  if (nums.length == 0) 0
  else nums.head + sum2(nums.tail: _*)
}
  • 过程
    在Scala中,定义函数时,如果函数体直接包裹在了花括号里面,而没有使用=连接,则函数的返回值类型就是Unit。这样的函数就被称之为过程。过程通常用于不需要返回值的函数。
    过程还有一种写法,就是将函数的返回值类型定义为Unit。
    def sayHello(name: String) { print("Hello, " + name); "Hello, " + name }
    def sayHello1(name: String): Unit = "Hello, " + name
  • Lazy值
    在Scala中,提供了lazy值的特性,也就是说,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算。这种特性对于特别耗时的计算操作特别有用,比如打开文件进行IO,进行网络IO等。
    import scala.io.Source._
    lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString

    即使文件不存在,也不会报错,只有第一个使用变量时会报错,证明了表达式计算的lazy特性。

数组

  • Array与ArrayBuffer
    在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组。例如字符串数组在底层就是Java的String[],整数数组在底层就是Java的Int[]。

// 数组初始化后,长度就固定下来了,而且元素全部根据其类型初始化
image_1ap0p5dkk57q133u7in1ug81rhl13.png-23.4kB
// 可以直接使用Array()创建数组,元素类型自动推断
image_1ap0p72fe1c461jmt1hvj1mu0qfg1g.png-34.2kB

在Scala中,如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer。
// 如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类
import scala.collection.mutable.ArrayBuffer
// 使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer
val b = ArrayBuffer[Int]()
image_1ap0pjetnk2d18kt1osc10aheum1t.png-19.6kB
// 使用+=操作符,可以添加一个元素,或者多个元素
b += 1
b += (2, 3, 4, 5)
image_1ap0pm3ns10jkcg71376v4m1ud42a.png-9.3kB
image_1ap0pmgkr1gfq1mom18pv16qo1v5o2n.png-13.3kB
// 使用++=操作符,可以添加其他集合中的所有元素
b ++= Array(6, 7, 8, 9, 10)
image_1ap0ppli81fvgm3bt681ti2gn934.png-33.3kB
// 使用trimEnd()函数,可以从尾部截断指定个数的元素
b.trimEnd(5)
image_1ap0pqoo2107f1sedjt16e9nu93h.png-27.3kB
// 使用insert()函数可以在指定位置插入元素
// 但是这种操作效率很低,因为需要移动指定位置后的所有元素
b.insert(5, 6)
b.insert(6, 7, 8, 9, 10)

// 使用remove()函数可以移除指定位置的元素
b.remove(1)
b.remove(1, 3)

// Array与ArrayBuffer可以互相进行转换
b.toArray
a.toBuffer

  • 遍历Array和ArrayBuffer
    使用for循环和until遍历Array / ArrayBuffer
    // until是RichInt提供的函数
    x
    for (i <- 0 until b.length)
    println(b(i))

    // 跳跃遍历Array / ArrayBuffer
    for(i <- 0 until (b.length, 2))
    println(b(i))

    image_1ap4iv0a0iqt94315o614ku18ut9.png-13.4kB
    // 从尾部遍历Array / ArrayBuffer
    for(i <- (0 until b.length).reverse)
    println(b(i))

    // 使用“增强for循环”遍历Array / ArrayBuffer
    for (e <- b)
    println(e)
    image_1ap4j34e61csl4dr1se11shr1g29m.png-10.1kB
  • 数组常见操作
    // 数组元素求和
    val a = Array(1, 2, 3, 4, 5)
    val sum = a.sum
    // 获取数组最大值
    val max = a.max
    image_1ap4jmdb1iod29q1psirq41raq13.png-37.5kB
    // 对数组进行排序
    scala.util.Sorting.quickSort(a)
    执行完后a会按照字典序进行排序
    // 获取数组中所有元素内容
    a.mkString
    a.mkString(“, “)
    a.mkString(“<”, “,”, “>”)
    // toString函数
    a.toString
    b.toString

数组转换

  • 使用yield和函数式编程转换数组
    // 对Array进行转换,获取的还是Array
    val a = Array(1, 2, 3, 4, 5)
    val a2 = for (ele <- a) yield ele * ele

    image_1ap4k0k6p1ien1k55gmr1lc05ei1g.png-30.9kB
    // 对ArrayBuffer进行转换,获取的还是ArrayBuffer
    val b = ArrayBuffer[Int]()
    b += (1, 2, 3, 4, 5)
    val b2 = for (ele <- b) yield ele * ele

    // 结合if守卫,仅转换需要的元素
    val a3 = for (ele <-a if ele % 2 == 0) yield ele * ele
    image_1ap4kbrht1snm35h776kii1kpe1t.png-29.9kB
    // 使用函数式编程转换数组(通常使用第一种方式)
    a.filter(_ % 2 == 0).map(2 * _)
    a.filter { _ % 2 == 0 } map { 2 * _ }

    image_1ap4keu4svmtrb01a4lj0s1k6m2a.png-14.3kB
    算法案例:移除第一个负数之后的所有负数
// 构建数组
val a = ArrayBuffer[Int]()
a += (1, 2, 3, 4, 5, -1, -3, -5, -9)
// 每发现一个第一个负数之后的负数,就进行移除,性能较差,多次移动数组
var foundFirstNegative = false
var arrayLength = a.length
var index = 0
while (index < arrayLength) {
  if (a(index) >= 0) {
    index += 1
  } else {
    if (!foundFirstNegative) { foundFirstNegative = true; index += 1 }
    else { a.remove(index); arrayLength -= 1 }
  }
} 

改良版

// 每记录所有不需要移除的元素的索引,稍后一次性移除所有需要移除的元素
// 性能较高,数组内的元素迁移只要执行一次即可
var foundFirstNegative = false
val keepIndexes = for (i <- 0 until a.length if !foundFirstNegative || a(i) >= 0) yield {
  if (a(i) < 0) foundFirstNegative = true
  i
}
//结果1
for (i <- 0 until keepIndexes.length) { a(i) = a(keepIndexes(i)) }
a.trimEnd(a.length - keepIndexes.length)
//结果2

结果1:输出的是不需要移除的数组元素的索引
image_1ap51d2sdhlicokvgbe661she34.png-23kB


Map与Tuple

  • Map

创建Map

// 创建一个不可变的Map
val ages = Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
ages("Leo") = 31
// 创建一个可变的Map
val ages = scala.collection.mutable.Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
ages("Leo") = 31

// 使用另外一种方式定义Map元素
val ages = Map(("Leo", 30), ("Jen", 25), ("Jack", 23))
// 创建一个空的HashMap
val ages = new scala.collection.mutable.HashMap[String, Int]

访问Map

// 获取指定key对应的value,如果key不存在,会报错
val leoAge = ages("Leo")
val leoAge = ages("leo")

// 使用contains函数检查key是否存在
val leoAge = if (ages.contains("leo")) ages("leo") else 0
// getOrElse函数
val leoAge = ages.getOrElse("leo", 0)//该函数优雅简洁

修改Map

// 更新Map的元素(可变)
ages("Leo") = 31
// 增加多个元素
ages += ("Mike" -> 35, "Tom" -> 40)
// 移除元素
ages -= "Mike"
// 更新不可变的map
val ages2 = ages + ("Mike" -> 36, "Tom" -> 40)
// 移除不可变map的元素
val ages3 = ages - "Tom"

遍历Map

// 遍历map的entrySet
for ((key, value) <- ages) println(key + " " + value)
// 遍历map的key
for (key <- ages.keySet) println(key)
// 遍历map的value
for (value <- ages.values) println(value)
// 生成新map,反转key和value
for ((key, value) <- ages) yield (value, key)

特殊Map

// SortedMap可以自动对Map的key的排序
val ages = scala.collection.immutable.SortedMap("leo" -> 30, "alice" -> 15, "jen" -> 25)

// LinkedHashMap可以记住插入entry的顺序
val ages = new scala.collection.mutable.LinkedHashMap[String, Int]

Tuple

// Tuple是一个元组,但不一定是两个,可以是多个
val t = (“leo”, 30)
image_1ap53nh5iqhk8ta1r239o71s013h.png-25.2kB
// 访问Tuple
t._1

// zip操作
val names = Array(“leo”, “jack”, “mike”)
val ages = Array(30, 24, 26)
val nameAges = names.zip(ages)
for ((name, age) <- nameAges) println(name + “: ” + age)
image_1ap53vjha1me7jld17ld1m381nm43u.png-23.2kB

面对对象编程之–类

1.定义一个简单的类

// 定义类,包含field以及方法

class HelloWorld {
   
  private var name = "leo"
  def sayHello() { print("Hello, " + name) }  
  def getName = name
}

// 创建类的对象,并调用其方法
val helloWorld = new HelloWorld
helloWorld.sayHello()
print(helloWorld.getName)
//也可以不加括号,如果定义方法时不带括号,则调用方法时也不能带括号

<
  • 9
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值