Scala —— 结合面向对象和函数式编程的简洁语言 —— 入门基础知识

Scala 入门系列 —— 基础知识

  • 数据类型
  • 字面量
    • 整型字面量
    • 浮点型字面量
    • 布尔型字面量
    • 字符字面量
    • 字符串字面量
      • 多行字符串
      • 插值字符串
  • 变量和常量
    • 类型推断机制
  • 操作符
  • 富包装类
  • 控制结构
    • 分支结构
    • 循环结构
      • do{...} while(逻辑表达式)
      • while(逻辑表达式) {...}
      • for(区间 步长 守卫){...}
      • for 循环多个生成器
      • for 推导式
      • 跳出循环
  • 输入输出
    • 输入
    • 输出
    • 写文件
    • 读文件
  • 异常控制
  • 数据结构
    • 数组(Array)
      • 定义数组
      • 遍历数组
      • 二维数组
    • 元组(Tuple)
    • 容器(Collection)
      • 序列(Sequence)
        • 列表(List)
        • 向量(Vector)
      • 集合(Set)
      • 映射(Map)
  • 函数
    • 函数特点:
    • 函数是头等公民
    • Lambda表达式
    • 类的定义
    • Scala 类的特点:
    • 类成员的可见性
    • 成员方法
    • 构造器
      • 辅助构造器
    • 单例对象
      • 伴生对象
      • 孤立对象
      • main 函数

Scala将面向对象和函数式编程结合在一种简洁的高级语言中。Scala的静态类型有助于避免复杂应用程序中的bug,它的JVM允许您构建高性能系统,并可以轻松访问庞大的库生态系统。

数据类型

Scala 和 Java 一样提供八种基础数据类型。如下所示:

  1. scala.Byte
  2. scala.Short
  3. scala.Int
  4. scala.Long
  5. scala.Float
  6. scala.Double
  7. scala.Char
  8. scala.Boolean

另外,比较特殊的是,Scala 的字符串类型和 Java 的字符串类型是共用同一个类,也就是:java.lang.String

字面量

所谓的字面量,就是从字面就能看明白类型和值的数据标量。

整型字面量

0
10
100L

浮点型字面量

3.14
3.14f

布尔型字面量

true
false

字符字面量

'A'
'\n'

字符串字面量

"Hello"

多行字符串

在 Scala 中可以使用三双引号表示多行字符串,其作用:可以在字符串中不需要使用 \n \t \r 等转义字符能自动识别文本中的特殊符号,例如:

println("""
	Hello
World!
""");

插值字符串

在普通字符串前加一个字母 s,Scala 会解析这个字符串中的变量或常量。例如

var res1 = 100;
println(s"res1=${res1}");

变量和常量

关键字 来源 被修饰的标识符是否可变
val value 不可变
val variable 可变

类型推断机制

在没有显式声明变量或者常量的数据类型时,scala 会根据字面量自动推断数据类型。例如下面两行都能实现声明一个整型变量:

var num:Int = 12
var num = 12

操作符

Scala 中绝大多数操作符和 Java 一样,举例如下:
算术运算符:±*/%
关系运算符:> < != ==
逻辑运算符:! || &&
赋值运算符:= += %=

和 Java不同的是:Scala没有自运算符,例如: i++++i 在 Scala 中是不被允许的。可以使用 += 1 来实现变量自增。

另外,在 Scala 中的操作符都被定义成了方法,例如 + 就是被定义到 Int类中的函数,也就是所有的 Int 类型都拥有 + 方法,也就是说:3 + 5 等价于 (3).+(5)

富包装类

每个基础类型都有对应的富包装类,如下表所示:

基础类型 富包装类
scala.Byte scala.runtime.RichByte
scala.Byte scala.runtime.RichByte
scala.Short scala.runtime.RichShort
scala.Int scala.runtime.RichInt
scala.Long scala.runtime.RichLong
scala.Float scala.runtime.RichFloat
scala.Double scala.runtime.RichDouble
scala.Char scala.runtime.RichChar
scala.Boolean scala.runtime.RichBoolean

