Scala基础语法详解

Scala官网入口

程序下载:
https://www.scala-lang.org/download/.
文档查看:
https://www.scala-lang.org/api/current/?_ga=1.178639076.1310790544.1468501313.

Scala简介


Scala 是一门多范式(multi-paradigm)的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性.Scala 运行在Java虚拟机上,并兼容现有的Java程序.Scala 源代码被编译成Java字节码,所以它可以运行于JVM之上,并可以调用现有的Java类库。

Scala语言具有以下特性:

面向对象 — Scala基于Java设计,但是与Java不同的是,Scala是一种完全面向对象的语言,每个值都是对象,对象的数据类型以及行为由类和特质描述。而Java借鉴了C的思想,所以Java的语法还是留有面向过程的东西,例如static、void、null、基本数据类型等。Scala的设计者Martin Odersky通过其他方式让Scala实现了纯面向对象的语法。

函数式编程 — Scala是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型。更进一步,程序员可以利用Scala的模式匹配,编写类似正则表达式的代码处理XML数据。

静态类型 — Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性。

扩展性 — Scala的设计秉承一项事实,即在实践中,某个领域特定的应用程序开发往往需要特定于该领域的语言扩展。Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构:
1.任何方法可用作前缀或后缀操作符
2.可以根据预期类型自动构造闭包。

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

Scala语法


一、简单的程序执行

下面通过两个简单的程序快速了解Scala的执行过程。Scala程序的执行入口是main方法,方法的声明使用关键字def,声明部分与方法体通过=连接,方法的返回值类型放置在方法的声明后面,通过:连接。

(一)输出到控制台

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

上面代码中object用于声明伴生对象Unit类替换了Java中的void关键字,在上面表示返回值类型。前面说过Scala是完全面向对象的语言,为了替代static这类静态语法在java中的作用,Scala使用伴生对象进行模拟,伴生对象中的内容都可以通过类名访问。
Scala的编译器将上面的代码编译产生两个.class文件:

  • Scala01_HelloScala.class
  • Scala01_HelloScala$.class

而println()方法和java中的System.out.println()一样,最终都是调用PrintStream类中的方法。此外,为了让代码尽可能简洁,Scala的默认的访问权限都是public

(二)从命令行输入

print("please input:")
val line: String = StdIn.readLine()
println("line = " + line)

结果:

please input:test Scala input
line = test Scala input

二、变量的使用

(一) 变量声明

// 变量声明格式:
// var|val 变量名称 [:变量类型] = 变量的值
var name: String = "Bessen"
val age: String = "80"
val +-*/ = "123"
val `val` = 123

注意:

  1. Scala声明变量必须显式的初始化。
  2. 使用var声明的变量的值是可以修改的,而使用val声明的变量无法修改(指变量引用的内存地址不能修改)。
  3. 声明变量时类型可以省略。
  4. Scala中的变量可以用特殊符号进行命名。另外,在Java中变量可以命名为_,但是在Scala中_有很多特殊用法,因此不能用于单独作为变量名。

(二)Scala的主要数据类型

在这里插入图片描述

Scala没有基本数据类型,其数据类型分为两大类,数值类AnyVal和引用类AnyRef,各类型之间的关系如上图所示。Unit相当于void,Null可以赋值给引用类,Nothing可以赋值给任何其他类型。

变量类型说明变量类型说明
Byte1个字节List不可变
Short2个字节Array基于数组
Int4个字节ListBuffer可变
Long8个字节Set无序,不允许有重复元素
Char2个字节,无符号Tuple元组,最多22个元素
Float8个字节,单精度浮点型Range范围
Double16个字节,双精度浮点型Map映射
Boolean布尔类型Queue队列,可变

(三)String类型操作

除了Java中支持的字符串操作外,Scala还支持String Interpolation,即字符串插值,包括三种实现方式:sfraw。在字符串前加上s之后可以在字符串内通过${}引用变量,类似于jQuery;加上f可以对引用的变量进行格式化;加上raw表示不执行转义字符,使其保持原样,测试代码如下。

//变量声明
val name = "Bessen"
val age = 80
val url = "www.bessen.com"

println(s"name=${name}, age=$age, url=$url")
println(f"name=${name}, age=${age}%.2f, url=$url")
print(raw"name=${name}, age=${age}%.2f, url=$url \n")

输出结果:

name=Bessen, age=80, url=www.bessen.com
name=Bessen, age=80.00, url=www.bessen.com
name=Bessen, age=80%.2f, url=www.bessen.com \n

三、运算符

  1. Scala中没有++--运算符,只有+=-=.
  2. Scala不支持三目运算符,但是Scala的所有表达式都有返回值,包括if else表达式,其返回值取决于代码的最后一行,因此可以方便地用if else实现三目运算符.
  3. Scala中的==等价于equals方法
val flg = true

//true:Unit    false:"in false"
val value: Any = if (flg) {
    "in true"
    println("true")
} else {
    "in false"
}
println(value)