富包装类中提供了很多使用的函数,例如:

3 max 5(3).max(5)
3 min 5(3).min(5)

技巧提示:在交互式环境中,可以通过 tab 键能将某个对象的所有可能使用的方法列举出来。

控制结构

分支结构

if(10 % 2 == 0) {
	println("偶数");
} else {
	println("奇数");
}

与 Java 中的 if 结构不一样的是,Scala 结构能为变量或者常量赋值,类似 Java 中的三元运算符,例如:

var str = if(10 % 2 == 0) "偶数" else "奇数"

循环结构

do{…} while(逻辑表达式)

先做,再判断是否需要继续做

do {
	println(s"num = ${num}");
	num -= 1;
} while(num > 0);

while(逻辑表达式) {…}

先判断是否需要开始做

while(num < 10) {
	println(s"num = ${num}");
	num += 1;
};

for(区间 步长 守卫){…}

Java 版本的 for 循环:

for(int i = 0;i < 20;i++) {
	System.out.println(i);
}

scala 版本的 for 循环:

for(i <- 0 until 20) {
	println(i);
}

Scala中的 for 循环和Java中截然不同,它有区间,步长,守卫等概念,如下图所示:

Scala —— 结合面向对象和函数式编程的简洁语言 —— 入门基础知识_第1张图片
此图中所示的 for 循环表示的含义是:

  • 循环将在[0, 20]的区间内按步长2依次生成0,2,4,6,…,20 这些数据,每个数据会在守卫的控制下来确定是否能进入到循环体。故,最终能输出的数据就是:0,10,20 三个数。
  • 需要注意的是:区间部分的 to 关键字用于生成 [0, 20] 区间的数据,而 until 关键字被用于生成 [0, 20) 区间的数据。

for 循环多个生成器

scala> for(i <- 0 to 2; j <- 4 until 6) println(s"i=${i} j=${j}")

i=0 j=4
i=0 j=5
i=1 j=4
i=1 j=5
i=2 j=4
i=2 j=5

多个生成器非常类似一个双重 for 循环,我们可以利用这个输出一个 九九乘法表,示例如下:

for(x <- 1 to 9; y <- 1 to x) {
  val r = x * y
  val rs = (if (r < 10) "0" else "") + r
  print(s"$y × $x = $rs\t")
  if(x == y) {
    println("")
  }
}

for 推导式

举例:试从 [0, 100] 之间,找到所有的 7 的倍数,并将 7 的倍数放到一个变量中。

var ns = for (i <- 0 to 100 if i % 7 == 0 && i != 0) yield {
	i;
}

for(i <- ns) {
	println(s"i=$i")
}

从 for 推导式会通过 yield 字段将循环体返回的数据逐个加入到 ns 容器中,得到的ns容器是包含一组Int 类型数据的一个容器,可以通过 for 循环将其迭代输出。

跳出循环

Scala 不像 Java那用为跳出循环提供 break 和continue等关键字,为了达到类似的效果,可以通过 util.control.Breaks 包下的breakable 和break。

利用 scala 的 breakable 和 break 实现 Java 的 break的效果:

import util.control.Breaks._
breakable {
	for(i <- 0 to 10) {
		if(i > 3) {
			break();
		}
		println(i);
	}
}
利用 scala 的 breakable 和 break 实现 Java 的 continue的效果:
import util.control.Breaks._
for(i <- 0 to 10) {
	breakable {
		if(i == 3) {
			break();
		}
		println(i);
	}
}

代码中的 import util.control.Breaks._ 和 Java 中 import util.control.Breaks.* 的效果是一样的。用于导入这个包下所有成员。

同时,通过这两个例子,也能看出:Scala 中的 break 跳出实际上是 breakable 语句块。只是当 breakable 的作用域在不同位置表现出的效果是不一样的而已。