输出结果

true
()

四、for循环用法

  1. 输出[1,5]的值
for (i <- 1 to 5) {
    print(s"i = ${i} \t")
}
  1. 输出[1,5)的值
for (i <- 1 until 5) {
    print(s"i = ${i} \t")
}
  1. 用Range控制步长
for (i <- Range(0, 5, 2)) {
    print(s"i = ${i} \t") 
}
//输出:i = 0 	i = 2 	i = 4
  1. 循环守卫,也称条件判断式。判断式为true则进入循环体内部,为false则跳过,类似于continue
for (i <- 1 to 5 if i % 2 == 0) {
	println(s"${i}")
}
  1. 表达式如果有多行代码,那么可以采用大括号声明
for (i <- Range(1, 5, 2); j = (5 - i) / 2) {
	println(" " * j + "*" * i + " " * j)
}
//等价于:
for {i <- Range(1, 5, 2)
	j = (5 - i) / 2} {
	println(" " * j + "*" * i + " " * j)
}
  1. for循环返回值

默认情况下,for循环的返回值为Unit,但是通过yield关键字,可以将每次循环的结果放到一个集合中。

val set: immutable.IndexedSeq[Int] = for (i <- 1 to 5) yield i
println(set)
//输出:Vector(1,2,3,4,5)
  1. break

Scala中没有continuebreak关键字,break的功能用Breaks类替换。

注意:这里breakable方法中直接将一段代码作为参数,这在Scala中称为控制抽象。

Breaks.breakable {
	for (i <- 1 to 10) {
        if (i == 5) {
			Breaks.break()
		}
		println(s"i = ${i}")
	}
}

五、函数式编程

Scala既是完全面向对象的语言,同时也是一种完全面向函数式编程的语言。函数在Scala中可以做任何事情,包括将函数赋值给变量,将函数可以作为函数的参数,甚至将一个函数作为返回值。函数式编程关心的重点在于将功能进行封装,通过调用函数解决问题。

(一)函数的声明

  • Scala中的fuction不同于Java中的method,Java的方法必须依赖于对象,而Scala中的函数可以独立声明。
  • Scala的语法非常灵活,可以在任意语法中声明其他语法规则,例如在函数中声明其他函数。
  • Scala函数没有重载的概念,因此在同一个作用域中函数不能同名。
def test(s: String): Unit = {
	println(s)
}

(二)返回值自动推断

在不写函数返回值的情况下,Scala可以通过函数最后一行代码自动推断函数的返回值类型(如果没有则返回Unit);当函数体中只有一行代码时,大括号可以省略;当函数没有参数列表,声明函数时小括号()可以省略(但是在调用函数时不能有小括号),例如:

def test(): String = {
	//return "Bessen"
	"Bessen"
}
//等同于
def test() = {
	"Bessen"
}
//等同于
def test() = "Bessen"
//等同于
def test = "Bessen"

注意:当省略函数的=和返回值类型时,相当于明确表示该函数没有返回值,自动推断功能不再起作用。

(三)参数列表

  1. 可变参数—需要写到形参列表的最后
def test(name: String*): Unit = {
	println(name)
}
  1. 默认参数—如果希望函数中某一个参数使用默认值,那么可以在声明时直接赋初始值,如果在调用时指定了实参,则实参会覆盖默认值。
def test(name: String, age: Int = 20): Unit = {
	println(s"${name} - ${age}")
}

(四)函数高级用法

  1. 将函数f1()的返回值作为另一个函数f2()的返回值

下面的代码将会输出in f1,由于f1是无参的函数,因此f1等效于f1()f1的返回值为Unitf2的返回值也是Unit,执行f1的过程中输出了in f1.

def f1() : Unit = {
	print("in f1")
}
def f2() = {f1}
f2()
  1. 将函数f1()本身作为另一个函数f2()的返回值

f1后面加上特殊符号_表示返回的是函数本身,下面的代码没有输出结果

def f1() : Unit = {
	print("in f1")
}
def f2() = {f1 _}
f2()
  1. 闭包

一个函数在实现逻辑时,将外部的变量引入到函数内,改变了这个变量的生命周期,称之为闭包。