输入输出

输入

Scala 通过控制台读取数据到程序中需要先导入 scala.io.StdIn 包中相关的函数,例如需要读取控制台输入的整型,我们可以这样写:

import scala.io.StdIn._;
var a = readInt();
var b = readInt();
var res = a + b;
println("两个数字之和为" + res);

输出

Scala 输出可以直接使用 print 和println函数来实现输出,它们被定义在scala.Predef 包中,而这个包是Scala 默认的包,所以才使得它们能直接被使用。

写文件

Scala 程序和 Java是完全兼容的,所以,可以直接在 Scala 中直接引用 Java的对象,例如:

import java.io.PrintWriter;
val outFile = new PrintWriter("io.txt");
outFile.println("Hello");
outFile.println("World!");
outFile.flush();
outFile.close();

读文件

Scala 提供了能直接读取文件的静态类,如下所示:

import scala.io.Source;
val inFile = Source.fromFile("io.txt");
val lines = inFile.getLines();
for(txt <- lines) {
	println(s"txt= $txt")
}

异常控制

受检异常:在编写代码的时候需要清楚知道可能抛出的异常并处理

  • 优点:让程序员在写代码的时候就能知道可能会抛出的异常
  • 缺点:可能会想太多,代码冗余

不受检异常:在运行时才抛出

  • 优点:代码简洁
  • 缺点:可能会在运行时抛出异常

一般地,Scala 会把所有的异常都处理成不受检异常!当然,也可以手动捕获异常,如下所示:

import scala.io.Source
try {
	val inputFile = Source.fromFile("io.txt", "GBK")
	val lines = inputFile.getLines();
	for(line <- lines) {
		println(line)
	}
} catch {
	case ex: java.io.FileNotFoundException => {
		println("找不到文件");
	}
	case ex: java.io.IOException => {
		println("IOException");
	}
}

数据结构

数组(Array)

数组是:具有相同数据类型的集合

定义数组

Scala 数组声明的语法格式:

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

var arr = new Array[String](3)

变量arr 是一个字符串类型的数组,数组长度为 3 ,可存储 3 个元素。我们可以为每个元素设置值,并通过索引来访问每个元素,需要注意的是:Scala 对数组下标的操作是使用小括号而不是中括号,如下所示:

arr(0) = 100;
println(arr(0));

上述方式是采用面向对象的方式声明了数组,Scala 还支持函数式编程的方式来声明数组,如下所示:

var arr:Array[String] = Array("Hello", "Scala");

var arr = Array("Hello", "Scala");

遍历数组

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

通过 size 或者 length 属性能获得数组的长度,例如:

println("长度为 " + arr.size);

二维数组

定义示例

val arr = Array.ofDim[Int](2,3)

val arr: Array[Array[Int]] = Array(Array(0, 0, 0), Array(0, 0, 0))

赋值示例

arr(0)(0) = 1

元组(Tuple)

元组是对多个不同类型的简单封装的对象。如下所示:

val tuple = (1, 3.14, “Hello”);

取值操作:

println(tuple._1);
println(tuple._2);
println(tuple._3);

需要注意的是:tuple 中的数据是常量,不可更改。

容器(Collection)

Scala 提供了一套非常方便的容器库,比如序列,集合和映射等,它们的特质都被定义到了scala.collection 包里统一管理。从宏观角度来看,它们的层次结构如下所示:

Scala —— 结合面向对象和函数式编程的简洁语言 —— 入门基础知识_第2张图片

序列(Sequence)

序列是按照一定顺序存储一系列数据的容器。

列表(List)

var ls = List(1, 2, 3)

println(ls(0));
ls = ls.updated(1, 33);
println(ls(1));
println(ls.size);
println(ls.head);
println(ls.tail);

var ls2 = 4::ls;
println(ls2);

var ls3 = 3::4::55::Nil;
println(ls3);

默认情况下,声明的列表是不可变集,执行 updated 方法后,不会修改原List,而是生成新的List返回。列表结合符号 :: 是从右向左链接,并且生成新的List,同样,也不会修改原List。

向量(Vector)

val v = Vector(1,2);
var v2 = 3+:v;
println(v);
println(v2);

集合(Set)

集合是一系列不重复元素的容器。

var s = Set("Hello", "World");
s += "Spark";
println(s.contains("Spark"));

映射(Map)

映射是一系列键值对元素容器。

var m = Map("name" -> "Timor", "age" -> 18);
m.updated("name", "jack");
println(m("name"));
m += ("weight" -> 33.5);
println(m("weight"));

函数

定义一个函数需要使用 def 关键字,def 是 define 的缩写。
Scala 标准定义示例:

Scala —— 结合面向对象和函数式编程的简洁语言 —— 入门基础知识_第3张图片

Java 中和上述定义类似的写法:
int add(int a, int b) {
system.out.println(“正在执行 add 函数”);
return a + b;
}

和 Java 定义函数的四个不同:

  1. 参数列表的数据类型定义的位置
  2. 返回类型的位置
  3. 小括号和大括号之间有等于符号
  4. Scala 函数返回没有return关键字
  5. 定义 Scala 函数需要使用 def 关键字

函数特点:

  1. 如果函数定义了返回类型,必须在函数体中返回对应的结果,且必须在函数体的最后一行返回指定数据类型的结果。
  2. 如果函数定义了无返回类型,则函数体中所有的代码都将视作无返回。
  3. 如果函数没有定义返回类型,Scala 会自动推测。

函数是头等公民

Scala 的函数的声明一个像定义变量一样来实现,如下所示:
Scala —— 结合面向对象和函数式编程的简洁语言 —— 入门基础知识_第4张图片
这样依赖,Scala 的函数可以像变量一样作为参数传递到函数中被使用,如下所示:

var arr = Array(2, 3, 6);
def map(ns:Array[Int], a:(Int)=>String) = {
	var i = 0;
	var ns2 = new Array[String](ns.size);
	while(i < ns.size) {
		ns2(i) = a(ns(i));
		i += 1;
	}
	ns2;
}
var res = map(arr, v => "192.168.1." + v);

当 Lambda 表达式的参数只有一个的时候,可以省略小括号,将上述代码放置到交互式环境中执行完成之后,res 的值是:

res: Array[String] = Array(192.168.1.2, 192.168.1.3, 192.168.1.6)

类似 map这种能够在函数中调用函数的函数,被称之为高阶函数。Scala 的提供了大量的高阶函数来给开发者使用,如下所示:

scala> var arr = Array(2, 4, 7)
arr: Array[Int] = Array(2, 4, 7)

scala> arr.map(v => "192.168.1." + v)
res0: Array[String] = Array(192.168.1.2, 192.168.1.4, 192.168.1.7)

Lambda表达式

  1. Lambda 表达式也被称之为:λ演算
  2. Lambda 表达式也被称之为:匿名函数
  3. 当Lambda 表达式的参数只有一个的时候,可以省略小括号
    例:v => {"192.168.1." + v }
  4. 当Lambda 表达式的函数体只有一句话的时候,可以省略大括号,
    例: (v) => "192.168.1." + v
  5. 当Lambda 表达式的参数只有一个,且该参数在函数体中只被使用了一次,则参数列表和 => 均可以被省略,例: "192.168.1." + _

类的定义

class Counter {
	val value = 0;
	var variable = 0;
	def say() = {
		println("Hi");
	}
}

在交互式环境下使用该类:

scala> var a = new Counter
a: Counter = Counter@75b76c78

scala> println(a.value)
0

scala> a.variable = 200
a.variable: Int = 200

scala> a.say
Hi

Scala 类的特点:

  1. 如果主构造器没有参数,在实例化的时候省略小括号。
  2. 如果成员函数是无参函数,允许在调用的时候省略小括号。