def f1(i: Int) = {
	def f2(j: Int): Int = {
		i * j
	}
	f2 _
}
println(f1(2)(3)) //6
  1. 函数柯里化(Currying

柯里化指的是将原来接受多个参数的函数变成新的接受一个参数的函数的过程。新函数的参数接受原来的第二个参数为唯一参数,如果有n个参数,就是把这个函数分解成n个新的函数。
柯里化就是以函数为主体这种思想发展的必然产生的结果。
柯里化形式:集合.函数(函数).函数(函数).函数(函数)

在Scala中,函数柯里化具有非常重要的意义,这是因为Scala中有一个隐式转换的语法(后面会细讲),该语法只能在函数只有一个参数时起作用,通过函数柯里化后正好满足这一条件。

//通过函数柯里化,前面的代码可以写成:
def f3(i: Int)(j: Int): Int = {
	i * j
}
println(f3(2)(3))
  1. 将函数作为参数

方式:函数名: 参数列表 => 返回值

def f4(f: () => Int): Int = {
	f() + 10
}
def f5(): Int = {
	5
}
println(f4(f5)) // 15
  1. 匿名函数

匿名函数在很多语言中都被大量使用,有着非常重要的作用。

def f4(f: (i: Int) => Int): Int = {
	f(5) + 10
}
println(f4((i) => {i + 5})) // 20
//简化:
println(f4( _ + 5 )) // 20
  1. 惰性函数(延迟加载)

为了节约内存,我们有时候希望只在用到某个变量时才执行赋值操作,同时不希望改变原有的代码逻辑,这时可以使用lazy关键字声明惰性函数。当变量或函数返回值被声明为lazy时,该变量或函数的执行将被推迟,直到我们首次对其取值时才会执行。
注意: lazy不能修饰 var 类型的变量

def f1(i: Int) : Int = {
	println("in f1")
	i*i
}
lazy val a = f1(2)
println("-----------")
println(s"a = ${a}")

输出结果

-----------
in f1
a = 4

六、异常处理

Scala中没有throws关键字抛出异常,对异常的处理可以通过Break对象和try catch块抓取。catch子句内可以使用case匹配多个异常(模式匹配在后面细讲),由于匹配是按次序从上到下依次进行,因此,因当把越具体的异常写在前面。

try {
	val i = 10 / 0
} catch {
	case ex: ArithmeticException => println("捕获了除数为零的算术异常")
	case ex: Exception => println("捕获了异常")
	} finally {
	println("finally...")
}

七、Scala的面向对象语法

(一)class

先看具体实例,简单声明一个Student类,并创建对象,调用属性和方法。

object TestClass {

    def main(args: Array[String]): Unit = {

        // 创建类的对象
        val user: Student = new Student();

        // 调用对象的属性或方法
        user.name = "zhangsan"
        println(user.name)
    }
}

// 声明类
class Student {

  var name: String = _
  var age: Int = _

  def login(): Boolean = {
    true
  }
}

除了函数的返回值类型可以自动推断外,变量的类型也可以根据初始化值自动推断。由于在Scala中声明变量必须显式地初始化,因此class的成员变量也需要赋初值,如果赋值为null,则一定要加类型,因为不加类型, 那么该变量的类型就是Null类型。如果在定义时暂时不赋值,可以使用特殊符号下划线_,让系统分配默认值。

1.成员变量

Scala类中声明的成员变量可以用_初始化,编译器会默认赋值。
成员变量的访问权限看似为public,其实在编译器底层是private,Scala自动生成了公共的的getter和setter方法,例如:

class Student {
	// 不加权限修饰符,生成公共的setter和getter方法,因此任何地方都可以访问
	var username: String = _

	// 如果给成员变量增加private修饰符,那么成员变量无法在外部访问,因为底层生成的setter和getter方法都是私有的
	private var age: Int = _

	// 如果声明的成员变量使用val,那么在底层成员变量是私有的,并且使用final修饰,底层只提供公共的getter方法,而没有setter方法
	val email: String = "my@163.com"
	
	// 在成员变量前添加@BeanProperty装饰时,会自动生成规范的setXxx/getXxx方法,这时外部可以使用对象.setXxx()和对象.getXxx()来调用属性
	@BeanProperty var address: String = _
}
object TestField {

	def main(args: Array[String]): Unit = {

		val user = new Student
		println(user.username)
		user.username_=("zhangsan")//底层的getter方法
		//user.setAddress()
		user.getAddress
	}
}
2.构造方法

由于Scala是完全面向函数式编程的语言,即一切都是函数,所以Scala的类也是函数,于是有了下面的语法:

class A{
}

相当于

class A(){
}

即Scala的类本身就是构造方法,称之为主构造方法。类体中的语句相当于构造方法中的语句,可以在创建对象时直接执行!
除了主构造方法外,Scala还可以定义多个函数名为this辅助构造方法,辅助构造方法必须在第一行调用主构造方法,且构造方法考虑顺序。具体实例如下:

//如果主构造方法无参数,小括号可以省略,添加private表示将主构造方法私有化
class Test private(s: String) { // 主构造方法

    println("在主构造方法")
    println(s)

    def this(s: String, ss: String) { 
        this(s) // 直接调用主构造器
        println("辅助构造方法2")
    }
    
    def this() {
        this("辅助构造方法1", "xxxx") 
    }
}

注意:如果主构造方法传入的参数用varval进行了声明,相当于把这个参数同时变成了类的属性。

3.伴生类、伴生对象和单例模式

当一个源码文件中有两个同名的类,分别用class和object声明时,称他们为伴生类和伴生对象。Scala的编译器会将两个类整合成同一个类,并且伴生对象是静态的。伴生对象的常见用法是在其中定义apply方法,从而实现单例模式的设计思想,例如Scala中的Range就是这样实现的。

//伴生类
class Student {
    private var sname = _
}
//伴生对象
object Student {

    // Scala自动识别apply方法,用于创建伴生类对象
    def apply(s: String): Student = new Student()
}
object Test {
    def main(args: Array[String]): Unit = {
    
        // 采用伴生对象来创建伴生类,相当于Student.apply("Bessen")
        val student = Student("Bessen")
    }
}
4.类的其他操作
  1. 判断类别—判断user对象是不是User类
user.isInstanceOf[User]
  1. 类别转化—将user对象强制进行类别转化
val user2: User2 = user.asInstanceOf[User2]
  1. 获取类信息—获取User类的信息
val userClassInfo: Class[User] = classOf[User]
userClassInfo.getInterfaces
  1. 类的别名—给User类取别名NewUser
type NewUser = User
  1. 继承App类
    App类存在于Scala包中,当一个类继承了App类后,可以直接运行类体中的代码,而不再需要编写main方法作为程序的入口

(二)package

Scala的发明者认为,在Java中package的作用只是对类进行区分,意义不大,因此,他将Scala中的package关键字进行了丰富,具体包括以下几点:

  1. 在同一个源码文件中,可以多次声明package,声明的类在最后的那个包中。
  2. 源码中的类所在的位置不需要和包路径相同。
  3. package后面可以跟随大括号,大括号内声明的类在这个包中,子包中的类可以直接访问父包的类,不需要引入。
  4. Scala中存在package object的概念,可以在其中声明属性和方法。
  5. 包名可以使用相对路径也可以用绝对路径,一般使用相对路径。

具体实例:

package package1
package test {
	class Emp {
	}
	package object test{
		val test = "123"
		def test1() {
		}
	}
	package test1 {
		object Scala03_Package {
			def main(args: Array[String]): Unit = {
				val emp = new Emp()
				println(emp)
			}
		}
	}
}

(三)import

Scala的导入功能非常强大,具有以下特点:

  1. Scala会自动引入的常用的包:java.lang.* , scala包 ,Predef包
  2. import可以在任意的地方使用,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率。
  3. import可以导入一个包中的所有的类,采用下划线_代替星号*
import java.util._
  1. import 导入相同包中的多个类,采用大括号进行包含处理
import java.util.{ArrayList, List, Date}
  1. import 可以采用特殊的方式来隐藏指定的类:{类名 => _}
import java.util.{Date => _}
  1. Java中的import只是用来导类的,并不能真的导包,而Scala可以真正实现导包的功能,如import java.util,使用类时采用new util.Date()这种形式
  2. scala可以在导入类的时候给类起别名
import java.util.{HashMap=>JavaHashMap}
  1. scala中如果想要从最开始的包中查找类,需要增加绝对路径,使用_root_开头
import _root_.java.util._

八、Scala中的访问权限

Scala的设计者发现Java中的访问权限容易用错,概念不够清晰,因此,他将Scala中的访问权限进行了更严格的限定,访问的方式分成以下几类:

  • 默认权限:默认不加权限为public,但Scala没有public关键字
  • 包访问权限:在前面声明private[包名]
  • 子类访问权限protected,只能子类访问,同包也不能
  • 类访问权限private

九、继承

抽象类
使用abstract关键字,用法与Java基本相同
继承
继承使用extends关键字,子类可以用super关键字调用父类。
子类在继承父类的同时会继承父类的构造方法,在创建子类对象时,构造方法的执行顺序相当于在子类的主构造方法第一行调用父类的主构造方法。如果父类的主构造方法有参数,在声明extends时要同时显式地声明参数,例如:

class Person(s: String) {
    println("Person 主构造方法 = " + s)
}
class User(s: String) extends Person11(s) {

    println("User 主构造方法")

    def this() {
        this("XXXXX")
        println("User 辅助构造方法")
    }
}
object Scala11_Class1 {
    def main(args: Array[String]): Unit = {
        val user = new User()
    }
}

输出结果:

Person 主构造方法 = XXXXX
User 主构造方法
User 辅助构造方法

重写
Scala重写方法,需要添加override关键字,类似于Java的@override

注意:

  1. Scala的抽象类中可以有抽象属性(只有声明而没有初始化的属性),这是因为Scala中属性也是函数!!!
  2. 根据前面一点可以得出,重写非抽象的属性需要加override关键字
  3. 不可以重写var声明的非抽象属性

十、特质(Trait)

Scala中没有接口(Interface)的概念,取而代之的是特质(Trait),特质默认是抽象类,可以被继承下去,因此使用的关键字是extends,通过with关键字,一个类可以连接/混入多个Trait,从而实现了多继承!
Trait具有以下特点:

  1. 可以直接在类体中直接添加代码
  2. 可以直接在类体中添加属性和方法(包括抽象的和非抽象)
  3. 特质和父类没有关系,只和当前混入的类有关系,所以,在调用时,父类先执行,然后当前混入的特质再执行,然后当前类再执行, 如果父类混入了相同的特质,那么特质的代码只会执行一遍。
trait Trait1 {
    println("trait code...")
}

class Person1 extends Trait1 {
    println("parent code...")
}

class User1 extends Person1 with Trait1 {
    println("child code...")
}

object TestTrait {
    def main(args: Array[String]): Unit = {
        new User1()
    }
}

输出结果:

trait code...
parent code...
child code...
  1. 和Java的接口类似,特质之间可以相互继承,但与Java不同的是,特质还可以直接继承一个类(例如继承一个异常类)!
  2. Scala可以直接将Java中的接口当作Trait使用
  3. 当一个类混入了多个Trait时,按照混入的顺序从左到右执行Trait内的代码,调用继承自Trait的方法时,则从右到左执行。。。
  4. 通过在Trait的类体中添加this: 类名 =>可以指定该特质的使用范围
  5. 在特质中也可以像继承一样使用super关键字,但是特质中的super不是指代父特质,而是指代上一级特质(上下级按照子类混入特质的顺序排序),如果要指代某一个特质,可以在super后面使用[]指定,例如super[特质名]
trait Operate {

    println("Operate...")

    def insert() {
        println("插入数据")
    }
}

trait DB extends Operate {

    println("DB...")

    override def insert() {
        print("向数据库")
        super.insert()
    }
}

trait File extends Operate {

    println("File...")

    override def insert() {
        print("向文件")
        super.insert()
        super[Operate].insert()
    }
}

class MySql extends DB with File with Serializable {
}

object TestTrait {
    def main(args: Array[String]): Unit = {
        val mysql = new MySql()
        mysql.insert()
    }
}

输出结果:

Operate...
DB...
File...
向文件向数据库插入数据
插入数据

Trait动态混入

根据开放封闭原则,当需要在原来的代码中增加新的功能时,不允许修改原来的代码,即不能更改之前的类。为此,Scala引入了动态混入的语法,可以在创建对象时动态地添加新的Trait,例如:

object TestTrait {

    def main(args: Array[String]): Unit = {
    
        val mysql = new MySql() with Operate
        mysql.insert()
    }
}

trait Operate {

    def insert() {
        println("插入数据")
    }
}

class MySql {
}

十一、类型转换

(一)自动转换

自动转换包括数值类型的自动转换,即byte -> short -> int -> long等,以及多态。

(二)强制类型转换

Scala的强制类型转换通过调用对象的类型转换方法即可,如1.0.toInt

(三)自定义转换规则—隐式转换

Scala也允许开发人员自定义类型转换规则,通过在自定义的转换方法前添加关键字implicit使其生效。
转换规则生效的前提包括:

  • 不能存在二义性
  • 隐式操作不能嵌套使用
implicit def transform(d: Double): {
    Int = d.toInt
}
  1. 隐式参数
    将某个形参变量标记为implicit,编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为默认参数,调用方法时不能加(),否则隐式值无法传递。
//隐式值
implicit val myname: String = "lisi"

// 隐式参数
def test(implicit name: String = "zhangsan"): Unit = {
    println("hello" + name)

test
  1. 隐式类
    通过在非顶级类且非样例类的前面添加关键字implicit,可以将一个类转换成隐式类,该隐式类的构造参数有且只能有一个,用于指定被包装的class。被包装的类将获得隐式类的内容,很好地体现了开闭原则。下面案例将User类进行包装,通过implicit为其添加Person类的方法。
object TestImplicit {

    def main(args: Array[String]): Unit = {

        val user4 = new User4()
        user4.insert()
        user4.delete()
    }

    implicit class Person(u: User4) {
        def delete() {
        }
    }

    class User4 {
        def insert() {
        }
    }
}

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

十二、序列、集合、映射(重点)

Scala中的两类集合所在包

  • 不可变集合包名:scala.collection.immutable
  • 可变集合包名: scala.collection.mutable

Scala集合继承关系

(一)Array

1.不可变数组Array

Scala中的Array就是与Java中的数组数据结构类似,但是用法有较大区别,下面为Array的具体使用案例:

//创建数组
val ints: Array[Int] = Array(1, 2, 3, 4)
val arr: Array[Int] = new Array[Int](10) //容量为10的一维数组
val arr1: Array[Int] = new Array[Int](3,4) //二维数组

//更新数组元素
ints(0) = 2
ints.update(1, 5)

// 转换成字符串
println(arr.mkString(","))
// 访问数组元素
println(ints(0))
// 数组长度
println(ints.length)
    
// 遍历数组
for (elem <- ints) {
    println(elem)
}
//传入匿名函数作为参数遍历数组
ints.foreach(println)

// 增加元素返回新的数组
// :+ 在集合最右边新增元素
// +: 在集合最左边新增元素
val ints1: Array[Int] = ints :+ (5) //或者 ints :+ 5
val ints2: Array[Int] = 5 +: (ints)

//转换为可变数组
val buffer: mutable.Buffer[Int] = ints.toBuffer
2.可变数组ArrayBuffer
//创建数组
val arrayBuffer: ArrayBuffer[Int] = ArrayBuffer(5, 6, 7, 8)
val arr2 = ArrayBuffer[Int]() 

// 访问数组元素值
println(arrayBuffer(0))

//转换成字符串
println(arrayBuffer.mkString(","))

//更新数组元素
arrayBuffer(0) = 9
arrayBuffer.insert(1, 9)

//添加元素
arrayBuffer.append(9)
val buffer: ArrayBuffer[Int] = arrayBuffer += (9)

//删除数组元素
arrayBuffer -= (1)
arrayBuffer.remove(1)// 删除指定位置数据
arrayBuffer.remove(1, 2)// 删除指定位置开始n个数据

// 传入匿名函数遍历数组
arrayBuffer.foreach(println)

//转变为不可变数组
val array: Array[Int] = arrayBuffer.toArray

(二)序列Seq

1.不可变List
//创建列表
val list: List[Int] = List(1, 2, 3, 4)

// 访问列表元素
println(list(0))
//转换成字符串    
println(list1.mkString(","))

// 增加数据( :+ 在集合最后面添加元素, +: 在集合最前面添加元素)
val list1: List[Int] = list :+ 1
val list2: List[Int] = 1 +: list

// List连接
val list3: List[Int] = list ++ list1
// 通过连接添加元素
val list4: List[Int] = 7 :: 8 :: 9 :: list
val list5 = 9 :: list1 ::: list//注意区别
println(1 :: 2 :: 3 :: Nil)//Nil表示空List

//更新List
val list6: List[Int] = list.updated(2, 5)

// 删除前n个数据
val list7: List[Int] = list.drop(2)


2.可变ListBuffer

除了支持前面的操作外,ListBuffer还具有以下方法

//创建ListBuffer
var listBuffer = ListBuffer(1, 2, 3, 4)

// 获取头元素
println(listBuffer.head) // 1

// 尾(除了头以外)
println(listBuffer.tail) // ListBuffer(2, 3, 4)

// 最后一个
println(listBuffer.last) // 4

// 初始化(除了最后一个)
println(mlist.init)
3.List常用方法
val list = List(1, 2, 4, 3, 1, 3)

println("sum = " + list.sum) //所有元素求和     
println("max = " + list.max) // 最大元素
println("min = " + list.min) //最小元素
println("product = " + list.product) //所有元素乘积
println("reverse = " + list.reverse) //顺序反转

// take函数,获取列表前n个元素
println(list.take(3))

// groupBy分组函数,根据传入函数的返回值对List进行分组
val intToInts: Map[Int, List[Int]] = list.groupBy(i => i % 2)
intToInts.foreach(t => {println(t._1 + "=" + t._2)}) // 遍历分组后的List
/* 结果:
1 = List(1, 3, 1, 3)
0 = List(2, 4)*/

// sortBy函数(根据返回值大小排序)
val sortList: List[Int] = list.sortBy(x => x)

// sortWith函数,自定义排序规则
val ints: List[Int] = list.sortWith((x, y) => {x < y})

// iterator迭代器
for (elem <- list.iterator) {
    println(elem)
}

// map函数映射
val mapList: List[(Int, Int)] = list.map(x => (x, 1))
println(mapList.mkString(","))

// flatMap函数扁平化操作
val lineList = List("Hello World", "Hello Scala", "Hello Hadoop")
val flatMapList: List[String] = lineList.flatMap(x => x.split(" "))
println(flatMapList.mkString(","))
// 输出:List("Hello","World","Hello","Scala","Hello","Hadoop")

// filter过滤,true保留,false不保留
val filterList: List[Int] = list.filter(x => x % 2 == 0)

// 拉链:Zip,将两个集合数据进行关联,形成了元组列表
val list1 = List(1, 2, 3, 7)
val list2 = List(3, 4, 5, 6)
val tuples: List[(Int, Int)] = list1.zip(list2)

// 集合并集
val unionList: List[Int] = list1.union(list2)

// 集合交集
val intersectList: List[Int] = list1.intersect(list2)

// 集合差集
val diffList: List[Int] = list1.diff(list2)
4.List归约和折叠
  1. 归约
    List的reduce方法可以实现对列表的简化,将一组数据处理返回一个结果,实现归约/聚合的效果。
val list = List(1, 2, 3, 4)

//实现(1,2,3,4) => (((1-2)-3)-4) 
val result: Int = list.reduceLeft(_ - _)

//实现(1,2,3,4) => (1-(2-(3-4))) 
val result: Int = list.reduceRight(_ - _)
  1. 聚合
    List的fold方法实现聚合功能,和reduce的区别是fold需要传入一个列表外的变量。
val list = List(1, 2, 3, 4)

//实现(1,2,3,4) => ((((10-1)-2)-3)-4) 
val result: Int = list.foldLeft(10)(_ - _)

//实现(1,2,3,4) => (1-(2-(3-(4-10)))) 
val result: Int = list.foldRight(10)(_ - _)

(三)队列Queue(可变)

//创建队列
val q = mutable.Queue(1, 2, 3, 4)

//访问队列元素
println(q.head) // 1
println(q.last) // 4
println(q.tail) // Queue(2,3,4)
println(q.tail.tail) // Queue(3,4)

//入列
q.enqueue(5)

//出列
q.dequeue()

其他队列

  • 双端队列
  • 阻塞队列

(四)集合Set

集合的特点是内部元素无序且不可重复,Scala的immutablemutable下都有Set类,在创建对象时,如果不指定包名,默认使用的是immutable包下的Set。下面是两种Set的使用实例。

// 创建Set对象
val set: Set[Int] = Set(1, 2, 3, 4)// 默认immutable
val mset = mutable.Set(1, 2, 3, 4)//可变的Set对象

// 增加数据
println(set + 11)

// 删除数据
println(set - 3)

// 遍历集合
for (elem <- set) {
    println(elem)
}

// 可变集合的元素添加
mset.add(4)
mset += 6
mset.+=(6)

// 可变集合的元素删除
mset -= 2
mset.-=(4)
mset.remove(6)

(五)元组Tuple

元组在很多语言中都有使用,表示的是多个元素的集合,有点类似于Set,但是元组是有序的,而且Scala中的元组有数目限制,一个元组最多包含22个元素。

//元组的创建
val tuple: (String, Int, String) = ("zhangsan", 22, "男")

// 访问元组中的元素
println(tuple._1)
println(tuple.productElement(0))

// 遍历元祖
for (elem <- tuple.productIterator) {
    println(elem)
}

(六)映射Map

和Set一样,Scala的Map也有同名的可变Map不可变Map,在不指定包名时创建的是不可变Map对象。

  • 不可变Map使用:
//创建Map对象
val map: Map[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)

//转换为字符串
println(map.mkString(","))

// 增加数据
// 方式1-增加单个元素
val map1: Map[String, Int] = map + ("d" -> 4)
val map11: Map[String, Int] = map + ("a" -> 6) // key相同则覆盖

// 方式2-增加多个元素
val map12 = mutable.Map(("A", 1), ("B", "北京"), ("C", 3))
val map13 = map12 + ("E" -> 1, "F" -> 3)
map12 += ("EE" -> 1, "FF" -> 3)

// 根据键删除数据
val map2: Map[String, Int] = map - ("b")
val map3: Map[String, Int] = map - ("e") // 删除一个不存在key,不会报错
val map33: Map[String, Int] = map - ("a", "b") // 删除多个元素

// 修改数据
val map4: Map[String, Int] = map.updated("b", 5)

// 访问数据
// 方式1-直接访问
val i = map("a") // 如果key不存在,会抛出异常

// 方式2-先使用contains方法检查是否存在key,防止异常
val map5 = mutable.Map(("A", 1), ("B", 2), ("C", 3), ("D", 30.9))
if (map5.contains("B")) {
    println("key存在,值= " + map5("B"))
} else {
    println("key不存在")
}

// 方式3-使用map.get(key).get取值,map.get(key)返回一个Option,如果是Some表示有值,None表示无值,None调用get将报错
println(map.get("b")) // Some(2)
println(map.get("b").get) // 2
// println(map.get("f").get) // 报错

// 方式4-使用map4.getOrElse(默认值)key不存在则返回默认值
println(map.get("f").getOrElse(0))
println(map.get("c").getOrElse(0))

// 遍历
val tupleMap: Map[Int, String] = Map((1, "zhangsan"))
tupleMap.foreach(t => {
    println(t._1 + "=" + t._2)
})
  • 可变Map使用:
// 创建可变Map对象
val mmap: mutable.Map[String, Int] = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)

// 创建空的映射
val mapZero = new scala.collection.mutable.HashMap[String, Int]

//添加k,v
mmap.+("d" -> 4)

//更新k,v
mmap.update("d", 5)
mmap("a") = 6 // 如果key不存在,等价于添加一个key-val

//删除k,v
mmap.remove("d")

//遍历映射(每个键值对是一个元组)
for ((k, v) <- mmap) println(k + " -> " + v)
for (k <- mmap.keys) println(k)
for (v <- mmap.values) println(v)
for (t <- mmap) println(t)

十三、IO流


十四、并发与并行

  • 并发:单核同时执行多个线程
  • 并行:多核分别执行线程
// 在main线程执行任务
val result1 = (0 to 100).map { case _ => Thread.currentThread.getName }

// par表示从线程池中拿新的线程执行
val result2 = (0 to 100).par.map { case _ => Thread.currentThread.getName }

十五、模式匹配

Scala中的match类似于Java中的switch,match可以实现对值、类型甚至规则的匹配,自上而下依次匹配case后的条件,一旦匹配成功,马上执行对应的代码,然后退出match,不再继续匹配。需要注意的是:match不能区分泛型。
守护

val oper = '*'
val n1 = 20
val n2 = 10
var res = 0
oper match {
    case '+' => res = n1 + n2
    case '-' => res = n1 - n2
    case '*' => res = n1 * n2
    case if oper == '/' => res = n1 / n2
    case _ => println("oper error") // 用 _ 表示默认
}
println("res=" + res)

匹配规则:

for (arr <- Array(Array(0, 1), List(0, 2), Array(1, 0, 1), List(3), Array(1))) {
    val result = arr match {
    case Array(1, 0, _*) => "这是个以1,0开头的数组"
    case Array(0, 1) => "这是个Array(0,1)"
    case 3 :: Nil => "这是3加空集合的集合"
    case 4 :: tail => "这是个4开头的集合"
    case x :: y => "这是个两个元素的集合"
    case _ => "不是我想要的"
    }
    println(s"result =${result}")
}

输出结果:

result =这是个Array(0,1)
result =这是个两个元素的集合
result =这是个以1,0开头的数组
result =这是3加空集合的集合
result =不是我想要的
  • 特殊的模式匹配:
val (a, b) = (1, 2)
println(s"a = ${a}, b = ${b}") // a = 1, b = 2

val (q, r) = BigInt(10) /% 3
println(s"q = ${q}, r = ${r}") // q = 3, r = 1
  • 样例类

通过case关键字声明的Scala类称之为样例类或者模板类。样例类具有以下特点:

  1. 其伴生对象自动实现了apply方法,即样例类不需要new关键字也可以创建对象。
  2. 样例类的参数列表默认加了val声明,也就是说,创建类时传入参数直接可以作为对象的val属性使用。
  • 偏函数

当需要对满足条件的部分数据进行逻辑处理时,可以借助Scala提供的偏函数语法。通过创建一个PartialFunction对象并重写其中的isDefinedAtapply方法,将该对象作为参数传入collect方法,即可实现偏函数的功能。下面是将一个List中的数字进行+1操作并返回得到一个新List对象的实例。

val list = List(1, 2, 3, 4, "abc")

val addOne = new PartialFunction[Any, Int] {
    def isDefinedAt(any: Any) = {
        if (any.isInstanceOf[Int]) true
        else false
    }
    def apply(any: Any) = {
        any.asInstanceOf[Int] + 1
    }
}

val list1 = list.collect(addOne)
println(list1) // List(2,3,4,5)

前面的做法太过麻烦,在实际应用中一般采用匿名函数的方法对偏函数进行简化,上面的代码等效于:

val list = List(1, 2, 3, 4, "abc")
val list2 = list.collect{
     case i: Int => i + 1
}
println(list2) // List(2,3,4,5)

十六、泛型

Java1.5版本从Pizza语言中引入了泛型的语法,而Scala和Pizza都是同一个作者开发的,因此,Scala的泛型和Java基本是一样的,甚至Scala的泛型比Java还要更加丰富一些。
泛型是对类型的一种约束,具有不变性的特点,创建对象和声明对象时指定的泛型必须保持一致,但是在实际使用时其实是允许多态的。另外,Java中的泛型约束的作用范围只在约束的对象起作用,在赋值的过程中容易产生bug,例如:

List list = new util.ArrayList();
list.add(new Integer(1));
List<String> stringList = list; // 不报错
System.out.println(stringList.get(0)); // 报错

在Scala中定义函数或类都可以使用泛型,并且可以使用协变和逆变对泛型约束范围进行“放松”,下面是使用泛型的案例:

package com.bessen.chapter12

object Scala01_ {

    /**
      * 测试泛型
      *
      * @param args
      */
    def main(args: Array[String]): Unit = {
    
        // 测试函数的泛型
        test1[Student](new Student)
        test2[I18nStudent](new I18nStudent)

        //测试协变(可以用Student子类)
        val t1: Test1[Student] = new Test1[I18nStudent]

        //测试逆变(可以用Student父类)
        val t2: Test2[Student] = new Test2[Person]
    }

    //一般泛型定义
    def test1[T](t: T): Unit = {
    }

    //泛型约束范围为Student及其子类
    def test2[T <: Student](t: T): Unit = {
    }

    class Person {
    }

    class Student extends Person {
    }

    class I18nStudent extends Student {
    }

    //+表示协变,在创建对象时可以用其他类(子类)作为泛型
    class Test1[+Student] {
    }

    //-表示逆变,在创建对象时可以用其他类(父类)作为泛型
    class Test2[-Student] {
    }
}

GitHub源码

参考

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值