类成员的可见性

很多时候,我们会选择使用一些关键字来修改成员,比如 public,protected,private等。来决定成员的可见范围。在 Scala 中,如果没有加任何修饰关键字的话,默认情况下是公有的。
一般地,我们会选择将字段设置为私有,然后声明 getter和setter 方法,使其能在外部被调用,例如:

class Counter {
	// age 字段
	private var _age = 0;
	def age = _age;
	def age_=(v:Int) = {
		_age = v;
	}
	// name 字段
	private var _name = "";
	def name = _name;
	def name_=(v:String) = {
		_name = v;
	}
}

在上述代码中,name和age就相当于 getName 和getAge 方法,name_= 和age_= 就相当于setName和setAge方法,具体使用如下:

scala> val a = new Counter
a: Counter = Counter@6d4b21a6

scala> a.age = 10
a.age: Int = 10

scala> a.name_=("timor")

scala> a.name
res19: String = timor

成员方法

方法的定义需要注意以下几点:

  1. 当类的成员方法没有参数的时候,可以把小括号省略。
  2. 当类的成员方法被调用的时候,被允许使用大括号。
  3. 当类的成员方法是无返回(返回类型是 Unit)的时候,被允许省略 :Unit = 部分。
  4. 当类的成员方法只有一个参数的时候,可以省略圆点,采用中缀操作符的调用方法。

在上述Counter 类中增加如下函数

def max(num:Int) = {
	if (num > this._age) num else this._age;
}

在调用的时候可以按如下方式:

scala> a max 2
res24: Int = 10
scala> a.max(2)
res25: Int = 10

代码中的 a max 2 就是将函数max采用了中缀操作符的调用方法

构造器

在 Scala 中,类的主体就是类的主构造器。在类名后允许跟一对小括号用于在构造的时候传参,看起来就像一个函数一样,如下所示:

class Counter(var height:Int, val gender:Boolean, weight:Double) {
}

但是和函数不同的是,主构造器的参数名称前被允许使用 var 或 val 关键字。如果使用var关键字,Scala会自动为该名称生成对应的getter/setter方法。而使用val关键字,则只生成对应的 getter方法,不提供 setter方法。如果没有使用 var或者val关键字,则就是起到往主构造函数中传递参数的作用。

辅助构造器

在Scala 类中,可以有多个辅助构造器,但是每个辅助构造器必须直接或间接调用主构造器才能被允许使用。如下所示:

class Counter(var height:Int, val gender:Boolean, weight:Double) {
	println("main constructor");
	def this() {
		this(22, true, 3.14);
		println("first assist constructor");
	}
	def this(weight:Double) {
		this();
		println("second assist constructor");
	}
}

单例对象

Scala 单例对象提供了和Java静态成员同样的功能,所有被定义到单例对象中的字段和方法都是静态的。其优点就是能够在不用实例化的时候就能直接使用其成员字段或者成员方法。如下所示:

object Counter {
	private var value = 100;
	def increase() = {
		value += 1;
		value;
	}
}

使用示例:

scala> Counter.increase
res36: Int = 101

伴生对象

在同一个文件中,如果定义了和单例对象同名的类,它们两个就互为伴生关系,同名类就是这个单例对象的伴生类,而这个单例对象就是同名类的伴生对象。

孤立对象

在同一个文件中,如果没有和单例对象同名的类,则该单例对象就是孤立对象。

main 函数

在所有的应用程序中,都必须有一个唯一的入口函数,而这个入口函数必须是静态的,所以,Scala 应用的main函数都是被定义到单例对象中。例如:

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

此时,我们可以把这段代码保存到 Counter.scala 文件,然后使用 scalac 命令编译该文件,最后使用 scala 命令来执行编译后的目标程序。

Scala —— 结合面向对象和函数式编程的简洁语言 —— 入门基础知识_第5张图片

你可能感兴趣的:(#,Scala,编程语言,java,大数据)